-
Notifications
You must be signed in to change notification settings - Fork 292
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7873 from DataDog/dougqh/advice-annotation-checking
ByteBuddy Exception Suppression Checking
- Loading branch information
Showing
9 changed files
with
494 additions
and
0 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
dd-java-agent/instrumentation-annotation-processor/build.gradle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
apply from: "$rootDir/gradle/java.gradle" |
188 changes: 188 additions & 0 deletions
188
dd-java-agent/instrumentation-annotation-processor/src/main/java/datadog/apt/AnnoUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package datadog.apt; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import javax.lang.model.element.AnnotationMirror; | ||
import javax.lang.model.element.AnnotationValue; | ||
import javax.lang.model.element.Element; | ||
import javax.lang.model.element.ExecutableElement; | ||
import javax.lang.model.element.TypeElement; | ||
import javax.lang.model.element.VariableElement; | ||
import javax.lang.model.type.TypeMirror; | ||
import javax.lang.model.util.AbstractAnnotationValueVisitor6; | ||
|
||
/** | ||
* Utility class that works AnnotationMirrors & AnnotationValues | ||
* | ||
* <p>By convention, nulls pass through nicely to allow easy composition | ||
*/ | ||
public final class AnnoUtils { | ||
private AnnoUtils() {} | ||
|
||
/** Finds the AnnotationMirror on Element of type TypeElement */ | ||
public static final AnnotationMirror findAnnotation(Element element, TypeElement annoType) { | ||
if (element == null || annoType == null) return null; | ||
|
||
for (AnnotationMirror annoMirror : element.getAnnotationMirrors()) { | ||
if (isA(annoMirror, annoType)) return annoMirror; | ||
} | ||
return null; | ||
} | ||
|
||
public static final AnnotationMirror findAnnotation(Element element, Class<?> annoClass) { | ||
if (element == null || annoClass == null) return null; | ||
|
||
for (AnnotationMirror annoMirror : element.getAnnotationMirrors()) { | ||
if (isA(annoMirror, annoClass)) return annoMirror; | ||
} | ||
return null; | ||
} | ||
|
||
public static final boolean isSuppressed(Element element, String checkName) { | ||
if (element == null) return false; | ||
if (isSuppressedHelper(element, checkName)) return true; | ||
|
||
for (Element enclosingElement = element.getEnclosingElement(); | ||
enclosingElement != null; | ||
enclosingElement = enclosingElement.getEnclosingElement()) { | ||
if (isSuppressedHelper(enclosingElement, checkName)) return true; | ||
} | ||
return false; | ||
} | ||
|
||
private static final boolean isSuppressedHelper(Element element, String checkName) { | ||
AnnotationMirror mirror = findAnnotation(element, SuppressWarnings.class); | ||
AnnotationValue value = getValue(mirror); | ||
List<? extends AnnotationValue> suppressionList = asList(value); | ||
|
||
return contains(suppressionList, checkName); | ||
} | ||
|
||
public static final boolean contains(List<? extends AnnotationValue> annoList, Object value) { | ||
if (annoList == null) return false; | ||
|
||
for (AnnotationValue annoValue : annoList) { | ||
if (is(annoValue, value)) return true; | ||
} | ||
return false; | ||
} | ||
|
||
public static final boolean isA(AnnotationMirror annoMirror, TypeElement annoType) { | ||
if (annoMirror == null) return false; | ||
|
||
return annoMirror.getAnnotationType().asElement().equals(annoType); | ||
} | ||
|
||
public static final boolean isA(AnnotationMirror annoMirror, Class<?> annoClass) { | ||
if (annoMirror == null) return false; | ||
|
||
return TypeUtils.isClass(annoMirror.getAnnotationType(), annoClass); | ||
} | ||
|
||
public static final boolean is(AnnotationValue annoValue, Object obj) { | ||
// TODO: Add support for Class objects? | ||
return (annoValue == null) ? false : annoValue.getValue().equals(obj); | ||
} | ||
|
||
public static final TypeMirror asType(AnnotationValue annoValue) { | ||
return (annoValue == null) ? null : (TypeMirror) annoValue.getValue(); | ||
} | ||
|
||
public static final List<? extends AnnotationValue> asList(AnnotationValue annoValue) { | ||
if (annoValue == null) return null; | ||
|
||
return annoValue.accept( | ||
new AnnotationVisitor<List<? extends AnnotationValue>, Void>() { | ||
@Override | ||
public List<? extends AnnotationValue> visitArray( | ||
List<? extends AnnotationValue> vals, Void p) { | ||
return vals; | ||
} | ||
}, | ||
null); | ||
} | ||
|
||
public static final AnnotationValue getValue(AnnotationMirror annoMirror) { | ||
return getValue(annoMirror, "value"); | ||
} | ||
|
||
public static final AnnotationValue getValue(AnnotationMirror annoMirror, String simpleName) { | ||
if (annoMirror == null) return null; | ||
|
||
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> elementEntry : | ||
annoMirror.getElementValues().entrySet()) { | ||
ExecutableElement element = elementEntry.getKey(); | ||
if (!element.getSimpleName().contentEquals(simpleName)) continue; | ||
|
||
return elementEntry.getValue(); | ||
} | ||
return null; | ||
} | ||
|
||
abstract static class AnnotationVisitor<R, P> extends AbstractAnnotationValueVisitor6<R, P> { | ||
@Override | ||
public R visitArray(List<? extends AnnotationValue> vals, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitBoolean(boolean b, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitByte(byte b, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitChar(char c, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitDouble(double d, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitFloat(float f, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitInt(int i, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitLong(long i, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitShort(short s, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitString(String s, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitType(TypeMirror t, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitEnumConstant(VariableElement c, P p) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public R visitAnnotation(AnnotationMirror a, P p) { | ||
return null; | ||
} | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
...trumentation-annotation-processor/src/main/java/datadog/apt/ByteBuddyAdviceProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package datadog.apt; | ||
|
||
import static datadog.apt.AnnoUtils.*; | ||
import static datadog.apt.LogUtils.*; | ||
import static datadog.apt.TypeUtils.*; | ||
|
||
import java.util.Set; | ||
import javax.annotation.processing.AbstractProcessor; | ||
import javax.annotation.processing.RoundEnvironment; | ||
import javax.annotation.processing.SupportedAnnotationTypes; | ||
import javax.lang.model.SourceVersion; | ||
import javax.lang.model.element.AnnotationMirror; | ||
import javax.lang.model.element.Element; | ||
import javax.lang.model.element.TypeElement; | ||
import javax.lang.model.type.TypeMirror; | ||
|
||
/** | ||
* Annotation processor that checks ByteBuddy OnMethodEnter & OnMethodExit advice for suppress | ||
* attribute. | ||
* | ||
* <p>Warnings & errors generated by the this advice can be suppressed using | ||
* `@SuppressWarnings("bytebuddy-exception-suppression")` | ||
*/ | ||
@SupportedAnnotationTypes({"net.bytebuddy.asm.Advice.*"}) | ||
public class ByteBuddyAdviceProcessor extends AbstractProcessor { | ||
|
||
@Override | ||
public SourceVersion getSupportedSourceVersion() { | ||
return SourceVersion.latestSupported(); | ||
} | ||
|
||
@Override | ||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | ||
try { | ||
processImpl(annotations, roundEnv); | ||
} catch (RuntimeException e) { | ||
e.printStackTrace(System.err); | ||
} | ||
return false; | ||
} | ||
|
||
private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | ||
if (annotations.isEmpty()) return; | ||
|
||
TypeElement beforeAnno = findType(annotations, "net.bytebuddy.asm.Advice.OnMethodEnter"); | ||
TypeElement afterAnno = findType(annotations, "net.bytebuddy.asm.Advice.OnMethodExit"); | ||
|
||
if (beforeAnno != null) processAnnotation(beforeAnno, roundEnv); | ||
if (afterAnno != null) processAnnotation(afterAnno, roundEnv); | ||
} | ||
|
||
private void processAnnotation(TypeElement adviceAnno, RoundEnvironment roundEnv) { | ||
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(adviceAnno); | ||
log(processingEnv, "Processing annotation %s...", adviceAnno.getSimpleName()); | ||
if (annotatedElements.isEmpty()) return; | ||
|
||
for (Element annotatedElement : annotatedElements) { | ||
log( | ||
processingEnv, | ||
"\tProcessing annotated element %s::%s...", | ||
annotatedElement.getEnclosingElement().getSimpleName(), | ||
annotatedElement.getSimpleName()); | ||
|
||
AnnotationMirror adviceAnnoMirror = findAnnotation(annotatedElement, adviceAnno); | ||
TypeMirror suppressType = asType(getValue(adviceAnnoMirror, "suppress")); | ||
if (suppressType == null | ||
&& !isSuppressed(annotatedElement, "bytebuddy-exception-suppression")) { | ||
warning( | ||
processingEnv, | ||
annotatedElement, | ||
"Missing `suppress` attribute - use @SuppressWarnings(\"bytebuddy-exception-suppression\") to ignore"); | ||
} else if (!isClass(suppressType, Throwable.class) | ||
&& !isSuppressed(annotatedElement, "bytebuddy-exception-suppression")) { | ||
warning( | ||
processingEnv, | ||
annotatedElement, | ||
"`suppress` attribute != Throwable.class - use @SuppressWarnings(\"bytebuddy-exception-suppression\") to ignore"); | ||
} | ||
} | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
...va-agent/instrumentation-annotation-processor/src/main/java/datadog/apt/ElementUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package datadog.apt; | ||
|
||
import javax.lang.model.element.Element; | ||
import javax.lang.model.element.PackageElement; | ||
import javax.lang.model.element.TypeElement; | ||
|
||
/** | ||
* Utility class for working with Elements | ||
* | ||
* <p>By convention, nulls pass through nicely to allow easy composition | ||
*/ | ||
public final class ElementUtils { | ||
private ElementUtils() {} | ||
|
||
public static final boolean isPackage(Element element, Package pkg) { | ||
if (element == null) return false; | ||
|
||
return isPackage(element, pkg.getName()); | ||
} | ||
|
||
public static final boolean isPackage(Element element, String packageName) { | ||
if (!(element instanceof PackageElement)) return false; | ||
|
||
PackageElement packageElement = (PackageElement) element; | ||
|
||
return packageElement.getQualifiedName().contentEquals(packageName); | ||
} | ||
|
||
public static final boolean isClass(Element element, Class<?> clazz) { | ||
if (!(element instanceof TypeElement)) return false; | ||
TypeElement typeElement = (TypeElement) element; | ||
|
||
return TypeUtils.isClass(typeElement.asType(), clazz); | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
dd-java-agent/instrumentation-annotation-processor/src/main/java/datadog/apt/LogUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package datadog.apt; | ||
|
||
import javax.annotation.processing.ProcessingEnvironment; | ||
import javax.lang.model.element.Element; | ||
import javax.tools.Diagnostic.Kind; | ||
|
||
/** Utility class for logging from annotation processor to the ProcessingEnvironment */ | ||
public final class LogUtils { | ||
private LogUtils() {} | ||
|
||
private static final boolean NOTE = false; | ||
|
||
public static final void log( | ||
ProcessingEnvironment processingEnv, String formatStr, Object... args) { | ||
String msg = String.format(formatStr, args); | ||
|
||
processingEnv.getMessager().printMessage(Kind.NOTE, msg); | ||
} | ||
|
||
public static final void warning( | ||
ProcessingEnvironment processingEnv, Element element, String formatStr, Object... args) { | ||
message(processingEnv, element, Kind.WARNING, formatStr, args); | ||
} | ||
|
||
public static final void error( | ||
ProcessingEnvironment processingEnv, Element element, String formatStr, Object... args) { | ||
message(processingEnv, element, Kind.ERROR, formatStr, args); | ||
} | ||
|
||
public static final void message( | ||
ProcessingEnvironment processingEnv, | ||
Element element, | ||
Kind kind, | ||
String formatStr, | ||
Object... args) { | ||
String msg = String.format(formatStr, args); | ||
|
||
if (kind != Kind.NOTE || NOTE) { | ||
processingEnv.getMessager().printMessage(kind, msg, element); | ||
} | ||
} | ||
} |
Oops, something went wrong.