package org.checkerframework.common.util.report;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeValidator;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.common.util.report.qual.ReportCall;
import org.checkerframework.common.util.report.qual.ReportCreation;
import org.checkerframework.common.util.report.qual.ReportInherit;
import org.checkerframework.common.util.report.qual.ReportOverride;
import org.checkerframework.common.util.report.qual.ReportReadWrite;
import org.checkerframework.common.util.report.qual.ReportUse;
import org.checkerframework.common.util.report.qual.ReportWrite;
import org.checkerframework.framework.source.Result;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
public class ReportVisitor extends BaseTypeVisitor<BaseAnnotatedTypeFactory> {
/** The tree kinds that should be reported. */
private final String[] treeKinds;
/** The modifiers that should be reported. */
private final String[] modifiers;
public ReportVisitor(BaseTypeChecker checker) {
super(checker);
if (checker.hasOption("reportTreeKinds")) {
String trees = checker.getOption("reportTreeKinds");
treeKinds = trees.split(",");
} else {
treeKinds = null;
}
if (checker.hasOption("reportModifiers")) {
String mods = checker.getOption("reportModifiers");
modifiers = mods.split(",");
} else {
modifiers = null;
}
}
@Override
protected BaseAnnotatedTypeFactory createTypeFactory() {
return new ReportAnnotatedTypeFactory(checker);
}
private static class ReportAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
public ReportAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
postInit();
}
@Override
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
return getBundledTypeQualifiersWithoutPolyAll();
}
}
@SuppressWarnings("CompilerMessages") // These warnings are not translated.
@Override
public Void scan(Tree tree, Void p) {
if (tree != null && treeKinds != null) {
for (String tk : treeKinds) {
if (tree.getKind().toString().equals(tk)) {
checker.report(Result.failure("Tree.Kind." + tk), tree);
}
}
}
return super.scan(tree, p);
}
/**
* Check for uses of the {@link ReportUse} annotation. This method has to be called for every
* explicit or implicit use of a type, most cases are simply covered by the type validator.
*
* @param node the tree for error reporting only
* @param member the element from which to start looking
*/
private void checkReportUse(Tree node, Element member) {
Element loop = member;
while (loop != null) {
boolean report = this.atypeFactory.getDeclAnnotation(loop, ReportUse.class) != null;
if (report) {
checker.report(
Result.failure(
"usage",
node,
ElementUtils.getVerboseName(loop),
loop.getKind(),
ElementUtils.getVerboseName(member),
member.getKind()),
node);
break;
} else {
if (loop.getKind() == ElementKind.PACKAGE) {
loop = ElementUtils.parentPackage(elements, (PackageElement) loop);
continue;
}
}
// Package will always be the last iteration.
loop = loop.getEnclosingElement();
}
}
/* Would we want this? Seems redundant, as all uses of the imported
* package should already be reported.
* Also, how do we get an element for the import?
public Void visitImport(ImportTree node, Void p) {
checkReportUse(node, elem);
}
*/
@Override
public void processClassTree(ClassTree node) {
TypeElement member = TreeUtils.elementFromDeclaration(node);
boolean report = false;
// No need to check on the declaring class itself
// this.atypeFactory.getDeclAnnotation(member, ReportInherit.class) != null;
// Check whether any superclass/interface had the ReportInherit annotation.
List<TypeElement> suptypes = ElementUtils.getSuperTypes(elements, member);
for (TypeElement sup : suptypes) {
report = this.atypeFactory.getDeclAnnotation(sup, ReportInherit.class) != null;
if (report) {
checker.report(
Result.failure("inherit", node, ElementUtils.getVerboseName(sup)), node);
}
}
super.processClassTree(node);
}
@Override
public Void visitMethod(MethodTree node, Void p) {
ExecutableElement method = TreeUtils.elementFromDeclaration(node);
boolean report = false;
// Check all overridden methods.
Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
AnnotatedTypes.overriddenMethods(elements, atypeFactory, method);
for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair :
overriddenMethods.entrySet()) {
// AnnotatedDeclaredType overriddenType = pair.getKey();
ExecutableElement exe = pair.getValue();
report = this.atypeFactory.getDeclAnnotation(exe, ReportOverride.class) != null;
if (report) {
// Set method to report the right method, if found.
method = exe;
break;
}
}
if (report) {
checker.report(
Result.failure("override", node, ElementUtils.getVerboseName(method)), node);
}
return super.visitMethod(node, p);
}
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
ExecutableElement method = TreeUtils.elementFromUse(node);
checkReportUse(node, method);
boolean report = this.atypeFactory.getDeclAnnotation(method, ReportCall.class) != null;
if (!report) {
// Find all methods that are overridden by the called method
Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
AnnotatedTypes.overriddenMethods(elements, atypeFactory, method);
for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair :
overriddenMethods.entrySet()) {
// AnnotatedDeclaredType overriddenType = pair.getKey();
ExecutableElement exe = pair.getValue();
report = this.atypeFactory.getDeclAnnotation(exe, ReportCall.class) != null;
if (report) {
// Always report the element that has the annotation.
// Alternative would be to always report the initial element.
method = exe;
break;
}
}
}
if (report) {
checker.report(
Result.failure("methodcall", node, ElementUtils.getVerboseName(method)), node);
}
return super.visitMethodInvocation(node, p);
}
@Override
public Void visitMemberSelect(MemberSelectTree node, Void p) {
Element member = TreeUtils.elementFromUse(node);
checkReportUse(node, member);
boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null;
if (report) {
checker.report(
Result.failure("fieldreadwrite", node, ElementUtils.getVerboseName(member)),
node);
}
return super.visitMemberSelect(node, p);
}
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
Element member = TreeUtils.elementFromUse(node);
boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null;
if (report) {
checker.report(
Result.failure("fieldreadwrite", node, ElementUtils.getVerboseName(member)),
node);
}
return super.visitIdentifier(node, p);
}
@Override
public Void visitAssignment(AssignmentTree node, Void p) {
Element member = TreeUtils.elementFromUse(node.getVariable());
boolean report = this.atypeFactory.getDeclAnnotation(member, ReportWrite.class) != null;
if (report) {
checker.report(
Result.failure("fieldwrite", node, ElementUtils.getVerboseName(member)), node);
}
return super.visitAssignment(node, p);
}
@Override
public Void visitArrayAccess(ArrayAccessTree node, Void p) {
// TODO: should we introduce an annotation for this?
return super.visitArrayAccess(node, p);
}
@Override
public Void visitNewClass(NewClassTree node, Void p) {
Element member = TreeUtils.elementFromUse(node);
boolean report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null;
if (!report) {
// If the constructor is not annotated, check whether the class is.
member = member.getEnclosingElement();
report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null;
}
if (!report) {
// Check whether any superclass/interface had the ReportCreation annotation.
List<TypeElement> suptypes = ElementUtils.getSuperTypes(elements, (TypeElement) member);
for (TypeElement sup : suptypes) {
report = this.atypeFactory.getDeclAnnotation(sup, ReportCreation.class) != null;
if (report) {
// Set member to report the right member if found
member = sup;
break;
}
}
}
if (report) {
checker.report(
Result.failure("creation", node, ElementUtils.getVerboseName(member)), node);
}
return super.visitNewClass(node, p);
}
@Override
public Void visitNewArray(NewArrayTree node, Void p) {
// TODO Should we report this if the array type is @ReportCreation?
return super.visitNewArray(node, p);
}
@Override
public Void visitTypeCast(TypeCastTree node, Void p) {
// TODO Is it worth adding a separate annotation for this?
return super.visitTypeCast(node, p);
}
@Override
public Void visitInstanceOf(InstanceOfTree node, Void p) {
// TODO Is it worth adding a separate annotation for this?
return super.visitInstanceOf(node, p);
}
@SuppressWarnings("CompilerMessages") // These warnings are not translated.
@Override
public Void visitModifiers(ModifiersTree node, Void p) {
if (node != null && modifiers != null) {
for (Modifier hasmod : node.getFlags()) {
for (String searchmod : modifiers) {
if (hasmod.toString().equals(searchmod)) {
checker.report(Result.failure("Modifier." + hasmod), node);
}
}
}
}
return super.visitModifiers(node, p);
}
@Override
protected BaseTypeValidator createTypeValidator() {
return new ReportTypeValidator(checker, this, atypeFactory);
}
protected class ReportTypeValidator extends BaseTypeValidator {
public ReportTypeValidator(
BaseTypeChecker checker,
BaseTypeVisitor<?> visitor,
AnnotatedTypeFactory atypeFactory) {
super(checker, visitor, atypeFactory);
}
@Override
public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) {
Element member = type.getUnderlyingType().asElement();
checkReportUse(tree, member);
return super.visitDeclared(type, tree);
}
}
}