package threadchecker;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
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.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.NullType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.type.UnionType;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;


| A visitor class that visits the whole AST and does checks against the | thread annotations the user has provided. One scanner is used | for all compilation units in a compilation | class TCScanner extends TreePathScanner<Void, Void>{ private final Trees trees; private final Types types; private final Elements elements; private final HashMap<String, LocatedTag> packageAnns = new HashMap<>(); private final HashMap<String, LocatedTag> classAnns = new HashMap<>(); private final List<MethodRef> methodAnns = new ArrayList<>(); private final LinkedList<PathAnd<ClassTree>> typeScopeStack = new LinkedList<>(); private final LinkedList<PathAnd<MethodTree>> methodScopeStack = new LinkedList<>(); private final LinkedList<LocatedTag> lambdaScopeStack = new LinkedList<>(); private final Map<String, LocatedTag> fields = new HashMap<>(); private final List<String> objectMembers; private final List<String> ignorePackages; private final LinkedList<List<TypeMirror>> callingMethodStack = new LinkedList<>(); private final WrapDescent defaultReturn = new WrapDescent( () -> { callingMethodStack.addLast(null); }, () -> { callingMethodStack.removeLast(); } ); private CompilationUnitTree cu; private boolean inSynchronizedThis; public TCScanner(JavacTask task, List<String> ignorePackages) throws NoSuchMethodException { | | |try | |{} System.setErr(new PrintStream(new BufferedOutputStream(new FileOutputStream("J:\\tc.txt")))); } catch (FileNotFoundException e) {}e.printStackTrace(); } */ trees = Trees.instance(task); types = task.getTypes(); elements = task.getElements(); | |this.ignorePackages = new ArrayList<>(ignorePackages); | |objectMembers = elements.getAllMembers(elements.getTypeElement("java.lang.Object")) .stream().map(e -> e.getSimpleName().toString()).collect(Collectors.toList()); // Temporary: //this.ignorePackages.addAll(Arrays.asList("bluej.editor.stride")); //this.ignorePackages.addAll(Arrays.asList("bluej.stride")); // Now we have all the business logic for the Java standard libraries: //Setup default package annotations. For convenience, we mark that all subclasses // of classes in this package (except when overriden by classAnns) will have | |// all their methods tagged accordingly. So anything inheriting from a JComponent, | |// for example, will have all its methods tagged as Swing. (You can always | |// override this with a tag on the particular method) | |Arrays.asList( | |"javafx.application", "javafx.beans.binding", "javafx.beans.value", "javafx.collections", "javafx.css", "javafx.event", "javafx.scene", "javafx.scene.control", "javafx.scene.image", "javafx.scene.layout" ).forEach(pkg -> packageAnns.put(pkg, new LocatedTag(Tag.FX, false, true, "<JavaFX: " + pkg + ">"))); // Web must be done solely on actual FX thread: packageAnns.put("javafx.scene.web", new LocatedTag(Tag.FXPlatform, false, true, "<JavaFX javafx.scene.web>")); classAnns.put("javafx.event.EventHandler", new LocatedTag(Tag.FXPlatform, false, true, "<JavaFX EventHandler>")); Arrays.asList( "java.awt.event", "java.beans", "javax.accessibility", "javax.swing", "javax.swing.border", "javax.swing.event", "javax.swing.table", "javax.swing.text", "javax.swing.filechooser", "javax.swing.plaf.metal" ).forEach(pkg -> packageAnns.put(pkg, new LocatedTag(Tag.Swing, false, true, "<Swing: " + pkg + ">"))); //An override we need: methodAnns.add(new MethodRef("java.awt.Window", "dispose", new LocatedTag(Tag.Any, false, false, "<AWT>"))); methodAnns.add(new MethodRef("javafx.beans.property.ObjectPropertyBase", "get", new LocatedTag(Tag.Any, false, false, "<JavaFX Beans>"))); methodAnns.add(new MethodRef("javafx.beans.property.SimpleObjectProperty", "<init>", new LocatedTag(Tag.Any, false, false, "<JavaFX Beans>"))); methodAnns.add(new MethodRef("javafx.beans.property.SimpleBooleanProperty", "<init>", new LocatedTag(Tag.Any, false, false, "<JavaFX Beans>"))); methodAnns.add(new MethodRef("javafx.embed.swing.SwingNode", "<init>", new LocatedTag(Tag.Any, false, false, "<SwingNode>"))); methodAnns.add(new MethodRef("javafx.embed.swing.SwingNode", "setContent", new LocatedTag(Tag.Any, false, false, "<SwingNode>"))); methodAnns.add(new MethodRef("javafx.embed.swing.SwingNode", "getContent", new LocatedTag(Tag.Any, false, false, "<SwingNode>"))); methodAnns.add(new MethodRef("javafx.application.Application", "launch", new LocatedTag(Tag.Any, false, false, "<FX launch>"))); methodAnns.add(new MethodRef("javafx.application.Application", "start", new LocatedTag(Tag.FXPlatform, false, false, "<FX application>"))); methodAnns.add(new MethodRef("javafx.animation.AnimationTimer", "handle", new LocatedTag(Tag.FXPlatform, false, false, "<AnimationTimer>"))); // This one isn't actually true! But it's used during printing so let's live: methodAnns.add(new MethodRef("javax.swing.JComponent", "paint", new LocatedTag(Tag.Any, false, false, "<Swing paint hack>"))); // This is actually thread-safe: methodAnns.add(new MethodRef("javax.swing.JComponent", "getClientProperty", new LocatedTag(Tag.Any, false, false, "<Swing client properties>"))); methodAnns.add(new MethodRef("javax.swing.JComponent", "putClientProperty", new LocatedTag(Tag.Any, false, false, "<Swing client properties>"))); classAnns.put("java.awt.event.InputEvent", new LocatedTag(Tag.Any, false, true, "<AWT>")); classAnns.put("java.awt.event.ComponentEvent", new LocatedTag(Tag.Any, false, true, "<AWT>")); classAnns.put("java.awt.event.KeyEvent", new LocatedTag(Tag.Any, false, true, "<AWT>")); classAnns.put("java.awt.event.MouseEvent" methodAnns.add(new PathAnd<T>(cur, getCurrentPath())); } @Override public Void visitCompilationUnit(CompilationUnitTree cu, Void arg1) { | |this.cu = cu; | |// If this is a package-info.java file, it will have package annotations | |// which we check for validity: | |checkSingle(cu.getPackageAnnotations().stream().map(t -> getSourceTag(t, () -> cu.getPackageName().toString() + " package")), cu); // If it (or an ancestor package) is in our ignore list, don't process the type: if (ignorePackages.stream().anyMatch(pkg -> cu.getPackageName().toString().startsWith(pkg))) | |return null; | |else{ return super.visitCompilationUnit(cu, arg1); | |} | |} | |@Override | |public Void visitClass(ClassTree tree, Void arg1) | |{ | |addCur(typeScopeStack, tree); | |HashMap<String, LocatedTag> fieldCopy = new HashMap<>(fields); | |fields.clear(); | |Void r = super.visitClass(tree, arg1); | |typeScopeStack.removeLast(); | |fields.clear(); | |fields.putAll(fieldCopy); | |return r; | |} | |@Override | |public Void visitBlock(BlockTree node, Void p) | |{ | |// Look for initializer blocks, to alter methodScopeStack accordingly: | |if (getCurrentPath().getParentPath().getLeaf() instanceof ClassTree) | |{ | |methodScopeStack.add(new PathAnd<>(null, null)); | |Void r = super.visitBlock(node, p); | |methodScopeStack.removeLast(); | |return r; | |} | |else | |{ | |return super.visitBlock(node, p); | |} | |} | |// Visit the declaration of a method. We must check the tags against those of any | |// overriden methods from parent classes. | |@Override | |public Void visitMethod(MethodTree method, Void arg1) | |{ | |TypeMirror methodType = trees.getTypeMirror(getCurrentPath()); | |Collection<? extends TypeMirror> superTypes = allSuperTypes(trees.getTypeMirror(typeScopeStack.getLast().path), method); | |// Skip methods with @SuppressWarnings("threadchecker") if (!suppressesChecker(method)) { // Check if this method declares an annotation absent from // its parent, and warn that it can be circumvented: checkAgainstOverridden(method.getName(), getSourceTag(method, () -> cu.getPackageName().toString() + typeScopeStack.stream().limit(typeScopeStack.size()).map(TCScanner::typeToName).collect(Collectors.joining("."))), methodType, superTypes, false, method); addCur(methodScopeStack, method); Void r = super.visitMethod(method, arg1); this.methodScopeStack.removeLast(); return r; | |} | |else | |{ | |return null; | |} | |} | |/** | Examines a method, to see if the effective tags it has match against any overridden | method from a parent class. | | @param methodName The name of the method to check | @param methodTag The tag on the declaration of that method, in the class | in which it is being declared/invoked-on | @param methodType The type of the method. This includes the return type and parameter types. | @param superTypes The super types of the class in which the method is being | declared/invoked-on. This is all supertypes, whether by extends | or implements, all the way back up the type hierarchy to Object or interfaces. | @param invocation True if we are checking the method when it is being invoked, | False if we are checking the method when it is being declared | @param errorLocation A suitable location (within the compilation unit this.cu) | at which to issue any compiler errors | | @return Returns the effective tag of the method | private LocatedTag checkAgainstOverridden(Name methodName, LocatedTag methodTag, TypeMirror methodType, Collection<? extends TypeMirror> superTypes, boolean invocation, Tree errorLocation) { boolean subTagWasPackage = false; LocatedTag subTag = methodTag; if (subTag == null && !invocation) { subTag = getSourceTag(typeScopeStack.getLast().item, () -> cu.getPackageName().toString() + typeScopeStack.stream().limit(typeScopeStack.size() - 1).map(TCScanner::typeToName).collect(Collectors.joining("."))); if (subTag == null) { if (subTag == null) { PackageElement pkg = elements.getPackageOf(trees.getElement(typeScopeStack.getLast().path)); subTag = getRemoteTag(pkg, () -> pkg.getQualifiedName().toString(), errorLocation); if (subTag == null) { subTag = packageAnns.get(pkg.getQualifiedName().toString()); } subTagWasPackage = true; } } } Map<String, LocatedTag> appliedTags = new HashMap<>(); String subTagKey = "." + methodName; if (subTag != null) appliedTags.put(subTagKey, subTag); for (TypeMirror st : superTypes.stream().map(types::capture).collect(Collectors.toList())) { LocatedTag superClassTag = getRemoteTag(types.asElement(st), () -> st.toString(), errorLocation); if (superClassTag == null) { superClassTag = classAnns.get(types.erasure(st).toString()); if (superClassTag == null) { PackageElement pkg = elements.getPackageOf(types.asElement(st)); superClassTag = getRemoteTag(pkg, () -> pkg.getQualifiedName().toString(), errorLocation); if (superClassTag == null) { superClassTag = packageAnns.get(pkg.getQualifiedName().toString()); } } } if (superClassTag != null && (superClassTag.applyToAllSubclassMethods() /*&& types.asElement(st).getKind() != ElementKind.INTERFACE && (subTag == null || subTagWasPackage) && !(st.toString().equals("java.lang.Thread") && methodName.equals(elements.getName("<init>"))) ) { appliedTags.put(st.toString(), superClassTag); } boolean methodNameIsInit = methodName == null ? false : methodName.toString().equals("<init>"); for (Element superMember : types.asElement(st).getEnclosedElements()) { if (superMember instanceof ExecutableElement) { if (methodName != null && !methodNameIsInit && superMember.getSimpleName().equals(methodName) && types.isSubsignature((ExecutableType)methodType, (ExecutableType)types.asMemberOf((DeclaredType)st, superMember))) { LocatedTag superTag = getRemoteTag(superMember, () -> st.toString() + "." + superMember.getSimpleName().toString(), errorLocation); if (superTag == null) { superTag = methodAnns.stream() .filter(m -> m.matches(st, methodName, types.asMemberOf((DeclaredType)st, superMember))) .map(m -> m.tag) .findFirst() .orElse(null); if (superTag == null) { superTag = classAnns.get(types.erasure(st).toString()); if (superTag == null) { superTag = getRemoteTag(types.asElement(st), () -> st.toString(), errorLocation); if (superTag == null) { PackageElement pkg = elements.getPackageOf(types.asElement(st)); superTag = getRemoteTag(pkg, () -> pkg.getQualifiedName().toString(), errorLocation); if (superTag == null) { superTag = packageAnns.get(pkg.getQualifiedName().toString()); } } } } } if (superTag != null) { appliedTags.put(st.toString() + "." + superMember.getSimpleName().toString(), superTag); if (superTag.ignoreParent() && subTagWasPackage) { appliedTags.remove(subTagKey); subTag = null; } } if (superTag != null && subTag == null) { } else if (typeScopeStack.getLast().item.getSimpleName().length() == 0) { } else if ((!invocation || superTag != null) && subTag != null && !subTag.tag.canOverride(superTag == null ? null : superTag.tag) && !subTag.ignoreParent()) { if (st.toString().equals("java.lang.Object") || st.toString().startsWith("java.lang.Comparable") || st.toString().startsWith("java.util.Comparator")) { } else if (st.toString().startsWith("java.awt")) { } else{ issueError("\nOverridden method " + methodName + " can be called via parent " + st.toString() + ", tagged " + superTag + "\n without thread tag: " + subTag, errorLocation); } } } } } } final LocatedTag subTagFinal = subTag; if ((subTag == null || !subTag.ignoreParent()) && !"<init>".equals(methodName == null ? null : methodName.toString()) && appliedTags.values().stream().distinct().collect(Collectors.toList()).size() > 1 && (subTag == null || !appliedTags.values().stream().allMatch(t -> subTagFinal.tag.canOverride(t.tag)))) { issueError("\nMethod " + methodName + " overrides parent methods with conflicting thread tags:\n" + appliedTags.entrySet().stream().map(t -> " " + t.getKey() + ": " + t.getValue()).collect(Collectors.joining("\n")), errorLocation); } return appliedTags.isEmpty() ? null : appliedTags.values().iterator().next(); } @Override public Void visitMethodInvocation(MethodInvocationTree invocation, Void arg1) { AtomicReference<Name> name = new AtomicReference<>(); ExpressionTree lhs = invocation.getMethodSelect().accept(new SimpleTreeVisitor<ExpressionTree, Void>() { @Override public ExpressionTree visitMemberReference( MemberReferenceTree arg0, Void arg1) { name.set(arg0.getName()); return arg0.getQualifierExpression(); } @Override public ExpressionTree visitMemberSelect(MemberSelectTree arg0, Void arg1) { name.set(arg0.getIdentifier()); return arg0.getExpression(); } @Override public ExpressionTree visitIdentifier(IdentifierTree id, Void arg1) { name.set(id.getName()); return null; } }, null); TypeMirror invokeTargetType = null; try { if (lhs != null) invokeTargetType = trees.getTypeMirror(trees.getPath(cu, lhs)); else{ invokeTargetType = trees.getTypeMirror(typeScopeStack.getLast().path); } } catch (NullPointerException e) { System.err.println("Last: " + typeScopeStack.getLast()); e.printStackTrace(); trees.printMessage(Kind.ERROR, "NPE", invocation, cu); } List<TypeMirror> argTypes = invocation.getArguments().stream() .map(arg -> trees.getTypeMirror(new TreePath(getCurrentPath(), arg))) .collect(Collectors.toList()); WrapDescent wrap = checkInvocation(name.get(), lhs, invokeTargetType, invocation.getMethodSelect().toString(), argTypes, arg1, invocation); Void r = scan(invocation.getTypeArguments(), arg1); r = scan(invocation.getMethodSelect(), arg1); if (wrap.before != null) wrap.before.run(); r = scan(invocation.getArguments(), arg1); if (wrap.after != null) wrap.after.run(); return r; } private WrapDescent checkInvocation(Name name, Tree lhs, TypeMirror invokeTargetType, String methodSelect, List<TypeMirror> invokeArgTypes, Void arg1, Tree errorLocation) { if (inDebugClass()) { System.err.println("Checking invocation: " + lhs + " # " + name + " # " + invokeTargetType + " # " + methodSelect); } if (invokeTargetType == null) { return defaultReturn; } invokeTargetType = types.capture(invokeTargetType); Element invokedOn = types.asElement(invokeTargetType); if (invokedOn == null) { if (invokeTargetType instanceof ArrayType) { } else { } return defaultReturn; } Collection<? extends TypeMirror> superTypes = allSuperTypes(trees.getTypeMirror(typeScopeStack.getLast().path), lhs); LocatedTag invokedOnTag = null; List<TypeMirror> candidateArgs = null; final String nameString = name.toString(); if (nameString.equals("this")) { } else if (nameString.equals("super")) { } else { List<ExecutableElement> candidates = getMembers(invokedOn, lhs == null /*Including enclosing if no prefix false).stream() .filter(el -> el instanceof ExecutableElement) .map(el -> (ExecutableElement)el) .filter(el -> el.getSimpleName().toString().equals(nameString)) .filter(el -> el.isVarArgs() || invokeArgTypes == null || el.getParameters().size() == invokeArgTypes.size()) .collect(Collectors.toList()); if (candidates.size() > 1) { candidates = candidates.stream() .filter(el -> { if (el.isVarArgs() || invokeArgTypes == null) return true; for (int i = 0; i < invokeArgTypes.size(); i++) { if (!types.isAssignable(types.capture(invokeArgTypes.get(i)), el.getParameters().get(i).asType())) return false; } return true; }) .collect(Collectors.toList()); } if (candidates.size() == 1) { candidateArgs = candidates.get(0).getParameters().stream(). map(e -> types.capture(e.asType())).collect(Collectors.toList()); } final TypeMirror tmFinal = invokeTargetType; List<LocatedTag> candidateDirectTags = candidates.stream().map(e -> getRemoteTag(e, () -> tmFinal.toString() + "." + e.getSimpleName().toString(), errorLocation) ).distinct().collect(Collectors.toList()); if (candidateDirectTags.size() > 1) { trees.printMessage(Kind.ERROR, "\nCould not find unambigious declaration of method " + name + " in " + invokedOn.asType().toString() + " (and tags differ between resolutions)", errorLocation, cu); } else if (candidates.size() == 0) { if (inDebugClass()) System.err.println("Looking for method " + methodSelect + " in super type of " + invokeTargetType); List<? extends TypeMirror> directSupers = types.directSupertypes(invokeTargetType); if (directSupers.size() > 0) { if (inDebugClass()) System.err.println("Can't find method " + name + "{" + (invokeArgTypes == null ? "?" : invokeArgTypes.size()) + "} in " + invokeTargetType + "; trying super"); for (int i = 0; i < directSupers.size(); i++) { if (inDebugClass()) System.err.println(" Super " + i + ": " + directSupers.get(i)); WrapDescent r = checkInvocation(name, lhs, directSupers.get(i), methodSelect, invokeArgTypes, arg1, errorLocation); if (r != defaultReturn) return r; } } if (inDebugClass()) System.err.println("Can't find method " + name + " in " + invokeTargetType + " or supers; looking for outer classes: " + typeScopeStack.size()); if (lhs == null && typeScopeStack.size() > 1) { Iterator<PathAnd<ClassTree>> it = typeScopeStack.descendingIterator(); if (trees.getTypeMirror(it.next().path).equals(invokeTargetType)) while (it.hasNext()) { WrapDescent r = checkInvocation(name, lhs, trees.getTypeMirror(it.next().path), methodSelect, invokeArgTypes, arg1, errorLocation); if (r != defaultReturn) return r; } } else { if (inDebugClass()) System.err.println("Weren't on the outermost class so no need to check"); } } return defaultReturn; } else { Element e = candidates.get(0); invokeTargetType = e.getEnclosingElement().asType(); final TypeMirror invokeTargetTypeFinal = invokeTargetType; LocatedTag directTag = getRemoteTag(e, () -> invokeTargetTypeFinal.toString() + "." + e.getSimpleName().toString(), errorLocation); Collection<? extends TypeMirror> invokeTargetSuperTypes = allSuperTypes(invokeTargetType, lhs); LocatedTag overridden = checkAgainstOverridden(name, directTag, e.asType(), invokeTargetSuperTypes, true, errorLocation); LocatedTag classTag = getRemoteTag(e.getEnclosingElement(), () -> e.getEnclosingElement().getSimpleName().toString(), errorLocation); PackageElement pkg = elements.getPackageOf(e.getEnclosingElement()); LocatedTag packageDirectTag = getRemoteTag(pkg, () -> pkg.getQualifiedName().toString() + " package", errorLocation); LocatedTag packagePriorTag = packageAnns.get(pkg.getQualifiedName().toString()); LocatedTag superInherit = checkSingle(invokeTargetSuperTypes.stream() .map(ty -> { LocatedTag t = getRemoteTag(types.asElement(ty), () -> ty.toString(), errorLocation); if (t == null) { PackageElement superPkg = elements.getPackageOf(types.asElement(ty)); t = getRemoteTag(superPkg, () -> superPkg.getQualifiedName().toString() + " package", errorLocation); if (t == null) { t = packageAnns.get(superPkg.getQualifiedName().toString()); } } return t; }) .filter(t -> t != null && t.applyToAllSubclassMethods()), errorLocation); LocatedTag methodAnnTag = methodAnns.stream() .filter(m -> m.matches(invokeTargetTypeFinal, name, types.asMemberOf((DeclaredType)invokeTargetTypeFinal, e))) .map(m -> m.tag) .findFirst() .orElse(null); List<LocatedTag> tagList = Arrays.asList(directTag, methodAnnTag, classAnns.get(types.erasure(e.getEnclosingElement().asType()).toString()), classTag, packageDirectTag, packagePriorTag, overridden, superInherit); invokedOnTag = tagList.stream().filter(t -> t != null).findFirst().orElse(null); if (inDebugClass()) { for (MethodRef m : methodAnns) System.err.println("Method " + m.classType.toString() + " . \"" + m.methodName + "\"" + " class match: " + isSameType(types.erasure(m.classType), types.erasure(invokeTargetTypeFinal)) + " name match: " + m.methodName.equals(name.toString()) + " .matches: " + m.matches(invokeTargetTypeFinal, name, null)); System.err.println("Is thread: " + types.isSameType(types.erasure(elements.getTypeElement("java.lang.Thread").asType()), types.erasure(invokeTargetTypeFinal))); System.err.println("TCScanner for " + invokeTargetTypeFinal.toString() + " . \"" + name + "\"," + e.toString() + "; All super tags: " + invokeTargetSuperTypes.stream() .map(ty -> ty.toString() + ": " + getRemoteTag(types.asElement(ty), () -> ty.toString(), lhs)).collect(Collectors.joining(", "))); System.err.println("Tag list: " + tagList.stream().map(t -> "" + t).collect(Collectors.joining(", "))); } } } if (invokedOnTag != null) { if (inDebugClass()) System.err.println("Finding tag for: " + lhs + "." + methodSelect + "(...)"); Optional<LocatedTag> ann = getCurrentTag(superTypes, errorLocation); boolean sameInstance = lhs == null; if ((ann.isPresent() == false && invokedOnTag.tag != Tag.Any) || (ann.isPresent() && !ann.get().tag.canCall(invokedOnTag.tag, sameInstance))) { if (!inSynthetic()) { issueError("\n Method " + invokeTargetType.toString() + "." + name + " being called requires " + invokedOnTag + "\nbut this code may be running on another thread: " + ann.map(Object::toString).orElse("unspecified") + " lambda: " + lambdaScopeStack.size() + " " + lambdaScopeStack.stream().map(x -> "" + x).collect(Collectors.joining(", ")), errorLocation); } } if (ann.isPresent() && (ann.get().tag == Tag.FX || ann.get().tag == Tag.FX) && Arrays.asList("SwingUtilities.invokeAndWait", "EventQueue.invokeAndWait").contains(methodSelect)) { issueError("\n Swing invokeAndWait method is being called, but from tag " + ann.get() + " (you cannot make FX wait for Swing)", errorLocation); } }
| | |if (Arrays.asList("Platform.runLater", "SwingUtilities.invokeLater", "SwingUtilities.invokeAndWait", "EventQueue.invokeLater", "EventQueue.invokeAndWait").contains(methodSelect)) {} return new WrapDescent( () -> {}insideSpecial = methodSelect; }, () -> {}insideSpecial = null; } ); } else */ { | |final List<TypeMirror> candidateArgsFinal = invokeArgTypes; | |return new WrapDescent( | |() -> { callingMethodStack.addLast(candidateArgsFinal); | |}, | |() -> { callingMethodStack.removeLast(); | |} | |); | |} | |} | |private void issueError(String errorMsg, Tree errorLocation) | |{ | |long startPosition = trees.getSourcePositions().getStartPosition(cu, errorLocation); | |String link = cu.getLineMap() == null ? "" : cu.getSourceFile().getName() + ":" + cu.getLineMap().getLineNumber(startPosition) + ": error:"; // [line added as IntelliJ location link]"; trees.printMessage(Kind.ERROR, "\n" + link + errorMsg, errorLocation, cu); } private boolean inSynthetic() { return methodScopeStack.stream().anyMatch(p -> { MethodTree item = p.item; if (item == null) | |return false; // Initialiser block | |Name name = item.getName(); | |String s = name.toString(); | |return s.contains("$"); }); } private Optional<LocatedTag> getCurrentTag(Collection<? extends TypeMirror> superTypes, Tree errorLocation) { // If if we are an initializer block, also look at the outer method/class: | |Name methodName; | |TypeMirror methodType; | |LocatedTag methodAnn; | |LocatedTag classAnn; | |LocatedTag knownMethodAnn = null; | |if (methodScopeStack.isEmpty() || (methodScopeStack.getLast().item == null && methodScopeStack.size() == 1)) | |{ | |// We are in an initializer block for a top-level class or a named inner class. No method annotations but there might be | |// a class annotation: | |methodName = null; | |methodType = null; | |methodAnn = null; | |classAnn = getSourceTag(typeScopeStack.getLast().item, () -> cu.getPackageName().toString() + typeScopeStack.stream().limit(typeScopeStack.size() - 1).map(TCScanner::typeToName).collect(Collectors.joining("."))); } else if (methodScopeStack.getLast().item == null) { // We are in an initializer block for an anonymous inner class, declared inside a method. // (if methodScopeStack was size 1, we would have taken previous branch. We are | |// only here if we are inside a method, and you can only have an initializer block | |// inside a method by using an anonymous inner class.) | |// The initializer will be run on object creation, which occurs immediately inside the method | |// So we just grab the tag info from the surrounding method (and its class): | |MethodTree surroundingMethod = methodScopeStack.get(methodScopeStack.size() - 2).item; | |methodName = surroundingMethod.getName(); | |methodType = trees.getTypeMirror(trees.getPath(cu, surroundingMethod)); | |methodAnn = getSourceTag(surroundingMethod, () -> cu.getPackageName().toString() + typeScopeStack.stream().limit(typeScopeStack.size() - 1).map(TCScanner::typeToName).collect(Collectors.joining("."))); classAnn = getSourceTag(typeScopeStack.get(typeScopeStack.size() - 2).item, () -> cu.getPackageName().toString() + typeScopeStack.stream().limit(typeScopeStack.size() - 2).map(TCScanner::typeToName).collect(Collectors.joining("."))); knownMethodAnn = methodAnns.stream() .filter(m -> allSuperTypes( trees.getTypeMirror(typeScopeStack.get(typeScopeStack.size() - 2).path), errorLocation) | |.stream() | |.anyMatch(ty -> m.matches(ty, methodName, methodType))) | |.map(m -> m.tag) | |.findFirst().orElse(null); | |} | |else | |{ | |// Otherwise, we are in a standard method; gather tag info from method and surrounding class: | |methodName = methodScopeStack.getLast().item.getName(); | |methodType = trees.getTypeMirror(methodScopeStack.getLast().path); | |methodAnn = getSourceTag(methodScopeStack.getLast().item, () -> cu.getPackageName().toString() + typeScopeStack.stream().limit(typeScopeStack.size()).map(TCScanner::typeToName).collect(Collectors.joining("."))); classAnn = getSourceTag(typeScopeStack.getLast().item, () -> cu.getPackageName().toString() + typeScopeStack.stream().limit(typeScopeStack.size() - 1).map(TCScanner::typeToName).collect(Collectors.joining("."))); knownMethodAnn = methodAnns.stream() .filter(m -> allSuperTypes(trees.getTypeMirror(typeScopeStack.getLast().path), errorLocation).stream().anyMatch(ty -> m.matches(ty, methodName, methodType))) | |.map(m -> m.tag) | |.findFirst().orElse(null); | |} | |LocatedTag inheritedTag = methodScopeStack.isEmpty() ? null : checkAgainstOverridden(methodName, methodAnn, methodType, superTypes, false, errorLocation); | |PackageElement pkg = (PackageElement)trees.getElement(typeScopeStack.getFirst().path).getEnclosingElement(); | |LocatedTag packageAnn = getRemoteTag(pkg, () -> pkg.getQualifiedName().toString() + " package", errorLocation); //cu.getPackageAnnotations().stream().map(t -> getSourceTag(t, cu.getPackageName().toString() + " package")).filter(t -> t != null).findFirst().orElse(null); LocatedTag lambdaAnn = null; if (inDebugClass()) System.err.println(" >>Lambda stack size: " + lambdaScopeStack.size()); for (int i = lambdaScopeStack.size() - 1; i >= 0; i--) { lambdaAnn = lambdaScopeStack.get(i); if (lambdaAnn != null) { if (inDebugClass()) | |System.err.println(" >>Found lambda ann " + lambdaAnn); break; } } Optional<LocatedTag> ann = Optional.empty(); // If we are inside a lambda, it overrides everything else: | |if (lambdaAnn != null) | |ann = Optional.of(lambdaAnn); | |// If there is method, it overrides class: | |else if (methodAnn != null) | |ann = Optional.of(methodAnn); | |else if (knownMethodAnn != null) | |ann = Optional.of(knownMethodAnn); | |// Otherwise if there is class, overrides package: | |else if (classAnn != null) | |ann = Optional.of(classAnn); | |// Otherwise if there is package, use that: | |else if (packageAnn != null) | |ann = Optional.of(packageAnn); | |else if (packageAnns.containsKey(cu.getPackageName().toString())) | |ann = Optional.of(packageAnns.get(cu.getPackageName().toString())); | |// Or an inherited method/class: | |else if (inheritedTag != null) | |ann = Optional.of(inheritedTag); | |return ann; | |} | |/** | Looks to see if the tag matches a surrounding Platform.runLater, etc, that we | are currently inside during our visitor: | | | |private boolean matchesSpecial(LocatedTag tag) | |{}if (tag.tag == Tag.FX_UsesSwing || tag.tag == Tag.FX && Arrays.asList("Platform.runLater").contains(insideSpecial)) {} return true; } else if (tag.tag == Tag.Swing && Arrays.asList("SwingUtilities.invokeAndWait", "SwingUtilities.invokeLater", "EventQueue.invokeLater", "EventQueue.invokeAndWait").contains(insideSpecial)) {} return true; } return false; } */ private LocatedTag fromSpecial(String typeName, String call, Optional<LocatedTag> calledFrom_, Tree errorLocation) | |{ | |Tag calledFrom = calledFrom_.map(lt -> lt.tag).orElse(Tag.Any); | |if (Arrays.asList("Platform.runLater").contains(call)) { if (calledFrom == Tag.FXPlatform || calledFrom == Tag.FX) issueError("\nCalling runLater from thread " + calledFrom, errorLocation); return new LocatedTag(Tag.FXPlatform, true, true, "<runLater>"); } else if (Arrays.asList("SwingUtilities.invokeAndWait", "SwingUtilities.invokeLater", "EventQueue.invokeLater", "EventQueue.invokeAndWait").contains(call)) { if (calledFrom == Tag.Swing) issueError("Calling " + call + " from Swing thread", errorLocation); return new LocatedTag(Tag.Swing, true, true, "<invokeLater>"); } else if (Arrays.asList("background.execute").contains(call)) return new LocatedTag(Tag.Worker, true, true, "<Executor.execute>"); //else if (typeName.startsWith("java.") && call.endsWith("forEach")) // Bit hacky //{}// System.err.println("Found a forEach: " + call + " so returning: " + calledFrom_); // return calledFrom_.orElse(null); //} else{ return null; } } /** * Gets members of this type * @param invokedOn The type to consider * @param includeEnclosing Whether to also include members from enclosing elements (e.g. outer classes) | @param includeSuperTypes Whether to also include members from superclasses | @return | private List getMembers(Element invokedOn, boolean includeEnclosing, boolean includeSuperTypes) { List<Element> membersInclSuperAndEnclosing = new ArrayList<>(); for (Element e = invokedOn; e != null; e = includeEnclosing ? e.getEnclosingElement() : null) { if (e instanceof TypeElement) membersInclSuperAndEnclosing.addAll(includeSuperTypes ? elements.getAllMembers((TypeElement)e) : ((TypeElement)e).getEnclosedElements()); } p.public else if(e instanceof TypeParameterElement) { ((TypeParameterElement)e).getBounds().forEach(t -> membersInclSuperAndEnclosing.addAll(getMembers(types.asElement(t), includeEnclosing, includeSuperTypes))); } } return membersInclSuperAndEnclosing; } private Collection allSuperTypes(TypeMirror orig, Tree errorLocation) { if (orig == null) { return Collections.emptyList(); } LocatedTag ttag = getRemoteTag(types.asElement(orig), () -> orig.toString(), errorLocation); if (ttag != null && ttag.ignoreParent()) { return Collections.emptyList(); } List<? extends TypeMirror> supers = types.directSupertypes(orig); return Stream.concat(supers.stream(), supers.stream() .flatMap(t -> allSuperTypes(t, errorLocation).stream())).collect(Collectors.toList()); }
| Fetches a tag by looking at annotations on a given piece of program source | private LocatedTag getSourceTag(AnnotationTree a, Supplier<String> info) { if (a.getAnnotationType().toString().equals(OnThread.class.getSimpleName())) { Tag tag = null; boolean ignoreParent = false; boolean requireSynchronized = false; for (String s : a.getArguments().stream().map(Object::toString).collect(Collectors.toList())) { for (Tag t : Arrays.asList(Tag.values())) { if (s.equals("value = " + enumToQual(t))) { tag = t; } } if (s.equals("ignoreParent = true")) ignoreParent = true; if (s.equals("requireSynchronized = true")) requireSynchronized = true; } if (tag != null) return new LocatedTag(tag, ignoreParent, requireSynchronized, false, info); throw new IllegalStateException("Unknown tag: " + a.getArguments().get(0) + " in " + info); } return null; } private LocatedTag getRemoteTag(AnnotationMirror m, Supplier<String> info) { if (m.getAnnotationType().asElement().getSimpleName().toString().equals(OnThread.class.getSimpleName())) { return stringToTag(m.getElementValues().entrySet().stream().map(x -> "" + x.getKey() + " = " + x.getValue()), () -> "<" + info.get()); } return null; } private LocatedTag stringToTag(Stream<String> keyVals, Supplier<String> info) { List<String> stringValues = keyVals.collect(Collectors.toList()); Tag tag = null; boolean ignoreParent = false; boolean requireSynchronized = false; for (String s : stringValues) { for (Tag t : Arrays.asList(Tag.values())) { if (s.equals("value() = " + Tag.class.getCanonicalName() + "." + t.toString())) { tag = t; } } if (s.equals("ignoreParent() = true")) ignoreParent = true; if (s.equals("requireSynchronized() = true")) requireSynchronized = true; } if (tag != null) return new LocatedTag(tag, ignoreParent, requireSynchronized, false, info); throw new IllegalArgumentException("Unknown tag: " + stringValues.stream().collect(Collectors.joining(", ")) + " in " + cu.getSourceFile().toString()); }
| Fetches a tag by looking at an Element, which may well come from a separate compilation unit | The name is meant to contrast with getSourceTag, which does come from the current compilation unit | private LocatedTag getRemoteTag(Element e, Supplier<String> info, Tree errorLocation) { return checkSingle(e.getAnnotationMirrors().stream().map(m -> getRemoteTag(m, info)), errorLocation); }
| Fetches a tag by looking at annotations on a given piece of program source | private LocatedTag getSourceTag(ClassTree t, Supplier<String> enclosingStem) { return checkSingle(t.getModifiers().getAnnotations().stream().map(a -> getSourceTag(a, () -> enclosingStem.get() + "." + t.getSimpleName().toString() + " class")), t); }
| Fetches a tag by looking at annotations on a given piece of program source | private LocatedTag getSourceTag(VariableTree t, Supplier<String> enclosingStem) { return checkSingle(t.getModifiers().getAnnotations().stream().map(a -> getSourceTag(a, () -> enclosingStem.get() + "." + t.getName().toString() + " field")), t); }
| Fetches a tag by looking at annotations on a given piece of program source | private LocatedTag getSourceTag(MethodTree t, Supplier<String> enclosingStem) { return checkSingle(t.getModifiers().getAnnotations().stream().map(a -> getSourceTag(a, () -> enclosingStem.get() + "." + t.getName().toString() + " method")), t); }
| Is the method tagged as @SuppressWarnings, where one of the strings is "threadchecker"? |*/ private boolean suppressesChecker(MethodTree t) { return t.getModifiers().getAnnotations().stream().anyMatch(a -> { if (a.getAnnotationType().toString().equals("SuppressWarnings")) { return a.getArguments().stream().map(Object::toString).anyMatch(c -> c.contains("threadchecker")); } return false; }); } /** * Checks that the given stream of tags only contains a single distinct kind of tag (or none). * If it has multiple distinct tags, a compiler error is issued at the given location. private LocatedTag checkSingle(Stream<LocatedTag> tagStream, Tree tree) { List<LocatedTag> tags = tagStream.filter(t -> t != null).distinct().collect(Collectors.toList()); if (tags.size() > 1) { issueError("\n Multiple conflicting thread tags: " + tags.stream().map(Object::toString).collect(Collectors.joining(", ")), tree); } return tags.isEmpty() ? null : tags.get(0); } @Override public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { Tree parent = getCurrentPath().getParentPath().getLeaf(); TypeMirror lambdaClassType = calculatedExpectedLambdaType(parent, node); if (inDebugClass()) { System.err.println("Lambda type " + getCurrentPath().toString() + " (i.e. " + node.toString() + ") is " + lambdaClassType + " parent was " + parent.getClass() + " " + parent + " last stack 0: " + (callingMethodStack.isEmpty() ? "empty" : (callingMethodStack.getLast() == null ? "missing" : callingMethodStack.getLast().get(0)))); } if (lambdaClassType == null) { return super.visitLambdaExpression(node, p); } Optional<LocatedTag> lambdaAnn = lambdaClassToAnn(parent, lambdaClassType, node, true); if (lambdaAnn == null) { if (inDebugClass()) System.err.println(" Lambda annotation error"); return super.visitLambdaExpression(node, p); } lambdaScopeStack.add(lambdaAnn.orElse(null)); Void r = super.visitLambdaExpression(node, p); lambdaScopeStack.removeLast(); return r; } private boolean inDebugClass() { return false; } private Optional lambdaClassToAnn(Tree parent, TypeMirror lambdaClassType, Tree errorLocation, boolean issueError) { lambdaClassType = types.capture(lambdaClassType); Element lambdaClassElement = types.asElement(lambdaClassType); List<Element> lambdaClassMembers = getMembers(lambdaClassElement, false, true) .stream() .filter(e -> e instanceof ExecutableElement) .filter(e -> !e.getModifiers().contains(Modifier.STATIC) && !e.getModifiers().contains(Modifier.DEFAULT)) .filter(e -> !objectMembers.contains(e.getSimpleName().toString())) .collect(Collectors.toList()); if (lambdaClassMembers.size() != 1) { if (issueError && !(lambdaClassType.getKind() == TypeKind.TYPEVAR)) trees.printMessage(Kind.ERROR, "\n Lambda type " + (lambdaClassElement == null ? "Unknown" : lambdaClassElement.getSimpleName()) + " seems to have multiple members: " + lambdaClassMembers.stream().map(Element::getSimpleName).map(Object::toString).collect(Collectors.joining(", ")), errorLocation, cu); return null; } LocatedTag lambdaAnn = null; if (parent instanceof MethodInvocationTree) { String call = ((MethodInvocationTree)parent).getMethodSelect().toString(); lambdaAnn = fromSpecial(lambdaClassType.toString(), call, getCurrentTag(Collections.emptyList(), errorLocation), errorLocation); } if (lambdaAnn != null) return Optional.of(lambdaAnn); if (inDebugClass()) System.err.println(" >>Getting remote tag for " + lambdaClassMembers.get(0)); lambdaAnn = getRemoteTag(lambdaClassMembers.get(0), () -> "", errorLocation); if (inDebugClass()) System.err.println(" >> Tag on method: " + lambdaAnn); if (lambdaAnn != null) return Optional.of(lambdaAnn); lambdaAnn = getRemoteTag(lambdaClassElement, () -> "", errorLocation); if (inDebugClass()) System.err.println(" >> Tag directly on class: " + lambdaAnn); if (lambdaAnn != null) return Optional.of(lambdaAnn); String qualClassName = elements.getPackageOf(lambdaClassElement).getQualifiedName().toString() + "." + lambdaClassElement.getSimpleName(); lambdaAnn = classAnns.get(qualClassName); if (inDebugClass()) System.err.println(" >> Tag in classAnns: " + lambdaAnn + " based on " + qualClassName); if (lambdaAnn != null) return Optional.of(lambdaAnn); lambdaAnn = getRemoteTag(elements.getPackageOf(lambdaClassElement), () -> "", errorLocation); if (inDebugClass()) System.err.println(" >> Tag directly on package: " + lambdaAnn); if (lambdaAnn != null) return Optional.of(lambdaAnn); lambdaAnn = packageAnns.get(elements.getPackageOf(lambdaClassElement).getQualifiedName().toString()); if (inDebugClass()) System.err.println(" >> Tag in packageAnns: " + lambdaAnn); return Optional.ofNullable(lambdaAnn); } private TypeMirror calculatedExpectedLambdaType(Tree parent, Tree lambdaArg) { return parent.accept(new SimpleTreeVisitor<TypeMirror, Void>() { @Override public TypeMirror visitReturn(ReturnTree node, Void p) { if (!lambdaScopeStack.isEmpty()) return null; return trees.getTypeMirror(methodScopeStack.getLast().path).accept(new TypeVisitor<TypeMirror, Void>() { @Override public TypeMirror visitExecutable(ExecutableType t, Void p2) { return t.getReturnType(); } @Override public TypeMirror visit(TypeMirror t, Void aVoid) { return null; } @Override public TypeMirror visit(TypeMirror t) { return null; } @Override public TypeMirror visitArray(ArrayType t, Void aVoid) { return null; } @Override public TypeMirror visitDeclared(DeclaredType t, Void aVoid) { return null; } @Override public TypeMirror visitError(ErrorType t, Void aVoid) { return null; } @Override public TypeMirror visitIntersection(IntersectionType t, Void aVoid) { return null; } @Override public TypeMirror visitNoType(NoType t, Void aVoid) { return null; } @Override public TypeMirror visitNull(NullType t, Void aVoid) { return null; } @Override public TypeMirror visitPrimitive(PrimitiveType t, Void aVoid) { return null; } @Override public TypeMirror visitTypeVariable(TypeVariable t, Void aVoid) { return null; } @Override public TypeMirror visitUnion(UnionType t, Void aVoid) { return null; } @Override public TypeMirror visitUnknown(TypeMirror t, Void aVoid) { return null; } @Override public TypeMirror visitWildcard(WildcardType t, Void aVoid) { return null; } }, p); } @Override public TypeMirror visitVariable(VariableTree node, Void p) { return trees.getTypeMirror(trees.getPath(cu, node.getType())); } @Override public TypeMirror visitMethodInvocation(MethodInvocationTree node, Void p) { if (!callingMethodStack.isEmpty() && callingMethodStack.getLast() != null) { for (int i = 0; i < node.getArguments().size(); i++) { if (node.getArguments().get(i) == lambdaArg && i < callingMethodStack.getLast().size()) { return types.capture(callingMethodStack.getLast().get(i)); } } } return null; } @Override public TypeMirror visitNewClass(NewClassTree node, Void arg1) { if (!callingMethodStack.isEmpty() && callingMethodStack.getLast() != null) { for (int i = 0; i < node.getArguments().size(); i++) { if (node.getArguments().get(i) == lambdaArg && i < callingMethodStack.getLast().size()) { return callingMethodStack.getLast().get(i); } } } return null; } }, null); } @Override public Void visitNewClass(NewClassTree node, Void arg1) { List<TypeMirror> argTypes = node.getArguments().stream() .map(arg -> trees.getTypeMirror(new TreePath(getCurrentPath(), arg))) .collect(Collectors.toList()); WrapDescent wrap = checkInvocation(elements.getName("<init>"), node.getIdentifier(), trees.getTypeMirror(trees.getPath(cu, node.getIdentifier())), null, argTypes, arg1, node); if (wrap.before != null) wrap.before.run(); Void r = super.visitNewClass(node, arg1); if (wrap.after != null) wrap.after.run(); return r; } private static class PathAnd<T> { public final T item; public final TreePath path; private PathAnd(T item, TreePath path) { this.item = item; this.path = path; } } private class MethodRef { private final TypeMirror classType; private final String methodName; private final List<TypeMirror> methodType; private final LocatedTag tag; public MethodRef(String className, String methodName, LocatedTag tag) throws NoSuchMethodException { TypeElement el = elements.getTypeElement(className); if (el == null) throw new NoSuchMethodException(); this.classType = el.asType(); this.methodName = methodName; this.methodType = elements.getAllMembers(el).stream().filter(e -> methodName.equals(e.getSimpleName().toString())).map(Element::asType).collect(Collectors.toList()); this.tag = tag; } public boolean matches(TypeMirror classType, Name methodName, TypeMirror methodType) { return this.methodName.equals(methodName.toString()) && isSameType(types.erasure(this.classType), types.erasure(classType)) && (true || this.methodType.contains(methodType)); } } private class WrapDescent { Runnable before; Runnable after; public WrapDescent(Runnable before, Runnable after) { this.before = before; this.after = after; } } @Override public Void visitIdentifier(IdentifierTree node, Void aVoid) { if (methodScopeStack.size() > 0 && (methodScopeStack.getLast().item == null || methodScopeStack.getLast().item.getReturnType() == null)) return super.visitIdentifier(node, aVoid); LocatedTag tag = fields.get(node.getName().toString()); if (tag != null) { Collection<? extends TypeMirror> superTypes = allSuperTypes( trees.getTypeMirror(typeScopeStack.getLast().path), node); Optional<LocatedTag> ann = getCurrentTag(superTypes, node); if (ann.isPresent() && !ann.get().tag.canCall(tag.tag, true)) { issueError("\n Field " + node.getName() + " being used requires " + tag + "\nbut this code may be running on another thread: " + ann.map(Object::toString).orElse("unspecified"), node); } if (tag.requireSynchronized() && methodScopeStack.size() > 0 && methodScopeStack.getLast().item != null && !methodScopeStack.getLast().item.getModifiers().getFlags().contains(Modifier.SYNCHRONIZED) && !inSynchronizedThis) { issueError("\n Field " + node.getName() + " being used requires synchronized but method is not synchronized", node); } } return super.visitIdentifier(node, aVoid); } @Override public Void visitSynchronized(SynchronizedTree node, Void aVoid) { boolean isThis; if (node.getExpression() == null) isThis = true; else { isThis = node.getExpression().toString().contains("this") || node.getExpression().toString().contains(".class") | for static | } if (isThis) { inSynchronizedThis = true; Void p = super.visitSynchronized(node, aVoid); inSynchronizedThis = false; return p; } else{ return super.visitSynchronized(node, aVoid); } } @Override public Void visitVariable(VariableTree node, Void aVoid) { | | |The rule for accessing fields is: | |- If the field is final: | |- If the field is primitive or String or File or AtomicInteger, access is allowed from Any thread | |- If the type of the field (possibly by way of the field type's package) has a tag, access is allowed from Any thread. E.g. you may have a "final Package pkg;" field. Any thread should be able to access the field, because all of Package's methods are protected by the Swing tag anyway. Contrast with "final List foo;" -- this should not allow access from any thread because you can get race hazards modifying the list. - Otherwise, the field receives the tag from its enclosing type (or enclosing type's package). So for example, Project is tagged Swing; all of Project's fields are also tagged Swing. | |Thus you should only get field problems flagged when some methods have different tags to the class as a whole, or you use fields from inside invokeLater/runLater. Otherwise, all your methods are assumed to be running on the same thread, and thus there are no race hazards. if (typeScopeStack.size() == 1 && methodScopeStack.size() == 0) { LocatedTag explicit = getSourceTag(node, () -> cu.getPackageName().toString() + typeScopeStack.stream().map(TCScanner::typeToName).collect(Collectors.joining("."))); if (explicit != null) { fields.put(node.getName().toString(), explicit); return super.visitVariable(node, aVoid); } else if (node.getModifiers().getFlags().contains(Modifier.VOLATILE)) { fields.put(node.getName().toString(), new LocatedTag(Tag.Any, false, false, "volatile")); return super.visitVariable(node, aVoid); } else if (node.getModifiers().getFlags().contains(Modifier.FINAL)) { if (Arrays.asList("String", "int", "double", "boolean", "char", "float", "short", "long", "File", "AtomicInteger").contains(node.getType().toString())) { fields.put(node.getName().toString(), new LocatedTag(Tag.Any, false, false, "final String/primitive")); return super.visitVariable(node, aVoid); } else { TypeMirror typeMirror = trees.getTypeMirror(trees.getPath(cu, node)); if (typeMirror == null) trees.printMessage(Kind.ERROR, "Null TypeMirror", node, cu); Element element = types.asElement(typeMirror); if (element == null) { } else { LocatedTag varTypeTag = getRemoteTag(element, () -> node.getType().toString(), node); if (varTypeTag != null) { fields.put(node.getName().toString(), new LocatedTag(Tag.Any, false, false, "final BlueJ class")); return super.visitVariable(node, aVoid); } else { PackageElement pkg = elements.getPackageOf(element); LocatedTag pkgTag = getRemoteTag(pkg, () -> node.getType().toString(), node); if (pkgTag != null) { fields.put(node.getName().toString(), new LocatedTag(Tag.Any, false, false, "final BlueJ class")); return super.visitVariable(node, aVoid); } } } } } Collection<? extends TypeMirror> superTypes = allSuperTypes( trees.getTypeMirror(typeScopeStack.getLast().path), node); Optional<LocatedTag> ann = getCurrentTag(superTypes, cu); fields.put(node.getName().toString(), ann.orElse(null)); } return super.visitVariable(node, aVoid); } private boolean isSameType(TypeMirror a, TypeMirror b) { return types.isSameType(a, b) || a.toString().equals(b.toString()); } }

.   TCScanner
.   checkAgainstOverridden
.   visitMethodInvocation
.   visitMemberReference
.   visitMemberSelect
.   visitIdentifier
.   checkInvocation
.   getMembers
.   if
.   allSuperTypes
.   getSourceTag
.   getRemoteTag
.   stringToTag
.   getRemoteTag
.   getSourceTag
.   getSourceTag
.   getSourceTag
.   checkSingle
.   visitLambdaExpression
.   inDebugClass
.   lambdaClassToAnn
.   calculatedExpectedLambdaType
.   visitReturn
.   visitExecutable
.   visit
.   visit
.   visitArray
.   visitDeclared
.   visitError
.   visitIntersection
.   visitNoType
.   visitNull
.   visitPrimitive
.   visitTypeVariable
.   visitUnion
.   visitUnknown
.   visitWildcard
.   visitVariable
.   visitMethodInvocation
.   visitNewClass
.   visitNewClass
.   PathAnd

top, use, map, class MethodRef

.   MethodRef
.   matches

top, use, map, class WrapDescent

.   WrapDescent
.   visitIdentifier
.   visitSynchronized
.   visitVariable
.   isSameType




1460 neLoCode + 183 LoComm