Skip to content

Commit 6143528

Browse files
Add IAST telemetry annotations to bytebuddy advices (#5430)
1 parent 15dfde0 commit 6143528

File tree

138 files changed

+1139
-626
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

138 files changed

+1139
-626
lines changed

buildSrc/call-site-instrumentation-plugin/src/main/java/datadog/trace/plugin/csi/impl/ext/IastExtension.java

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.github.javaparser.ast.stmt.BlockStmt;
2727
import com.github.javaparser.ast.stmt.IfStmt;
2828
import com.github.javaparser.ast.stmt.Statement;
29+
import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration;
2930
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
3031
import datadog.trace.plugin.csi.AdviceGenerator.CallSiteResult;
3132
import datadog.trace.plugin.csi.Extension;
@@ -37,6 +38,7 @@
3738
import datadog.trace.plugin.csi.util.MethodType;
3839
import java.io.File;
3940
import java.io.FileNotFoundException;
41+
import java.lang.reflect.Field;
4042
import java.net.URL;
4143
import java.net.URLClassLoader;
4244
import java.nio.file.Files;
@@ -116,7 +118,7 @@ private void addTelemetry(final Configuration configuration, final CallSiteResul
116118
if (metaData != null) {
117119
final List<LambdaExpr> adviceLambdas = filterAdviceLambdas(advices, callSiteMethod);
118120
for (final LambdaExpr advice : adviceLambdas) {
119-
addTelemetryToAdvice(advice, metaData);
121+
addTelemetryToAdvice(resolver, advice, metaData);
120122
hasTelemetry = true;
121123
}
122124
}
@@ -166,21 +168,24 @@ private CompilationUnit findOriginalCallSite(
166168
return parseSourceFile(configuration, resolver, javaFile);
167169
}
168170

169-
private void addTelemetryToAdvice(final LambdaExpr adviceLambda, final AdviceMetadata metaData) {
171+
private void addTelemetryToAdvice(
172+
final TypeResolver resolver, final LambdaExpr adviceLambda, final AdviceMetadata metaData) {
170173
final BlockStmt lambdaBody = adviceLambda.getBody().asBlockStmt();
171174
final String metric = getMetricName(metaData);
175+
final String tagValue = getMetricTagValue(resolver, metaData);
172176
final String instrumentedMetric = "INSTRUMENTED_" + metric;
173177
final IfStmt instrumentedStatement =
174178
new IfStmt()
175179
.setCondition(isEnabledCondition(instrumentedMetric))
176180
.setThenStmt(
177-
new BlockStmt().addStatement(addTelemetryCollectorMethod(instrumentedMetric)));
181+
new BlockStmt()
182+
.addStatement(addTelemetryCollectorMethod(instrumentedMetric, tagValue)));
178183
lambdaBody.addStatement(0, instrumentedStatement);
179184
final String executedMetric = "EXECUTED_" + metric;
180185
final IfStmt executedStatement =
181186
new IfStmt()
182187
.setCondition(isEnabledCondition(executedMetric))
183-
.setThenStmt(addTelemetryCollectorByteCode(executedMetric));
188+
.setThenStmt(addTelemetryCollectorByteCode(executedMetric, tagValue));
184189
lambdaBody.addStatement(1, executedStatement);
185190
}
186191

@@ -191,19 +196,27 @@ private static Expression isEnabledCondition(final String metric) {
191196
.addArgument(accessLocalField("verbosity"));
192197
}
193198

194-
private static MethodCallExpr addTelemetryCollectorMethod(final String metric) {
195-
return new MethodCallExpr()
196-
.setScope(new NameExpr(IAST_METRIC_COLLECTOR_CLASS))
197-
.setName("add")
198-
.addArgument(
199-
new FieldAccessExpr().setScope(new NameExpr(IAST_METRIC_CLASS)).setName(metric))
200-
.addArgument(intLiteral(1));
199+
private static MethodCallExpr addTelemetryCollectorMethod(
200+
final String metric, final String tagValue) {
201+
final MethodCallExpr method =
202+
new MethodCallExpr()
203+
.setScope(new NameExpr(IAST_METRIC_COLLECTOR_CLASS))
204+
.setName("add")
205+
.addArgument(
206+
new FieldAccessExpr().setScope(new NameExpr(IAST_METRIC_CLASS)).setName(metric));
207+
if (tagValue != null) {
208+
method.addArgument(new StringLiteralExpr(tagValue));
209+
}
210+
method.addArgument(intLiteral(1));
211+
return method;
201212
}
202213

203-
private static BlockStmt addTelemetryCollectorByteCode(final String metric) {
214+
private static BlockStmt addTelemetryCollectorByteCode(
215+
final String metric, final String tagValue) {
204216
final BlockStmt stmt = new BlockStmt();
205217
// this code generates the java source code needed to provide the bytecode for the statement
206-
// IastTelemetryCollector.add($"{metric}, 1);
218+
// IastTelemetryCollector.add(${metric}, 1); or IastTelemetryCollector.add(${metric}, ${tag},
219+
// 1);
207220
stmt.addStatement(
208221
new MethodCallExpr()
209222
.setScope(new NameExpr("handler"))
@@ -213,12 +226,23 @@ private static BlockStmt addTelemetryCollectorByteCode(final String metric) {
213226
.addArgument(new StringLiteralExpr(IAST_METRIC_INTERNAL_NAME))
214227
.addArgument(new StringLiteralExpr(metric))
215228
.addArgument(new StringLiteralExpr("L" + IAST_METRIC_INTERNAL_NAME + ";")));
229+
if (tagValue != null) {
230+
stmt.addStatement(
231+
new MethodCallExpr()
232+
.setScope(new NameExpr("handler"))
233+
.setName("loadConstant")
234+
.addArgument(new StringLiteralExpr(tagValue)));
235+
}
216236
stmt.addStatement(
217237
new MethodCallExpr()
218238
.setScope(new NameExpr("handler"))
219239
.setName("instruction")
220240
.addArgument(
221-
new FieldAccessExpr().setScope(new NameExpr(OPCODES_FQDN)).setName("LCONST_1")));
241+
new FieldAccessExpr().setScope(new NameExpr(OPCODES_FQDN)).setName("ICONST_1")));
242+
final String descriptor =
243+
tagValue != null
244+
? "(L" + IAST_METRIC_INTERNAL_NAME + ";Ljava/lang/String;I)V"
245+
: "(L" + IAST_METRIC_INTERNAL_NAME + ";I)V";
222246
stmt.addStatement(
223247
new MethodCallExpr()
224248
.setScope(new NameExpr("handler"))
@@ -227,19 +251,49 @@ private static BlockStmt addTelemetryCollectorByteCode(final String metric) {
227251
new FieldAccessExpr().setScope(new NameExpr(OPCODES_FQDN)).setName("INVOKESTATIC"))
228252
.addArgument(new StringLiteralExpr(IAST_METRIC_COLLECTOR_INTERNAL_NAME))
229253
.addArgument(new StringLiteralExpr("add"))
230-
.addArgument(new StringLiteralExpr("(L" + IAST_METRIC_INTERNAL_NAME + ";J)V"))
254+
.addArgument(new StringLiteralExpr(descriptor))
231255
.addArgument(new BooleanLiteralExpr(false)));
232256
return stmt;
233257
}
234258

235259
private static String getMetricName(final AdviceMetadata metaData) {
236-
final StringBuilder metric = new StringBuilder(metaData.getKind());
237-
if (metaData.getTag() != null) {
238-
// Uses the name of the field to compose the name of the metric
239-
final Expression tag = metaData.getTag();
240-
metric.append("_").append(tag.asFieldAccessExpr().getName());
260+
final AnnotationExpr kind = metaData.getKind();
261+
return kind.getName().getId().toUpperCase();
262+
}
263+
264+
private static String getMetricTagValue(
265+
final TypeResolver resolver, final AdviceMetadata metadata) {
266+
if (metadata.getTag() == null) {
267+
return null;
268+
}
269+
final Expression tag = metadata.getTag();
270+
if (tag.isStringLiteralExpr()) {
271+
return tag.asStringLiteralExpr().getValue();
272+
} else {
273+
return getFieldValue(resolver, tag.asFieldAccessExpr());
274+
}
275+
}
276+
277+
private static String getFieldValue(final TypeResolver resolver, final FieldAccessExpr tag) {
278+
final FieldAccessExpr fieldAccessExpr = tag.asFieldAccessExpr();
279+
final ResolvedFieldDeclaration value = fieldAccessExpr.resolve().asField();
280+
try {
281+
final Field field = getField(value);
282+
field.setAccessible(true);
283+
return (String) field.get(field.getDeclaringClass());
284+
} catch (Exception e) {
285+
throw new RuntimeException(e);
286+
}
287+
}
288+
289+
private static Field getField(final ResolvedFieldDeclaration resolved) {
290+
try {
291+
final Field field = resolved.getClass().getDeclaredField("field");
292+
field.setAccessible(true);
293+
return (Field) field.get(resolved);
294+
} catch (Exception e) {
295+
throw new RuntimeException(e);
241296
}
242-
return metric.toString();
243297
}
244298

245299
/** Find all advice lambdas in the generated call site provider */
@@ -367,10 +421,10 @@ private static Expression getAnnotationExpression(final AnnotationExpr expr) {
367421
}
368422

369423
private static class AdviceMetadata {
370-
private final String kind;
424+
private final AnnotationExpr kind;
371425
private final Expression tag;
372426

373-
private AdviceMetadata(final String kind, final Expression tag) {
427+
private AdviceMetadata(final AnnotationExpr kind, final Expression tag) {
374428
this.kind = kind;
375429
this.tag = tag;
376430
}
@@ -381,8 +435,7 @@ private static AdviceMetadata findAdviceMetadata(final BodyDeclaration<?> target
381435
.map(
382436
annotation -> {
383437
final Expression tag = getAnnotationExpression(annotation);
384-
final String typeName = annotation.getName().getId();
385-
return new AdviceMetadata(typeName.toUpperCase(), tag);
438+
return new AdviceMetadata(annotation, tag);
386439
})
387440
.findFirst()
388441
.orElse(null);
@@ -395,7 +448,7 @@ private static boolean isAdviceAnnotation(final AnnotationExpr expr) {
395448
|| identifier.equals("Sink");
396449
}
397450

398-
public String getKind() {
451+
public AnnotationExpr getKind() {
399452
return kind;
400453
}
401454

buildSrc/call-site-instrumentation-plugin/src/test/groovy/datadog/trace/plugin/csi/impl/AdviceGeneratorTest.groovy

Lines changed: 4 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,20 @@
11
package datadog.trace.plugin.csi.impl
22

3-
import com.github.javaparser.JavaParser
4-
import com.github.javaparser.ParserConfiguration
5-
import com.github.javaparser.ast.CompilationUnit
6-
import com.github.javaparser.ast.Node
7-
import com.github.javaparser.ast.body.MethodDeclaration
8-
import com.github.javaparser.ast.body.TypeDeclaration
9-
import com.github.javaparser.ast.expr.ConditionalExpr
10-
import com.github.javaparser.ast.expr.MethodCallExpr
11-
import com.github.javaparser.symbolsolver.JavaSymbolSolver
3+
124
import datadog.trace.agent.tooling.csi.CallSite
135
import datadog.trace.agent.tooling.csi.CallSites
146
import datadog.trace.plugin.csi.AdviceGenerator
7+
import datadog.trace.plugin.csi.impl.assertion.AssertBuilder
8+
import datadog.trace.plugin.csi.impl.assertion.CallSiteAssert
159
import groovy.transform.CompileDynamic
16-
import org.objectweb.asm.Type
1710
import spock.lang.Requires
1811
import spock.lang.TempDir
1912

2013
import javax.servlet.ServletRequest
2114
import java.lang.invoke.MethodHandles
2215
import java.lang.invoke.MethodType
23-
import java.lang.reflect.Executable
24-
import java.lang.reflect.Method
25-
import java.security.MessageDigest
2616

2717
import static CallSiteFactory.pointcutParser
28-
import static datadog.trace.plugin.csi.impl.CallSiteFactory.typeResolver
29-
import static datadog.trace.plugin.csi.util.CallSiteUtils.classNameToType
3018

3119
@CompileDynamic
3220
final class AdviceGeneratorTest extends BaseCsiPluginTest {
@@ -464,127 +452,9 @@ final class AdviceGeneratorTest extends BaseCsiPluginTest {
464452
return new AdviceGeneratorImpl(targetFolder, pointcutParser())
465453
}
466454

467-
private static CompilationUnit parseJavaFile(final File file)
468-
throws FileNotFoundException {
469-
final JavaSymbolSolver solver = new JavaSymbolSolver(typeResolver());
470-
final JavaParser parser = new JavaParser(new ParserConfiguration().setSymbolResolver(solver));
471-
return parser.parse(file).getResult().get();
472-
}
473-
474-
private static Map<String, MethodDeclaration> groupMethods(final TypeDeclaration<?> classNode) {
475-
return classNode.methods.groupBy { it.name.asString() }
476-
.collectEntries { key, value -> [key, value.get(0)] }
477-
}
478-
479-
private static List<Class<?>> getImplementedTypes(final TypeDeclaration<?> type) {
480-
return type.asClassOrInterfaceDeclaration().implementedTypes.collect {
481-
final resolved = it.asClassOrInterfaceType().resolve()
482-
return resolved.typeDeclaration.get().clazz
483-
}
484-
}
485-
486-
private static Executable resolveMethod(final MethodCallExpr methodCallExpr) {
487-
final resolved = methodCallExpr.resolve()
488-
return resolved.@method as Method
489-
}
490-
491-
private static List<MethodCallExpr> getMethodCalls(final MethodDeclaration method) {
492-
return method.body.get().statements.findAll {
493-
it.isExpressionStmt() && it.asExpressionStmt().expression.isMethodCallExpr()
494-
}.collect {
495-
it.asExpressionStmt().expression.asMethodCallExpr()
496-
}
497-
}
498-
499455
private static void assertCallSites(final File generated, @DelegatesTo(CallSiteAssert) final Closure closure) {
500-
final asserter = buildAsserter(generated)
456+
final asserter = new AssertBuilder(generated).build()
501457
closure.delegate = asserter
502458
closure(asserter)
503459
}
504-
505-
private static CallSiteAssert buildAsserter(final File file) {
506-
final javaFile = parseJavaFile(file)
507-
assert javaFile.parsed == Node.Parsedness.PARSED
508-
final adviceClass = javaFile.primaryType.get()
509-
final interfaces = getImplementedTypes(adviceClass)
510-
final methods = groupMethods(adviceClass)
511-
Executable enabled = null
512-
List<String> enabledArgs = null
513-
if (interfaces.contains(CallSites.HasEnabledProperty)) {
514-
final isEnabled = methods['isEnabled']
515-
final returnStatement = isEnabled.body.get().statements.first.get().asReturnStmt()
516-
final enabledMethodCall = returnStatement.expression.get().asMethodCallExpr()
517-
enabled = resolveMethod(enabledMethodCall)
518-
enabledArgs = enabledMethodCall.getArguments().collect { it.asStringLiteralExpr().asString() }
519-
}
520-
final accept = methods['accept']
521-
final methodCalls = getMethodCalls(accept)
522-
final addHelpers = methodCalls.find { it.name.toString() == 'addHelpers' }
523-
assert addHelpers.scope.get().toString() == 'container'
524-
assert addHelpers.name.toString() == 'addHelpers'
525-
final helpers = addHelpers.getArguments().collect { typeResolver().resolveType(classNameToType(it.asStringLiteralExpr().asString())) }
526-
final addAdvices = methodCalls.findAll { it.name.toString() == 'addAdvice' }
527-
final advices = addAdvices.collect {
528-
def (owner, method, descriptor) = it.arguments.subList(0, 3)*.asStringLiteralExpr()*.asString()
529-
final handlerLambda = it.arguments[3].asLambdaExpr()
530-
final advice = handlerLambda.body.asBlockStmt().statements*.toString()
531-
return new AdviceAssert([
532-
owner : owner,
533-
method : method,
534-
descriptor: descriptor,
535-
statements: advice
536-
])
537-
}
538-
return new CallSiteAssert([
539-
interfaces : interfaces,
540-
helpers : helpers,
541-
advices : advices,
542-
enabled : enabled,
543-
enabledArgs: enabledArgs
544-
])
545-
}
546-
547-
static class CallSiteAssert {
548-
private Collection<Class<?>> interfaces
549-
private Collection<Class<?>> helpers
550-
private Collection<AdviceAssert> advices
551-
private Method enabled
552-
private Collection<String> enabledArgs
553-
554-
void interfaces(Class<?>... values) {
555-
assert values.toList() == interfaces
556-
}
557-
558-
void helpers(Class<?>... values) {
559-
assert values.toList() == helpers
560-
}
561-
562-
void advices(int index, @DelegatesTo(AdviceAssert) Closure closure) {
563-
final asserter = advices[index]
564-
closure.delegate = asserter
565-
closure(asserter)
566-
}
567-
568-
void enabled(Method method, String... args) {
569-
assert method == enabled
570-
assert args.toList() == enabledArgs
571-
}
572-
}
573-
574-
static class AdviceAssert {
575-
private String owner
576-
private String method
577-
private String descriptor
578-
private Collection<String> statements
579-
580-
void pointcut(String owner, String method, String descriptor) {
581-
assert owner == this.owner
582-
assert method == this.method
583-
assert descriptor == this.descriptor
584-
}
585-
586-
void statements(String... values) {
587-
assert values.toList() == statements
588-
}
589-
}
590460
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package datadog.trace.plugin.csi.impl.assertion
2+
3+
class AdviceAssert {
4+
protected String owner
5+
protected String method
6+
protected String descriptor
7+
protected Collection<String> statements
8+
9+
void pointcut(String owner, String method, String descriptor) {
10+
assert owner == this.owner
11+
assert method == this.method
12+
assert descriptor == this.descriptor
13+
}
14+
15+
void statements(String... values) {
16+
assert values.toList() == statements
17+
}
18+
}

0 commit comments

Comments
 (0)