Skip to content

Commit f19a626

Browse files
authored
Merge pull request #7873 from DataDog/dougqh/advice-annotation-checking
ByteBuddy Exception Suppression Checking
2 parents 44ac386 + a08d00e commit f19a626

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)