Skip to content

Commit

Permalink
Merge pull request #7873 from DataDog/dougqh/advice-annotation-checking
Browse files Browse the repository at this point in the history
ByteBuddy Exception Suppression Checking
  • Loading branch information
dougqh authored Nov 11, 2024
2 parents 44ac386 + a08d00e commit f19a626
Show file tree
Hide file tree
Showing 9 changed files with 494 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
apply from: "$rootDir/gradle/java.gradle"
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;
}
}
}
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");
}
}
}
}
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);
}
}
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);
}
}
}
Loading

0 comments on commit f19a626

Please sign in to comment.