-
Notifications
You must be signed in to change notification settings - Fork 331
Expand file tree
/
Copy pathFieldInjectionForkedTest.groovy
More file actions
259 lines (220 loc) · 9.11 KB
/
FieldInjectionForkedTest.groovy
File metadata and controls
259 lines (220 loc) · 9.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
package context
import datadog.trace.agent.test.AbortTransformationException
import datadog.trace.agent.test.InstrumentationSpecification
import datadog.trace.config.inversion.ConfigHelper
import datadog.trace.agent.test.utils.ClasspathUtils
import datadog.trace.api.InstrumenterConfig
import datadog.trace.test.util.GCUtils
import net.bytebuddy.agent.ByteBuddyAgent
import net.bytebuddy.utility.JavaModule
import net.sf.cglib.proxy.Enhancer
import net.sf.cglib.proxy.MethodInterceptor
import net.sf.cglib.proxy.MethodProxy
import spock.lang.IgnoreIf
import java.lang.instrument.ClassDefinition
import java.lang.ref.WeakReference
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.util.concurrent.atomic.AtomicReference
import static context.FieldInjectionTestInstrumentation.DisabledKeyClass
import static context.FieldInjectionTestInstrumentation.IncorrectCallUsageKeyClass
import static context.FieldInjectionTestInstrumentation.IncorrectContextClassUsageKeyClass
import static context.FieldInjectionTestInstrumentation.IncorrectKeyClassUsageKeyClass
import static context.FieldInjectionTestInstrumentation.InvalidInheritsSerializableKeyClass
import static context.FieldInjectionTestInstrumentation.InvalidSerializableKeyClass
import static context.FieldInjectionTestInstrumentation.KeyClass
import static context.FieldInjectionTestInstrumentation.UntransformableKeyClass
import static context.FieldInjectionTestInstrumentation.ValidInheritsSerializableKeyClass
import static context.FieldInjectionTestInstrumentation.ValidSerializableKeyClass
class FieldInjectionForkedTest extends InstrumentationSpecification {
@Override
protected void configurePreAgent() {
super.configurePreAgent()
// Opt out of strict config validation - test module loads test instrumentations with fake names
ConfigHelper.get().setConfigInversionStrict(ConfigHelper.StrictnessPolicy.TEST)
}
@Override
void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
if (typeName?.endsWith("UntransformableKeyClass")) {
throw new AbortTransformationException(
"Aborting transform for class name = " + typeName + ", loader = " + classLoader)
}
super.onDiscovery(typeName, classLoader, module, loaded)
}
def "#keyClassName structure modified = #shouldModifyStructure"() {
setup:
boolean hasField = false
boolean isPrivate = false
boolean isTransient = false
for (Field field : keyClass.getDeclaredFields()) {
if (field.getName().startsWith("__datadog")) {
isPrivate = Modifier.isPrivate(field.getModifiers())
isTransient = Modifier.isTransient(field.getModifiers())
hasField = true
break
}
}
boolean hasAccessorInterface = false
for (Class inter : keyClass.getInterfaces()) {
if (inter.getName() == 'datadog.trace.bootstrap.FieldBackedContextAccessor') {
hasAccessorInterface = true
}
}
expect:
hasField == shouldModifyStructure
isPrivate == shouldModifyStructure
isTransient == shouldModifyStructure
hasAccessorInterface == shouldModifyStructure
keyClass.newInstance().isInstrumented() == isInstrumented
where:
keyClass | shouldModifyStructure | isInstrumented
KeyClass | true | true
UntransformableKeyClass | false | false
ValidSerializableKeyClass | true | true
InvalidSerializableKeyClass | true | true
ValidInheritsSerializableKeyClass | true | true
InvalidInheritsSerializableKeyClass | true | true
keyClassName = keyClass.getSimpleName()
}
def "correct api usage stores state in map #instance1.class.name"() {
when:
instance1.incrementContextCount()
then:
instance1.incrementContextCount() == 2
instance2.incrementContextCount() == 1
where:
instance1 | instance2
new KeyClass() | new KeyClass()
new UntransformableKeyClass() | new UntransformableKeyClass()
}
def "get/put test #instance1.class.name"() {
when:
instance1.putContextCount(10)
instance1.putContextCount2(10)
then:
instance1.getContextCount() == 10
instance1.getContextCount2() == 10
where:
instance1 | _
new KeyClass() | _
new UntransformableKeyClass() | _
}
@IgnoreIf({ !InstrumenterConfig.get().isSerialVersionUIDFieldInjection() })
def "serializability not impacted #serializable"() {
expect:
serialVersionUID(serializable) == serialVersionUID
where:
serializable | serialVersionUID // These are calculated with the corresponding declarations in FieldInjectionTestInstrumentation removed
ValidSerializableKeyClass | 123
InvalidSerializableKeyClass | -5663127853206342441L
ValidInheritsSerializableKeyClass | 456
InvalidInheritsSerializableKeyClass | -4774694079403599336L
}
static final long serialVersionUID(Class<? extends Serializable> klass) throws Exception {
try {
def field = klass.getDeclaredField("serialVersionUID")
field.setAccessible(true)
return (long) field.get(null)
} catch (NoSuchFieldException ignored) {
def method = ObjectStreamClass.getDeclaredMethod("computeDefaultSUID", Class)
method.setAccessible(true)
return (long) method.invoke(null, klass)
}
}
def "works with cglib enhanced instances which duplicates context getter and setter methods"() {
setup:
Enhancer enhancer = new Enhancer()
enhancer.setSuperclass(KeyClass)
enhancer.setCallback(new MethodInterceptor() {
@Override
Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
return arg3.invokeSuper(arg0, arg2)
}
})
when:
(KeyClass) enhancer.create()
then:
noExceptionThrown()
}
//@Flaky("awaitGC is flaky")
def "backing map should not create strong refs to key class instances #keyValue.get().getClass().getName()"() {
when:
final int count = keyValue.get().incrementContextCount()
WeakReference<KeyClass> instanceRef = new WeakReference(keyValue.get())
keyValue.set(null)
GCUtils.awaitGC(instanceRef)
then:
instanceRef.get() == null
count == 1
where:
keyValue | _
new AtomicReference(new KeyClass()) | _
new AtomicReference(new UntransformableKeyClass()) | _
}
def "context classes are retransform safe"() {
when:
ByteBuddyAgent.getInstrumentation().retransformClasses(KeyClass)
ByteBuddyAgent.getInstrumentation().retransformClasses(UntransformableKeyClass)
then:
new KeyClass().isInstrumented()
!new UntransformableKeyClass().isInstrumented()
new KeyClass().incrementContextCount() == 1
new UntransformableKeyClass().incrementContextCount() == 1
}
def "context classes are redefine safe"() {
when:
ByteBuddyAgent.getInstrumentation().redefineClasses(new ClassDefinition(KeyClass, ClasspathUtils.convertToByteArray(KeyClass)))
ByteBuddyAgent.getInstrumentation().redefineClasses(new ClassDefinition(UntransformableKeyClass, ClasspathUtils.convertToByteArray(UntransformableKeyClass)))
then:
new KeyClass().isInstrumented()
!new UntransformableKeyClass().isInstrumented()
new KeyClass().incrementContextCount() == 1
new UntransformableKeyClass().incrementContextCount() == 1
}
def "incorrect key class usage fails at class load time"() {
expect:
!new IncorrectKeyClassUsageKeyClass().isInstrumented()
}
def "incorrect context class usage fails at class load time"() {
expect:
!new IncorrectContextClassUsageKeyClass().isInstrumented()
}
def "incorrect call usage fails at class load time"() {
expect:
!new IncorrectCallUsageKeyClass().isInstrumented()
}
}
/**
* Make sure that fields don't get injected into the class if it is disabled via system properties.
*/
class FieldInjectionDisabledForkedTest extends InstrumentationSpecification {
void configurePreAgent() {
super.configurePreAgent()
// Opt out of strict config validation - test module loads test instrumentations with fake names
ConfigHelper.get().setConfigInversionStrict(ConfigHelper.StrictnessPolicy.TEST)
injectSysConfig("dd.trace.runtime.context.field.injection", "false")
}
def "Check that structure is not modified when structure modification is disabled"() {
setup:
def keyClass = DisabledKeyClass
boolean hasField = false
for (Field field : keyClass.getDeclaredFields()) {
if (field.getName().startsWith("__datadog")) {
hasField = true
break
}
}
boolean hasAccessorInterface = false
for (Class inter : keyClass.getInterfaces()) {
if (inter.getName() == 'datadog.trace.bootstrap.FieldBackedContextAccessor') {
hasAccessorInterface = true
}
}
expect:
hasField == false
hasAccessorInterface == false
keyClass.newInstance().isInstrumented() == true
}
}