use of com.sun.source.tree.StatementTree in project checker-framework by typetools.
the class InterningVisitor method suppressInsideComparison.
/**
* Pattern matches particular comparisons to avoid common false positives in the {@link
* Comparable#compareTo(Object)} and {@link Object#equals(Object)}.
*
* <p>Specifically, this method tests if: the comparison is a == comparison, it is the test of
* an if statement that's the first statement in the method, and one of the following is true:
*
* <ol>
* <li>the method overrides {@link Comparator#compare}, the "then" branch of the if statement
* returns zero, and the comparison tests equality of the method's two parameters
* <li>the method overrides {@link Object#equals(Object)} and the comparison tests "this"
* against the method's parameter
* <li>the method overrides {@link Comparable#compareTo(Object)}, the "then" branch of the if
* statement returns zero, and the comparison tests "this" against the method's parameter
* </ol>
*
* @param node the comparison to check
* @return true if one of the supported heuristics is matched, false otherwise
*/
// TODO: handle != comparisons too!
// TODO: handle more methods, such as early return from addAll when this == arg
private boolean suppressInsideComparison(final BinaryTree node) {
// Only handle == binary trees
if (node.getKind() != Tree.Kind.EQUAL_TO) {
return false;
}
Tree left = node.getLeftOperand();
Tree right = node.getRightOperand();
// Only valid if we're comparing identifiers.
if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER))
return false;
// parens and blocks), terminate.
if (!Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) {
return false;
}
// Ensure the if statement is the first statement in the method
TreePath parentPath = getCurrentPath().getParentPath();
// Retrieve the enclosing if statement tree and method tree
Tree tree, ifStatementTree = null;
MethodTree methodTree = null;
while ((tree = parentPath.getLeaf()) != null) {
if (tree.getKind() == Tree.Kind.IF) {
ifStatementTree = tree;
} else if (tree.getKind() == Tree.Kind.METHOD) {
methodTree = (MethodTree) tree;
break;
}
parentPath = parentPath.getParentPath();
}
// The call to Heuristics.matchParents already ensured there is an enclosing if statement
assert ifStatementTree != null;
// The call to Heuristics.matchParents already ensured there is an enclosing method
assert methodTree != null;
StatementTree stmnt = methodTree.getBody().getStatements().get(0);
// The call to Heuristics.matchParents already ensured the enclosing method has at least one statement (an if statement) in the body
assert stmnt != null;
if (stmnt != ifStatementTree) {
// The if statement is not the first statement in the method.
return false;
}
ExecutableElement enclosing = TreeUtils.elementFromDeclaration(visitorState.getMethodTree());
assert enclosing != null;
final Element lhs = TreeUtils.elementFromUse((IdentifierTree) left);
final Element rhs = TreeUtils.elementFromUse((IdentifierTree) right);
// Matcher to check for if statement that returns zero
Heuristics.Matcher matcherIfReturnsZero = new Heuristics.Matcher() {
@Override
public Boolean visitIf(IfTree tree, Void p) {
return visit(tree.getThenStatement(), p);
}
@Override
public Boolean visitBlock(BlockTree tree, Void p) {
if (tree.getStatements().size() > 0) {
return visit(tree.getStatements().get(0), p);
}
return false;
}
@Override
public Boolean visitReturn(ReturnTree tree, Void p) {
ExpressionTree expr = tree.getExpression();
return (expr != null && expr.getKind() == Tree.Kind.INT_LITERAL && ((LiteralTree) expr).getValue().equals(0));
}
};
// "return 0" statement (for the Comparator.compare heuristic).
if (overrides(enclosing, Comparator.class, "compare")) {
final boolean returnsZero = Heuristics.Matchers.withIn(Heuristics.Matchers.ofKind(Tree.Kind.IF, matcherIfReturnsZero)).match(getCurrentPath());
if (!returnsZero) {
return false;
}
assert enclosing.getParameters().size() == 2;
Element p1 = enclosing.getParameters().get(0);
Element p2 = enclosing.getParameters().get(1);
return (p1.equals(lhs) && p2.equals(rhs)) || (p2.equals(lhs) && p1.equals(rhs));
} else if (overrides(enclosing, Object.class, "equals")) {
assert enclosing.getParameters().size() == 1;
Element param = enclosing.getParameters().get(0);
Element thisElt = getThis(trees.getScope(getCurrentPath()));
assert thisElt != null;
return (thisElt.equals(lhs) && param.equals(rhs)) || (param.equals(lhs) && thisElt.equals(rhs));
} else if (overrides(enclosing, Comparable.class, "compareTo")) {
final boolean returnsZero = Heuristics.Matchers.withIn(Heuristics.Matchers.ofKind(Tree.Kind.IF, matcherIfReturnsZero)).match(getCurrentPath());
if (!returnsZero) {
return false;
}
assert enclosing.getParameters().size() == 1;
Element param = enclosing.getParameters().get(0);
Element thisElt = getThis(trees.getScope(getCurrentPath()));
assert thisElt != null;
return (thisElt.equals(lhs) && param.equals(rhs)) || (param.equals(lhs) && thisElt.equals(rhs));
}
return false;
}
use of com.sun.source.tree.StatementTree in project error-prone by google.
the class StreamResourceLeak method addFix.
@Override
protected void addFix(Description.Builder description, ExpressionTree tree, VisitorState state) {
TreePath parentPath = state.getPath().getParentPath();
Tree parent = parentPath.getLeaf();
SuggestedFix.Builder fix = SuggestedFix.builder();
String streamType = SuggestedFixes.prettyType(state, fix, ASTHelpers.getReturnType(tree));
if (parent instanceof MemberSelectTree) {
StatementTree statement = state.findEnclosing(StatementTree.class);
if (statement instanceof VariableTree) {
// Variables need to be declared outside the try-with-resources:
// e.g. `int count = Files.lines(p).count();`
// -> `int count; try (Stream<String> stream = Files.lines(p)) { count = stream.count(); }`
VariableTree var = (VariableTree) statement;
int pos = ((JCTree) var).getStartPosition();
int initPos = ((JCTree) var.getInitializer()).getStartPosition();
int eqPos = pos + state.getSourceForNode(var).substring(0, initPos - pos).lastIndexOf('=');
fix.replace(eqPos, initPos, String.format(";\ntry (%s stream = %s) {\n%s =", streamType, state.getSourceForNode(tree), var.getName().toString()));
} else {
// the non-variable case, e.g. `return Files.lines(p).count()`
// -> try (Stream<Stream> stream = Files.lines(p)) { return stream.count(); }`
fix.prefixWith(statement, String.format("try (%s stream = %s) {\n", streamType, state.getSourceForNode(tree)));
}
fix.replace(tree, "stream");
fix.postfixWith(statement, "}");
description.addFix(fix.build());
} else if (parent instanceof VariableTree) {
// If the stream is assigned to a variable, wrap the variable in a try-with-resources
// that includes all statements in the same block that reference the variable.
Tree grandParent = parentPath.getParentPath().getLeaf();
if (!(grandParent instanceof BlockTree)) {
return;
}
List<? extends StatementTree> statements = ((BlockTree) grandParent).getStatements();
int idx = statements.indexOf(parent);
int lastUse = idx;
for (int i = idx + 1; i < statements.size(); i++) {
boolean[] found = { false };
statements.get(i).accept(new TreeScanner<Void, Void>() {
@Override
public Void visitIdentifier(IdentifierTree tree, Void unused) {
if (Objects.equals(ASTHelpers.getSymbol(tree), ASTHelpers.getSymbol(parent))) {
found[0] = true;
}
return null;
}
}, null);
if (found[0]) {
lastUse = i;
}
}
fix.prefixWith(parent, "try (");
fix.replace(state.getEndPosition(((VariableTree) parent).getInitializer()), state.getEndPosition(parent), ") {");
fix.postfixWith(statements.get(lastUse), "}");
description.addFix(fix.build());
} else if (parent instanceof EnhancedForLoopTree) {
// If the stream is used in a loop (e.g. directory streams), wrap the loop in
// try-with-resources.
fix.prefixWith(parent, String.format("try (%s stream = %s) {\n", streamType, state.getSourceForNode(tree)));
fix.replace(tree, "stream");
fix.postfixWith(parent, "}");
description.addFix(fix.build());
} else if (parent instanceof MethodInvocationTree) {
// If the stream is used in a method that is called in an expression statement, wrap it in
// try-with-resources.
Tree grandParent = parentPath.getParentPath().getLeaf();
if (!(grandParent instanceof ExpressionStatementTree)) {
return;
}
fix.prefixWith(parent, String.format("try (%s stream = %s) {\n", streamType, state.getSourceForNode(tree)));
fix.replace(tree, "stream");
fix.postfixWith(grandParent, "}");
description.addFix(fix.build());
}
}
use of com.sun.source.tree.StatementTree in project error-prone by google.
the class ModifyCollectionInEnhancedForLoop method blockEndsInBreakOrReturn.
private static boolean blockEndsInBreakOrReturn(VisitorState state) {
TreePath statementPath = state.findPathToEnclosing(StatementTree.class);
if (statementPath == null) {
return false;
}
Tree parent = statementPath.getParentPath().getLeaf();
if (!(parent instanceof BlockTree)) {
return false;
}
StatementTree statement = (StatementTree) statementPath.getLeaf();
List<? extends StatementTree> statements = ((BlockTree) parent).getStatements();
int idx = statements.indexOf(statement);
if (idx == -1 || idx == statements.size()) {
return false;
}
switch(getLast(statements).getKind()) {
case BREAK:
case RETURN:
return true;
default:
return false;
}
}
use of com.sun.source.tree.StatementTree in project error-prone by google.
the class FindIdentifiers method findAllIdents.
/**
* Finds the set of all bare variable identifiers in scope at the current location. Identifiers
* are ordered by ascending distance/scope count from the current location to match shadowing
* rules. That is, if two variables with the same simple names appear in the set, the one that
* appears first in iteration order is the one you get if you use the bare name in the source
* code.
*
* <p>We do not report variables that would require a qualfied access. We also do not handle
* wildcard imports.
*/
public static LinkedHashSet<VarSymbol> findAllIdents(VisitorState state) {
ImmutableSet.Builder<VarSymbol> result = new ImmutableSet.Builder<>();
Tree prev = state.getPath().getLeaf();
for (Tree curr : state.getPath().getParentPath()) {
switch(curr.getKind()) {
case BLOCK:
for (StatementTree stmt : ((BlockTree) curr).getStatements()) {
if (stmt.equals(prev)) {
break;
}
addIfVariable(stmt, result);
}
break;
case METHOD:
for (VariableTree param : ((MethodTree) curr).getParameters()) {
result.add(ASTHelpers.getSymbol(param));
}
break;
case CATCH:
result.add(ASTHelpers.getSymbol(((CatchTree) curr).getParameter()));
break;
case CLASS:
case INTERFACE:
case ENUM:
case ANNOTATION_TYPE:
// field is referred to by qualified name, but we don't support that.
for (Tree member : ((ClassTree) curr).getMembers()) {
if (member.equals(prev)) {
break;
}
addIfVariable(member, result);
}
// Collect inherited fields.
Type classType = ASTHelpers.getType(curr);
List<Type> classTypeClosure = state.getTypes().closure(classType);
List<Type> superTypes = classTypeClosure.size() <= 1 ? Collections.emptyList() : classTypeClosure.subList(1, classTypeClosure.size());
for (Type type : superTypes) {
Scope scope = type.tsym.members();
ImmutableList.Builder<VarSymbol> varsList = ImmutableList.builder();
for (Symbol var : scope.getSymbols(VarSymbol.class::isInstance)) {
varsList.add((VarSymbol) var);
}
result.addAll(varsList.build().reverse());
}
break;
case FOR_LOOP:
addAllIfVariable(((ForLoopTree) curr).getInitializer(), result);
break;
case ENHANCED_FOR_LOOP:
result.add(ASTHelpers.getSymbol(((EnhancedForLoopTree) curr).getVariable()));
break;
case TRY:
TryTree tryTree = (TryTree) curr;
boolean inResources = false;
for (Tree resource : tryTree.getResources()) {
if (resource.equals(prev)) {
inResources = true;
break;
}
}
if (inResources) {
// Case 1: we're in one of the resource declarations
for (Tree resource : tryTree.getResources()) {
if (resource.equals(prev)) {
break;
}
addIfVariable(resource, result);
}
} else if (tryTree.getBlock().equals(prev)) {
// Case 2: We're in the block (not a catch or finally)
addAllIfVariable(tryTree.getResources(), result);
}
break;
case COMPILATION_UNIT:
for (ImportTree importTree : ((CompilationUnitTree) curr).getImports()) {
if (importTree.isStatic() && importTree.getQualifiedIdentifier().getKind() == Kind.MEMBER_SELECT) {
MemberSelectTree memberSelectTree = (MemberSelectTree) importTree.getQualifiedIdentifier();
Scope scope = state.getTypes().membersClosure(ASTHelpers.getType(memberSelectTree.getExpression()), /* skipInterface= */
false);
for (Symbol var : scope.getSymbols(sym -> sym instanceof VarSymbol && sym.getSimpleName().equals(memberSelectTree.getIdentifier()))) {
result.add((VarSymbol) var);
}
}
}
break;
default:
// other node types don't introduce variables
break;
}
prev = curr;
}
// TODO(eaftan): switch out collector for ImmutableSet.toImmutableSet()
return result.build().stream().filter(var -> isVisible(var, state.getPath())).collect(Collectors.toCollection(LinkedHashSet::new));
}
use of com.sun.source.tree.StatementTree in project error-prone by google.
the class AbstractExpectedExceptionChecker method buildBaseFix.
protected BaseFix buildBaseFix(VisitorState state, List<Tree> expectations, @Nullable StatementTree failure) {
String exceptionClass = "Throwable";
// additional assertions to perform on the captured exception (if any)
List<String> newAsserts = new ArrayList<>();
Builder fix = SuggestedFix.builder();
for (Tree expectation : expectations) {
MethodInvocationTree invocation = (MethodInvocationTree) ((ExpressionStatementTree) expectation).getExpression();
MethodSymbol symbol = ASTHelpers.getSymbol(invocation);
Symtab symtab = state.getSymtab();
List<? extends ExpressionTree> args = invocation.getArguments();
switch(symbol.getSimpleName().toString()) {
case "expect":
Type type = ASTHelpers.getType(getOnlyElement(invocation.getArguments()));
if (isSubtype(type, symtab.classType, state)) {
// expect(Class<?>)
exceptionClass = state.getSourceForNode(getReceiver(getOnlyElement(args)));
} else if (isSubtype(type, state.getTypeFromString("org.hamcrest.Matcher"), state)) {
Type matcherType = state.getTypes().asSuper(type, state.getSymbolFromString("org.hamcrest.Matcher"));
if (!matcherType.getTypeArguments().isEmpty()) {
Type matchType = getOnlyElement(matcherType.getTypeArguments());
if (isSubtype(matchType, symtab.throwableType, state)) {
exceptionClass = SuggestedFixes.qualifyType(state, fix, matchType);
}
}
// expect(Matcher)
fix.addStaticImport("org.hamcrest.MatcherAssert.assertThat");
newAsserts.add(String.format("assertThat(thrown, %s);", state.getSourceForNode(getOnlyElement(args))));
}
break;
case "expectCause":
ExpressionTree matcher = getOnlyElement(invocation.getArguments());
if (IS_A.matches(matcher, state)) {
fix.addStaticImport("com.google.common.truth.Truth.assertThat");
newAsserts.add(String.format("assertThat(thrown).hasCauseThat().isInstanceOf(%s);", state.getSourceForNode(getOnlyElement(((MethodInvocationTree) matcher).getArguments()))));
} else {
fix.addStaticImport("org.hamcrest.MatcherAssert.assertThat");
newAsserts.add(String.format("assertThat(thrown.getCause(), %s);", state.getSourceForNode(getOnlyElement(args))));
}
break;
case "expectMessage":
if (isSubtype(getOnlyElement(symbol.getParameters()).asType(), symtab.stringType, state)) {
// expectedMessage(String)
fix.addStaticImport("com.google.common.truth.Truth.assertThat");
newAsserts.add(String.format("assertThat(thrown).hasMessageThat().contains(%s);", state.getSourceForNode(getOnlyElement(args))));
} else {
// expectedMessage(Matcher)
fix.addStaticImport("org.hamcrest.MatcherAssert.assertThat");
newAsserts.add(String.format("assertThat(thrown.getMessage(), %s);", state.getSourceForNode(getOnlyElement(args))));
}
break;
default:
throw new AssertionError("unknown expect method: " + symbol.getSimpleName());
}
}
// remove all interactions with the ExpectedException rule
fix.replace(((JCTree) expectations.get(0)).getStartPosition(), state.getEndPosition(getLast(expectations)), "");
if (failure != null) {
fix.delete(failure);
}
return new BaseFix(fix.build(), exceptionClass, newAsserts);
}
Aggregations