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 extends TypeMirror> 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