Skip to content

Commit f5c17ac

Browse files
committed
ByteBuddy Exception Suppression Checking
Introduces an annotation processor that checks ByteBuddy Advice.OnMethodEnter and Advice.OnMethodExit annotations to make sure they have the suppress property set This annotation processor is now run automatically for all Java integrations. Presently, the processor only emits warnings. In a later pull request, this will likely be changed to error. For situations where suppression isn't needed. The check can be suppressed using a SuppressWarnings annotation.
1 parent 3f89f03 commit f5c17ac

File tree

9 files changed

+494
-0
lines changed

9 files changed

+494
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
apply from: "$rootDir/gradle/java.gradle"
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package datadog.apt;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import javax.lang.model.element.AnnotationMirror;
6+
import javax.lang.model.element.AnnotationValue;
7+
import javax.lang.model.element.Element;
8+
import javax.lang.model.element.ExecutableElement;
9+
import javax.lang.model.element.TypeElement;
10+
import javax.lang.model.element.VariableElement;
11+
import javax.lang.model.type.TypeMirror;
12+
import javax.lang.model.util.AbstractAnnotationValueVisitor6;
13+
14+
/**
15+
* Utility class that works AnnotationMirrors & AnnotationValues
16+
*
17+
* <p>By convention, nulls pass through nicely to allow easy composition
18+
*/
19+
public final class AnnoUtils {
20+
private AnnoUtils() {}
21+
22+
/** Finds the AnnotationMirror on Element of type TypeElement */
23+
public static final AnnotationMirror findAnnotation(Element element, TypeElement annoType) {
24+
if (element == null || annoType == null) return null;
25+
26+
for (AnnotationMirror annoMirror : element.getAnnotationMirrors()) {
27+
if (isA(annoMirror, annoType)) return annoMirror;
28+
}
29+
return null;
30+
}
31+
32+
public static final AnnotationMirror findAnnotation(Element element, Class<?> annoClass) {
33+
if (element == null || annoClass == null) return null;
34+
35+
for (AnnotationMirror annoMirror : element.getAnnotationMirrors()) {
36+
if (isA(annoMirror, annoClass)) return annoMirror;
37+
}
38+
return null;
39+
}
40+
41+
public static final boolean isSuppressed(Element element, String checkName) {
42+
if (element == null) return false;
43+
if (isSuppressedHelper(element, checkName)) return true;
44+
45+
for (Element enclosingElement = element.getEnclosingElement();
46+
enclosingElement != null;
47+
enclosingElement = enclosingElement.getEnclosingElement()) {
48+
if (isSuppressedHelper(enclosingElement, checkName)) return true;
49+
}
50+
return false;
51+
}
52+
53+
private static final boolean isSuppressedHelper(Element element, String checkName) {
54+
AnnotationMirror mirror = findAnnotation(element, SuppressWarnings.class);
55+
AnnotationValue value = getValue(mirror);
56+
List<? extends AnnotationValue> suppressionList = asList(value);
57+
58+
return contains(suppressionList, checkName);
59+
}
60+
61+
public static final boolean contains(List<? extends AnnotationValue> annoList, Object value) {
62+
if (annoList == null) return false;
63+
64+
for (AnnotationValue annoValue : annoList) {
65+
if (is(annoValue, value)) return true;
66+
}
67+
return false;
68+
}
69+
70+
public static final boolean isA(AnnotationMirror annoMirror, TypeElement annoType) {
71+
if (annoMirror == null) return false;
72+
73+
return annoMirror.getAnnotationType().asElement().equals(annoType);
74+
}
75+
76+
public static final boolean isA(AnnotationMirror annoMirror, Class<?> annoClass) {
77+
if (annoMirror == null) return false;
78+
79+
return TypeUtils.isClass(annoMirror.getAnnotationType(), annoClass);
80+
}
81+
82+
public static final boolean is(AnnotationValue annoValue, Object obj) {
83+
// TODO: Add support for Class objects?
84+
return (annoValue == null) ? false : annoValue.getValue().equals(obj);
85+
}
86+
87+
public static final TypeMirror asType(AnnotationValue annoValue) {
88+
return (annoValue == null) ? null : (TypeMirror) annoValue.getValue();
89+
}
90+
91+
public static final List<? extends AnnotationValue> asList(AnnotationValue annoValue) {
92+
if (annoValue == null) return null;
93+
94+
return annoValue.accept(
95+
new AnnotationVisitor<List<? extends AnnotationValue>, Void>() {
96+
@Override
97+
public List<? extends AnnotationValue> visitArray(
98+
List<? extends AnnotationValue> vals, Void p) {
99+
return vals;
100+
}
101+
},
102+
null);
103+
}
104+
105+
public static final AnnotationValue getValue(AnnotationMirror annoMirror) {
106+
return getValue(annoMirror, "value");
107+
}
108+
109+
public static final AnnotationValue getValue(AnnotationMirror annoMirror, String simpleName) {
110+
if (annoMirror == null) return null;
111+
112+
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> elementEntry :
113+
annoMirror.getElementValues().entrySet()) {
114+
ExecutableElement element = elementEntry.getKey();
115+
if (!element.getSimpleName().contentEquals(simpleName)) continue;
116+
117+
return elementEntry.getValue();
118+
}
119+
return null;
120+
}
121+
122+
abstract static class AnnotationVisitor<R, P> extends AbstractAnnotationValueVisitor6<R, P> {
123+
@Override
124+
public R visitArray(List<? extends AnnotationValue> vals, P p) {
125+
return null;
126+
}
127+
128+
@Override
129+
public R visitBoolean(boolean b, P p) {
130+
return null;
131+
}
132+
133+
@Override
134+
public R visitByte(byte b, P p) {
135+
return null;
136+
}
137+
138+
@Override
139+
public R visitChar(char c, P p) {
140+
return null;
141+
}
142+
143+
@Override
144+
public R visitDouble(double d, P p) {
145+
return null;
146+
}
147+
148+
@Override
149+
public R visitFloat(float f, P p) {
150+
return null;
151+
}
152+
153+
@Override
154+
public R visitInt(int i, P p) {
155+
return null;
156+
}
157+
158+
@Override
159+
public R visitLong(long i, P p) {
160+
return null;
161+
}
162+
163+
@Override
164+
public R visitShort(short s, P p) {
165+
return null;
166+
}
167+
168+
@Override
169+
public R visitString(String s, P p) {
170+
return null;
171+
}
172+
173+
@Override
174+
public R visitType(TypeMirror t, P p) {
175+
return null;
176+
}
177+
178+
@Override
179+
public R visitEnumConstant(VariableElement c, P p) {
180+
return null;
181+
}
182+
183+
@Override
184+
public R visitAnnotation(AnnotationMirror a, P p) {
185+
return null;
186+
}
187+
}
188+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package datadog.apt;
2+
3+
import static datadog.apt.AnnoUtils.*;
4+
import static datadog.apt.LogUtils.*;
5+
import static datadog.apt.TypeUtils.*;
6+
7+
import java.util.Set;
8+
import javax.annotation.processing.AbstractProcessor;
9+
import javax.annotation.processing.RoundEnvironment;
10+
import javax.annotation.processing.SupportedAnnotationTypes;
11+
import javax.lang.model.SourceVersion;
12+
import javax.lang.model.element.AnnotationMirror;
13+
import javax.lang.model.element.Element;
14+
import javax.lang.model.element.TypeElement;
15+
import javax.lang.model.type.TypeMirror;
16+
17+
/**
18+
* Annotation processor that checks ByteBuddy OnMethodEnter & OnMethodExit advice for suppress
19+
* attribute.
20+
*
21+
* <p>Warnings & errors generated by the this advice can be suppressed using
22+
* `@SuppressWarnings("bytebuddy-exception-suppression")`
23+
*/
24+
@SupportedAnnotationTypes({"net.bytebuddy.asm.Advice.*"})
25+
public class ByteBuddyAdviceProcessor extends AbstractProcessor {
26+
27+
@Override
28+
public SourceVersion getSupportedSourceVersion() {
29+
return SourceVersion.latestSupported();
30+
}
31+
32+
@Override
33+
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
34+
try {
35+
processImpl(annotations, roundEnv);
36+
} catch (RuntimeException e) {
37+
e.printStackTrace(System.err);
38+
}
39+
return false;
40+
}
41+
42+
private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
43+
if (annotations.isEmpty()) return;
44+
45+
TypeElement beforeAnno = findType(annotations, "net.bytebuddy.asm.Advice.OnMethodEnter");
46+
TypeElement afterAnno = findType(annotations, "net.bytebuddy.asm.Advice.OnMethodExit");
47+
48+
if (beforeAnno != null) processAnnotation(beforeAnno, roundEnv);
49+
if (afterAnno != null) processAnnotation(afterAnno, roundEnv);
50+
}
51+
52+
private void processAnnotation(TypeElement adviceAnno, RoundEnvironment roundEnv) {
53+
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(adviceAnno);
54+
log(processingEnv, "Processing annotation %s...", adviceAnno.getSimpleName());
55+
if (annotatedElements.isEmpty()) return;
56+
57+
for (Element annotatedElement : annotatedElements) {
58+
log(
59+
processingEnv,
60+
"\tProcessing annotated element %s::%s...",
61+
annotatedElement.getEnclosingElement().getSimpleName(),
62+
annotatedElement.getSimpleName());
63+
64+
AnnotationMirror adviceAnnoMirror = findAnnotation(annotatedElement, adviceAnno);
65+
TypeMirror suppressType = asType(getValue(adviceAnnoMirror, "suppress"));
66+
if (suppressType == null
67+
&& !isSuppressed(annotatedElement, "bytebuddy-exception-suppression")) {
68+
warning(
69+
processingEnv,
70+
annotatedElement,
71+
"Missing `suppress` attribute - use @SuppressWarnings(\"bytebuddy-exception-suppression\") to ignore");
72+
} else if (!isClass(suppressType, Throwable.class)
73+
&& !isSuppressed(annotatedElement, "bytebuddy-exception-suppression")) {
74+
warning(
75+
processingEnv,
76+
annotatedElement,
77+
"`suppress` attribute != Throwable.class - use @SuppressWarnings(\"bytebuddy-exception-suppression\") to ignore");
78+
}
79+
}
80+
}
81+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package datadog.apt;
2+
3+
import javax.lang.model.element.Element;
4+
import javax.lang.model.element.PackageElement;
5+
import javax.lang.model.element.TypeElement;
6+
7+
/**
8+
* Utility class for working with Elements
9+
*
10+
* <p>By convention, nulls pass through nicely to allow easy composition
11+
*/
12+
public final class ElementUtils {
13+
private ElementUtils() {}
14+
15+
public static final boolean isPackage(Element element, Package pkg) {
16+
if (element == null) return false;
17+
18+
return isPackage(element, pkg.getName());
19+
}
20+
21+
public static final boolean isPackage(Element element, String packageName) {
22+
if (!(element instanceof PackageElement)) return false;
23+
24+
PackageElement packageElement = (PackageElement) element;
25+
26+
return packageElement.getQualifiedName().contentEquals(packageName);
27+
}
28+
29+
public static final boolean isClass(Element element, Class<?> clazz) {
30+
if (!(element instanceof TypeElement)) return false;
31+
TypeElement typeElement = (TypeElement) element;
32+
33+
return TypeUtils.isClass(typeElement.asType(), clazz);
34+
}
35+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package datadog.apt;
2+
3+
import javax.annotation.processing.ProcessingEnvironment;
4+
import javax.lang.model.element.Element;
5+
import javax.tools.Diagnostic.Kind;
6+
7+
/** Utility class for logging from annotation processor to the ProcessingEnvironment */
8+
public final class LogUtils {
9+
private LogUtils() {}
10+
11+
private static final boolean NOTE = false;
12+
13+
public static final void log(
14+
ProcessingEnvironment processingEnv, String formatStr, Object... args) {
15+
String msg = String.format(formatStr, args);
16+
17+
processingEnv.getMessager().printMessage(Kind.NOTE, msg);
18+
}
19+
20+
public static final void warning(
21+
ProcessingEnvironment processingEnv, Element element, String formatStr, Object... args) {
22+
message(processingEnv, element, Kind.WARNING, formatStr, args);
23+
}
24+
25+
public static final void error(
26+
ProcessingEnvironment processingEnv, Element element, String formatStr, Object... args) {
27+
message(processingEnv, element, Kind.ERROR, formatStr, args);
28+
}
29+
30+
public static final void message(
31+
ProcessingEnvironment processingEnv,
32+
Element element,
33+
Kind kind,
34+
String formatStr,
35+
Object... args) {
36+
String msg = String.format(formatStr, args);
37+
38+
if (kind != Kind.NOTE || NOTE) {
39+
processingEnv.getMessager().printMessage(kind, msg, element);
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)