package org.checkerframework.common.util.debug; import com.sun.source.util.TreePath; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import java.io.PrintStream; import java.lang.reflect.Constructor; import java.util.List; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.AbstractElementVisitor6; import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.source.SourceVisitor; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.javacutil.AbstractTypeProcessor; import org.checkerframework.javacutil.AnnotationProvider; /** * Outputs the method signatures of a class with fully annotated types. * * <p>The class determines the effective annotations for a checker in source or the classfile. * Finding the effective annotations is useful for the following purposes: * * <ol> * <li value="1">Debugging annotations in classfile * <li value="2">Debugging the default annotations that are implicitly added by the checker * </ol> * * <p>The class can be used in two possible ways, depending on the type file: * * <ol> * <li id="a">From source: the class is to be used as an annotation processor when reading * annotations from source. It can be invoked via the command: * <p>{@code javac -processor SignaturePrinter <java files> ...} * <li id="b">From classfile: the class is to be used as an independent app when reading * annotations from classfile. It can be invoked via the command: * <p>{@code java SignaturePrinter <class name>} * </ol> * * By default, only the annotations explicitly written by the user are emitted. To view the default * and effective annotations in a class that are associated with a checker, the fully qualified name * of the checker needs to be passed as {@code -Achecker=} argument, e.g. * * <pre>{@code * javac -processor SignaturePrinter * -Achecker=org.checkerframework.checker.nullness.NullnessChecker JavaFile.java * }</pre> */ @SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedAnnotationTypes("*") @SupportedOptions("checker") public class SignaturePrinter extends AbstractTypeProcessor { private SourceChecker checker; ///////// Initialization ///////////// private void init(ProcessingEnvironment env, String checkerName) { if (checkerName != null) { try { Class<?> checkerClass = Class.forName(checkerName); Constructor<?> cons = checkerClass.getConstructor(); checker = (SourceChecker) cons.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } else { checker = new SourceChecker() { @Override protected SourceVisitor<?, ?> createSourceVisitor() { return null; } @Override public AnnotationProvider getAnnotationProvider() { throw new UnsupportedOperationException( "getAnnotationProvider is not implemented for this class."); } }; } checker.init(env); } @Override public void typeProcessingStart() { super.typeProcessingStart(); String checkerName = processingEnv.getOptions().get("checker"); init(processingEnv, checkerName); } @Override public void typeProcess(TypeElement element, TreePath p) { // TODO: fix this mess // checker.currentPath = p; // CompilationUnitTree root = p != null ? p.getCompilationUnit() : null; // ElementPrinter printer = new ElementPrinter(checker.createTypeFactory(), System.out); // printer.visit(element); } ////////// Printer ////////// static class ElementPrinter extends AbstractElementVisitor6<Void, Void> { private static final String INDENTION = " "; private final PrintStream out; private String indent = ""; private final AnnotatedTypeFactory factory; public ElementPrinter(AnnotatedTypeFactory factory, PrintStream out) { this.factory = factory; this.out = out; } public void printTypeParams(List<? extends AnnotatedTypeVariable> params) { if (params.isEmpty()) { return; } out.print("<"); boolean isntFirst = false; for (AnnotatedTypeMirror param : params) { if (isntFirst) { out.print(", "); } isntFirst = true; out.print(param); } out.print("> "); } public void printParameters(AnnotatedExecutableType type) { ExecutableElement elem = type.getElement(); out.print("("); for (int i = 0; i < type.getParameterTypes().size(); ++i) { if (i != 0) { out.print(", "); } printVariable( type.getParameterTypes().get(i), elem.getParameters().get(i).getSimpleName()); } out.print(")"); } public void printThrows(AnnotatedExecutableType type) { if (type.getThrownTypes().isEmpty()) { return; } out.print(" throws "); boolean isntFirst = false; for (AnnotatedTypeMirror thrown : type.getThrownTypes()) { if (isntFirst) { out.print(", "); } isntFirst = true; out.print(thrown); } } public void printVariable(AnnotatedTypeMirror type, Name name, boolean isVarArg) { out.print(type); if (isVarArg) { out.println("..."); } out.print(' '); out.print(name); } public void printVariable(AnnotatedTypeMirror type, Name name) { printVariable(type, name, false); } public void printType(AnnotatedTypeMirror type) { out.print(type); out.print(' '); } public void printName(CharSequence name) { out.print(name); } @Override public Void visitExecutable(ExecutableElement e, Void p) { out.print(indent); AnnotatedExecutableType type = factory.getAnnotatedType(e); printTypeParams(type.getTypeVariables()); if (e.getKind() != ElementKind.CONSTRUCTOR) { printType(type.getReturnType()); } printName(e.getSimpleName()); printParameters(type); printThrows(type); out.println(';'); return null; } @Override public Void visitPackage(PackageElement e, Void p) { throw new IllegalArgumentException("Cannot process packages"); } private String typeIdentifier(TypeElement e) { switch (e.getKind()) { case INTERFACE: return "interface"; case CLASS: return "class"; case ANNOTATION_TYPE: return "@interface"; case ENUM: return "enum"; default: throw new IllegalArgumentException("Not a type element: " + e.getKind()); } } @Override public Void visitType(TypeElement e, Void p) { String prevIndent = indent; out.print(indent); out.print(typeIdentifier(e)); out.print(' '); out.print(e.getSimpleName()); out.print(' '); AnnotatedDeclaredType dt = factory.getAnnotatedType(e); printSupers(dt); out.println("{"); indent += INDENTION; for (Element enclosed : e.getEnclosedElements()) { this.visit(enclosed); } indent = prevIndent; out.print(indent); out.println("}"); return null; } private void printSupers(AnnotatedDeclaredType dt) { if (dt.directSuperTypes().isEmpty()) { return; } out.print("extends "); boolean isntFirst = false; for (AnnotatedDeclaredType st : dt.directSuperTypes()) { if (isntFirst) { out.print(", "); } isntFirst = true; out.print(st); } out.print(' '); } @Override public Void visitTypeParameter(TypeParameterElement e, Void p) { throw new IllegalStateException("Shouldn't visit any type parameters"); } @Override public Void visitVariable(VariableElement e, Void p) { if (!e.getKind().isField()) { throw new IllegalStateException("can only process fields, received " + e.getKind()); } out.print(indent); AnnotatedTypeMirror type = factory.getAnnotatedType(e); this.printVariable(type, e.getSimpleName()); out.println(';'); return null; } } public static void printUsage() { System.out.println(" Usage: java SignaturePrinter [-Achecker=<checkerName>] classname"); } private static final String CHECKER_ARG = "-Achecker="; public static void main(String[] args) { if (!(args.length == 1 && !args[0].startsWith(CHECKER_ARG)) && !(args.length == 2 && args[0].startsWith(CHECKER_ARG))) { printUsage(); return; } // process arguments String checkerName = ""; if (args[0].startsWith(CHECKER_ARG)) { checkerName = args[0].substring(CHECKER_ARG.length()); } // Setup compiler environment Context context = new Context(); JavacProcessingEnvironment env = JavacProcessingEnvironment.instance(context); SignaturePrinter printer = new SignaturePrinter(); printer.init(env, checkerName); String className = args[args.length - 1]; TypeElement elem = env.getElementUtils().getTypeElement(className); if (elem == null) { System.err.println("Couldn't find class: " + className); return; } printer.typeProcess(elem, null); } }