/* * SonarQube Java * Copyright (C) 2012-2016 SonarSource SA * mailto:contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.java.se; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.java.cfg.CFG; import org.sonar.java.cfg.LiveVariables; import org.sonar.java.matcher.MethodMatcher; import org.sonar.java.model.ExpressionUtils; import org.sonar.java.model.JavaTree; import org.sonar.java.resolve.Flags; import org.sonar.java.resolve.JavaSymbol; import org.sonar.java.resolve.JavaType; import org.sonar.java.resolve.Types; import org.sonar.java.se.checks.ConditionAlwaysTrueOrFalseCheck; import org.sonar.java.se.checks.DivisionByZeroCheck; import org.sonar.java.se.checks.LocksNotUnlockedCheck; import org.sonar.java.se.checks.NoWayOutLoopCheck; import org.sonar.java.se.checks.NonNullSetToNullCheck; import org.sonar.java.se.checks.NullDereferenceCheck; import org.sonar.java.se.checks.OptionalGetBeforeIsPresentCheck; import org.sonar.java.se.checks.SECheck; import org.sonar.java.se.checks.UnclosedResourcesCheck; import org.sonar.java.se.constraint.Constraint; import org.sonar.java.se.constraint.ConstraintManager; import org.sonar.java.se.constraint.ObjectConstraint; import org.sonar.java.se.symbolicvalues.SymbolicValue; import org.sonar.plugins.java.api.JavaFileScanner; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.ArrayAccessExpressionTree; import org.sonar.plugins.java.api.tree.ArrayDimensionTree; import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; import org.sonar.plugins.java.api.tree.BinaryExpressionTree; import org.sonar.plugins.java.api.tree.BlockTree; import org.sonar.plugins.java.api.tree.ConditionalExpressionTree; import org.sonar.plugins.java.api.tree.DoWhileStatementTree; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.ForStatementTree; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.IfStatementTree; import org.sonar.plugins.java.api.tree.LiteralTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.NewArrayTree; import org.sonar.plugins.java.api.tree.NewClassTree; import org.sonar.plugins.java.api.tree.ReturnStatementTree; import org.sonar.plugins.java.api.tree.ThrowStatementTree; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.TypeCastTree; import org.sonar.plugins.java.api.tree.UnaryExpressionTree; import org.sonar.plugins.java.api.tree.VariableTree; import org.sonar.plugins.java.api.tree.WhileStatementTree; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; public class ExplodedGraphWalker { private static final String EQUALS_METHOD_NAME = "equals"; /** * Arbitrary number to limit symbolic execution. */ private static final int MAX_STEPS = 16_000; public static final int MAX_NESTED_BOOLEAN_STATES = 10_000; private static final Logger LOG = Loggers.get(ExplodedGraphWalker.class); private static final Set<String> THIS_SUPER = ImmutableSet.of("this", "super"); private static final boolean DEBUG_MODE_ACTIVATED = false; @VisibleForTesting static final int MAX_EXEC_PROGRAM_POINT = 2; private static final MethodMatcher SYSTEM_EXIT_MATCHER = MethodMatcher.create().typeDefinition("java.lang.System").name("exit").addParameter("int"); private static final MethodMatcher OBJECT_WAIT_MATCHER = MethodMatcher.create().typeDefinition("java.lang.Object").name("wait").withAnyParameters(); private static final MethodMatcher THREAD_SLEEP_MATCHER = MethodMatcher.create().typeDefinition("java.lang.Thread").name("sleep").withAnyParameters(); private final ConditionAlwaysTrueOrFalseCheck alwaysTrueOrFalseChecker; private MethodTree methodTree; private ExplodedGraph explodedGraph; @VisibleForTesting Deque<ExplodedGraph.Node> workList; ExplodedGraph.Node node; ExplodedGraph.ProgramPoint programPosition; ProgramState programState; private LiveVariables liveVariables; private CheckerDispatcher checkerDispatcher; private final SymbolicExecutionVisitor symbolicExecutionVisitor; @VisibleForTesting int steps; ConstraintManager constraintManager; private boolean cleanup = true; MethodBehavior methodBehavior; private Set<ExplodedGraph.Node> endOfExecutionPath; public static class ExplodedGraphTooBigException extends RuntimeException { public ExplodedGraphTooBigException(String s) { super(s); } } public static class MaximumStepsReachedException extends RuntimeException { public MaximumStepsReachedException(String s) { super(s); } public MaximumStepsReachedException(String s, TooManyNestedBooleanStatesException e) { super(s, e); } } public static class TooManyNestedBooleanStatesException extends RuntimeException { } @VisibleForTesting public ExplodedGraphWalker() { alwaysTrueOrFalseChecker = new ConditionAlwaysTrueOrFalseCheck(); List<SECheck> checks = Lists.newArrayList(alwaysTrueOrFalseChecker, new NullDereferenceCheck(), new DivisionByZeroCheck(), new UnclosedResourcesCheck(), new LocksNotUnlockedCheck(), new NonNullSetToNullCheck(), new NoWayOutLoopCheck()); this.checkerDispatcher = new CheckerDispatcher(this, checks); symbolicExecutionVisitor = new SymbolicExecutionVisitor((List) checks); } @VisibleForTesting ExplodedGraphWalker(boolean cleanup) { this(); this.cleanup = cleanup; } private ExplodedGraphWalker(ConditionAlwaysTrueOrFalseCheck alwaysTrueOrFalseChecker, List<SECheck> seChecks, SymbolicExecutionVisitor symbolicExecutionVisitor) { this.alwaysTrueOrFalseChecker = alwaysTrueOrFalseChecker; this.checkerDispatcher = new CheckerDispatcher(this, seChecks); this.symbolicExecutionVisitor = symbolicExecutionVisitor; } public ExplodedGraph getExplodedGraph() { return explodedGraph; } public MethodBehavior visitMethod(MethodTree tree, MethodBehavior methodBehavior) { BlockTree body = tree.block(); this.methodBehavior = methodBehavior; if (body != null) { execute(tree); } return this.methodBehavior; } private void execute(MethodTree tree) { CFG cfg = CFG.build(tree); checkerDispatcher.init(tree, cfg); liveVariables = LiveVariables.analyze(cfg); explodedGraph = new ExplodedGraph(); methodTree = tree; constraintManager = new ConstraintManager(); workList = new LinkedList<>(); endOfExecutionPath = new HashSet<>(); if(DEBUG_MODE_ACTIVATED) { LOG.debug("Exploring Exploded Graph for method " + tree.simpleName().name() + " at line " + ((JavaTree) tree).getLine()); } programState = ProgramState.EMPTY_STATE; steps = 0; for (ProgramState startingState : startingStates(tree, programState)) { enqueue(new ExplodedGraph.ProgramPoint(cfg.entry(), 0), startingState); } while (!workList.isEmpty()) { steps++; if (steps > MAX_STEPS) { throwMaxSteps(tree); } // LIFO: setNode(workList.removeFirst()); if (programPosition.block.successors().isEmpty()) { endOfExecutionPath.add(node); continue; } try { if (programPosition.i < programPosition.block.elements().size()) { // process block element visit(programPosition.block.elements().get(programPosition.i), programPosition.block.terminator()); } else if (programPosition.block.terminator() == null) { // process block exit, which is unconditional jump such as goto-statement or return-statement handleBlockExit(programPosition); } else if (programPosition.i == programPosition.block.elements().size()) { // process block exist, which is conditional jump such as if-statement checkerDispatcher.executeCheckPostStatement(programPosition.block.terminator()); } else { // process branch // process block exist, which is conditional jump such as if-statement checkerDispatcher.executeCheckPreStatement(programPosition.block.terminator()); handleBlockExit(programPosition); } } catch (TooManyNestedBooleanStatesException e) { throwTooManyBooleanStates(tree, e); } } handleEndOfExecutionPath(); checkerDispatcher.executeCheckEndOfExecution(); // Cleanup: workList = null; node = null; programState = null; constraintManager = null; } private void throwTooManyBooleanStates(MethodTree tree, TooManyNestedBooleanStatesException e) { interrupted(); String message = String.format("reached maximum number of %d branched states for method %s in class %s", MAX_NESTED_BOOLEAN_STATES, tree.simpleName().name(), tree.symbol().owner().name()); throw new MaximumStepsReachedException(message, e); } private void throwMaxSteps(MethodTree tree) { interrupted(); String message = String.format("reached limit of %d steps for method %s#%d in class %s", MAX_STEPS, tree.simpleName().name(), tree.simpleName().firstToken().line(), tree.symbol().owner().name()); throw new MaximumStepsReachedException(message); } private void interrupted() { handleEndOfExecutionPath(); checkerDispatcher.interruptedExecution(); } private void setNode(ExplodedGraph.Node node) { this.node = node; programPosition = this.node.programPoint; programState = this.node.programState; } private void handleEndOfExecutionPath() { ExplodedGraph.Node savedNode = node; endOfExecutionPath.forEach(n -> { setNode(n); if (!programState.exitingOnRuntimeException()) { checkerDispatcher.executeCheckEndOfExecutionPath(constraintManager); } methodBehavior.createYield(programState, node.happyPath); }); setNode(savedNode); } private Iterable<ProgramState> startingStates(MethodTree tree, ProgramState currentState) { Stream<ProgramState> stateStream = Stream.of(currentState); boolean isEqualsMethod = EQUALS_METHOD_NAME.equals(tree.simpleName().name()) && tree.parameters().size() == 1; SymbolMetadata packageMetadata = ((JavaSymbol.MethodJavaSymbol) tree.symbol()).packge().metadata(); boolean nonNullParams = packageMetadata.isAnnotatedWith("javax.annotation.ParametersAreNonnullByDefault"); boolean nullableParams = packageMetadata.isAnnotatedWith("javax.annotation.ParametersAreNullableByDefault"); for (final VariableTree variableTree : tree.parameters()) { // create final SymbolicValue sv = constraintManager.createSymbolicValue(variableTree); Symbol variableSymbol = variableTree.symbol(); methodBehavior.addParameter(variableSymbol, sv); stateStream = stateStream.map(ps -> ps.put(variableSymbol, sv)); if (isEqualsMethod || parameterCanBeNull(variableSymbol, nullableParams)) { stateStream = stateStream.flatMap((ProgramState ps) -> Stream.concat( sv.setConstraint(ps, ObjectConstraint.nullConstraint()).stream(), sv.setConstraint(ps, ObjectConstraint.notNull()).stream() )); } else if(nonNullParams) { stateStream = stateStream.flatMap(ps -> sv.setConstraint(ps, ObjectConstraint.notNull()).stream()); } } return stateStream.collect(Collectors.toList()); } private static boolean parameterCanBeNull(Symbol variableSymbol, boolean nullableParams) { SymbolMetadata metadata = variableSymbol.metadata(); return metadata.isAnnotatedWith("javax.annotation.CheckForNull") || metadata.isAnnotatedWith("javax.annotation.Nullable") || (nullableParams && !variableSymbol.type().isPrimitive()); } private void cleanUpProgramState(CFG.Block block) { if (cleanup) { programState = programState.cleanupDeadSymbols(liveVariables.getOut(block), methodBehavior.parameters()); programState = programState.cleanupConstraints(); } } private void handleBlockExit(ExplodedGraph.ProgramPoint programPosition) { CFG.Block block = programPosition.block; Tree terminator = block.terminator(); cleanUpProgramState(block); boolean exitPath = node.exitPath; if (terminator != null) { switch (terminator.kind()) { case IF_STATEMENT: handleBranch(block, cleanupCondition(((IfStatementTree) terminator).condition())); return; case CONDITIONAL_OR: case CONDITIONAL_AND: handleBranch(block, ((BinaryExpressionTree) terminator).leftOperand()); return; case CONDITIONAL_EXPRESSION: handleBranch(block, ((ConditionalExpressionTree) terminator).condition()); return; case FOR_STATEMENT: ExpressionTree condition = ((ForStatementTree) terminator).condition(); if (condition != null) { handleBranch(block, condition, false); return; } break; case WHILE_STATEMENT: ExpressionTree whileCondition = ((WhileStatementTree) terminator).condition(); handleBranch(block, cleanupCondition(whileCondition), !whileCondition.is(Tree.Kind.BOOLEAN_LITERAL)); return; case DO_STATEMENT: ExpressionTree doCondition = ((DoWhileStatementTree) terminator).condition(); handleBranch(block, cleanupCondition(doCondition), !doCondition.is(Tree.Kind.BOOLEAN_LITERAL)); return; case SYNCHRONIZED_STATEMENT: resetFieldValues(); break; case RETURN_STATEMENT: ExpressionTree returnExpression = ((ReturnStatementTree) terminator).expression(); if (returnExpression != null) { programState.storeExitValue(); } break; case THROW_STATEMENT: ProgramState.Pop unstack = programState.unstackValue(1); // we don't use the SV related to the expression programState = unstack.state.stackValue(constraintManager.createExceptionalSymbolicValue(((ThrowStatementTree) terminator).expression().symbolType())); programState.storeExitValue(); break; default: // do nothing by default. } } // unconditional jumps, for-statement, switch-statement, synchronized: if (exitPath) { if (block.exitBlock() != null) { enqueue(new ExplodedGraph.ProgramPoint(block.exitBlock(), 0), programState, true); } else { for (CFG.Block successor : block.successors()) { enqueue(new ExplodedGraph.ProgramPoint(successor, 0), programState, true); } } } else { for (CFG.Block successor : block.successors()) { if (!block.isFinallyBlock() || isDirectFlowSuccessorOf(successor, block)) { node.happyPath = terminator == null || !terminator.is(Tree.Kind.THROW_STATEMENT); enqueue(new ExplodedGraph.ProgramPoint(successor, 0), programState, successor == block.exitBlock()); } } } } private static boolean isDirectFlowSuccessorOf(CFG.Block successor, CFG.Block block) { return successor != block.exitBlock() || successor.isMethodExitBlock(); } /** * Required for accurate reporting. * If condition is && or || expression, then return its right operand. */ private static ExpressionTree cleanupCondition(ExpressionTree condition) { ExpressionTree cleanedUpCondition = ExpressionUtils.skipParentheses(condition); if (cleanedUpCondition.is(Tree.Kind.CONDITIONAL_AND, Tree.Kind.CONDITIONAL_OR)) { cleanedUpCondition = cleanupCondition(((BinaryExpressionTree) cleanedUpCondition).rightOperand()); } return cleanedUpCondition; } private void handleBranch(CFG.Block programPosition, Tree condition) { handleBranch(programPosition, condition, true); } private void handleBranch(CFG.Block programPosition, Tree condition, boolean checkPath) { Pair<List<ProgramState>, List<ProgramState>> pair = constraintManager.assumeDual(programState); ExplodedGraph.ProgramPoint falseBlockProgramPoint = new ExplodedGraph.ProgramPoint(programPosition.falseBlock(), 0); for (ProgramState state : pair.a) { ProgramState ps = state; if (condition.parent().is(Tree.Kind.CONDITIONAL_AND) && !isPartOfConditionalExpressionCondition(condition)) { // push a FALSE value on the top of the stack to enforce the choice of the branch, // as non-reachable symbolic values won't get a TRUE/FALSE constraint when assuming dual ps = state.stackValue(SymbolicValue.FALSE_LITERAL); } // enqueue false-branch, if feasible enqueue(falseBlockProgramPoint, ps, node.exitPath); if (checkPath) { alwaysTrueOrFalseChecker.evaluatedToFalse(condition, node); } } ExplodedGraph.ProgramPoint trueBlockProgramPoint = new ExplodedGraph.ProgramPoint(programPosition.trueBlock(), 0); for (ProgramState state : pair.b) { ProgramState ps = state; if (condition.parent().is(Tree.Kind.CONDITIONAL_OR) && !isPartOfConditionalExpressionCondition(condition)) { // push a TRUE value on the top of the stack to enforce the choice of the branch, // as non-reachable symbolic values won't get a TRUE/FALSE constraint when assuming dual ps = state.stackValue(SymbolicValue.TRUE_LITERAL); } // enqueue true-branch, if feasible enqueue(trueBlockProgramPoint, ps, node.exitPath); if (checkPath) { alwaysTrueOrFalseChecker.evaluatedToTrue(condition, node); } } } private static boolean isPartOfConditionalExpressionCondition(Tree tree) { Tree current; Tree parent = tree; do { current = parent; parent = parent.parent(); } while (parent.is(Tree.Kind.PARENTHESIZED_EXPRESSION, Tree.Kind.CONDITIONAL_AND, Tree.Kind.CONDITIONAL_OR)); return parent.is(Tree.Kind.CONDITIONAL_EXPRESSION) && current.equals(((ConditionalExpressionTree) parent).condition()); } private void visit(Tree tree, @Nullable Tree terminator) { if (!checkerDispatcher.executeCheckPreStatement(tree)) { // Some of the check pre statement sink the execution on this node. return; } switch (tree.kind()) { case METHOD_INVOCATION: MethodInvocationTree mit = (MethodInvocationTree) tree; if(SYSTEM_EXIT_MATCHER.matches(mit)) { //System exit is a sink of execution return; } executeMethodInvocation(mit); return; case LABELED_STATEMENT: case SWITCH_STATEMENT: case EXPRESSION_STATEMENT: case PARENTHESIZED_EXPRESSION: throw new IllegalStateException("Cannot appear in CFG: " + tree.kind().name()); case VARIABLE: executeVariable((VariableTree) tree, terminator); break; case TYPE_CAST: executeTypeCast((TypeCastTree) tree); break; case ASSIGNMENT: case MULTIPLY_ASSIGNMENT: case DIVIDE_ASSIGNMENT: case REMAINDER_ASSIGNMENT: case PLUS_ASSIGNMENT: case MINUS_ASSIGNMENT: case LEFT_SHIFT_ASSIGNMENT: case RIGHT_SHIFT_ASSIGNMENT: case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: executeAssignement((AssignmentExpressionTree) tree); break; case AND_ASSIGNMENT: case XOR_ASSIGNMENT: case OR_ASSIGNMENT: executeLogicalAssignment((AssignmentExpressionTree) tree); break; case ARRAY_ACCESS_EXPRESSION: executeArrayAccessExpression((ArrayAccessExpressionTree) tree); break; case NEW_ARRAY: executeNewArray((NewArrayTree) tree); break; case NEW_CLASS: executeNewClass((NewClassTree) tree); break; case MULTIPLY: case DIVIDE: case REMAINDER: case PLUS: case MINUS: case LEFT_SHIFT: case RIGHT_SHIFT: case UNSIGNED_RIGHT_SHIFT: case AND: case XOR: case OR: case GREATER_THAN: case GREATER_THAN_OR_EQUAL_TO: case LESS_THAN: case LESS_THAN_OR_EQUAL_TO: case EQUAL_TO: case NOT_EQUAL_TO: executeBinaryExpression(tree); break; case POSTFIX_INCREMENT: case POSTFIX_DECREMENT: case PREFIX_INCREMENT: case PREFIX_DECREMENT: case UNARY_MINUS: case UNARY_PLUS: case BITWISE_COMPLEMENT: case LOGICAL_COMPLEMENT: case INSTANCE_OF: executeUnaryExpression(tree); break; case IDENTIFIER: executeIdentifier((IdentifierTree) tree); break; case MEMBER_SELECT: executeMemberSelect((MemberSelectExpressionTree) tree); break; case INT_LITERAL: case LONG_LITERAL: case FLOAT_LITERAL: case DOUBLE_LITERAL: case CHAR_LITERAL: case STRING_LITERAL: SymbolicValue val = constraintManager.createSymbolicValue(tree); programState = programState.stackValue(val); programState = programState.addConstraint(val, ObjectConstraint.notNull()); break; case BOOLEAN_LITERAL: boolean value = Boolean.parseBoolean(((LiteralTree) tree).value()); programState = programState.stackValue(value ? SymbolicValue.TRUE_LITERAL : SymbolicValue.FALSE_LITERAL); break; case NULL_LITERAL: programState = programState.stackValue(SymbolicValue.NULL_LITERAL); break; case LAMBDA_EXPRESSION: case METHOD_REFERENCE: programState = programState.stackValue(constraintManager.createSymbolicValue(tree)); break; default: } checkerDispatcher.executeCheckPostStatement(tree); clearStack(tree); } private void executeMethodInvocation(MethodInvocationTree mit) { setSymbolicValueOnFields(mit); // unstack arguments and method identifier ProgramState.Pop unstack = programState.unstackValue(mit.arguments().size() + 1); logState(mit); programState = unstack.state; // get method behavior for method with known declaration (ie: within the same file) MethodBehavior methodInvokedBehavior = null; Symbol methodSymbol = mit.symbol(); Tree declaration = methodSymbol.declaration(); if(declaration != null) { methodInvokedBehavior = symbolicExecutionVisitor.execute((MethodTree) declaration); } // Enqueue additional exceptional paths corresponding to unchecked exceptions, for instance OutOfMemoryError enqueueUncheckedExceptionalPaths(); final SymbolicValue resultValue = constraintManager.createMethodSymbolicValue(mit, unstack.values); if (methodInvokedBehavior != null && methodInvokedBehavior.isComplete() && methodCanNotBeOverriden(methodSymbol)) { List<SymbolicValue> invocationArguments = invocationArguments(unstack.values); List<Type> invocationTypes = mit.arguments().stream().map(ExpressionTree::symbolType).collect(Collectors.toList()); Map<Type, SymbolicValue.ExceptionalSymbolicValue> thrownExceptionsByExceptionType = new HashMap<>(); // Enqueue exceptional paths from exceptional yields methodInvokedBehavior.exceptionalPathYields() .flatMap(yield -> yield.statesAfterInvocation( invocationArguments, invocationTypes, programState, () -> thrownExceptionsByExceptionType.computeIfAbsent(yield.exceptionType, constraintManager::createExceptionalSymbolicValue))) .forEach(ps -> enqueueExceptionalPaths(ps.clearStack(), (SymbolicValue.ExceptionalSymbolicValue) ps.peekValue())); // Enqueue happy paths methodInvokedBehavior.happyPathYields() .flatMap(yield -> yield.statesAfterInvocation(invocationArguments, invocationTypes, programState, () -> resultValue)) .map(psYield -> handleSpecialMethods(psYield, mit)) .forEach(psYield -> { checkerDispatcher.syntaxNode = mit; checkerDispatcher.addTransition(psYield); clearStack(mit); }); } else { // Enqueue exceptional paths from thrown exceptions enqueueThrownExceptionalPaths(methodSymbol); // Enqueue happy paths programState = handleSpecialMethods(programState.stackValue(resultValue), mit); checkerDispatcher.executeCheckPostStatement(mit); clearStack(mit); } } private ProgramState handleSpecialMethods(ProgramState ps, MethodInvocationTree mit) { if (isNonNullMethod(mit.symbol())) { return ps.addConstraint(ps.peekValue(), ObjectConstraint.notNull()); } else if (OBJECT_WAIT_MATCHER.matches(mit)) { return ps.resetFieldValues(constraintManager); } return ps; } private void enqueueThrownExceptionalPaths(Symbol symbol) { if (!symbol.isMethodSymbol()) { // do nothing for unknown methods return; } ProgramState ps = programState.clearStack(); ((Symbol.MethodSymbol) symbol).thrownTypes().stream() .map(constraintManager::createExceptionalSymbolicValue) .forEach(exceptionSV -> enqueueExceptionalPaths(ps, exceptionSV)); } private void enqueueUncheckedExceptionalPaths() { enqueueExceptionalPaths(programState.clearStack(), constraintManager.createExceptionalSymbolicValue(null)); } private void enqueueExceptionalPaths(ProgramState ps, SymbolicValue.ExceptionalSymbolicValue exceptionSV) { Set<CFG.Block> exceptionBlocks = node.programPoint.block.exceptions(); List<CFG.Block> catchBlocks = exceptionBlocks.stream().filter(CFG.Block.IS_CATCH_BLOCK).collect(Collectors.toList()); // only consider the first match, as order of catch block is important Optional<CFG.Block> firstMatchingCatchBlock = catchBlocks.stream() .filter(b -> isCaughtByBlock(exceptionSV.exceptionType(), b)) .sorted((b1, b2) -> Integer.compare(b2.id(), b1.id())) .findFirst(); if (firstMatchingCatchBlock.isPresent()) { enqueue(new ExplodedGraph.ProgramPoint(firstMatchingCatchBlock.get(), 0), ps); return; } // branch to any unchecked exception catch catchBlocks.stream() .filter(ExplodedGraphWalker::isCatchingUncheckedException) .forEach(b -> enqueue(new ExplodedGraph.ProgramPoint(b, 0), ps)); // store the exception as exit value in case of method exit in next block ProgramState newPs = ps.clearStack().stackValue(exceptionSV); newPs.storeExitValue(); // use other exceptional blocks, i.e. finally block and exit blocks exceptionBlocks.stream() .filter(CFG.Block.IS_CATCH_BLOCK.negate()) .forEach(b -> enqueue(new ExplodedGraph.ProgramPoint(b, 0), newPs, true)); // explicitly add the exception if next block is method exit node.programPoint.block.successors().stream() .filter(CFG.Block::isMethodExitBlock) .forEach(b -> enqueue(new ExplodedGraph.ProgramPoint(b, 0), newPs, true)); } private static boolean isCaughtByBlock(@Nullable Type thrownType, CFG.Block catchBlock) { if (thrownType != null) { Type caughtType = ((VariableTree) catchBlock.elements().get(0)).symbol().type(); return thrownType.isSubtypeOf(caughtType); } return false; } private static boolean isCatchingUncheckedException(CFG.Block catchBlock) { Type caughtType = ((VariableTree) catchBlock.elements().get(0)).symbol().type(); return caughtType.isSubtypeOf("java.lang.RuntimeException") || caughtType.isSubtypeOf("java.lang.Error") || caughtType.is("java.lang.Exception") || caughtType.is("java.lang.Throwable"); } private static boolean methodCanNotBeOverriden(Symbol methodSymbol) { if ((((JavaSymbol.MethodJavaSymbol) methodSymbol).flags() & Flags.NATIVE) != 0) { return false; } return !methodSymbol.isAbstract() && (methodSymbol.isPrivate() || methodSymbol.isFinal() || methodSymbol.isStatic() || methodSymbol.owner().isFinal()); } private static List<SymbolicValue> invocationArguments(List<SymbolicValue> values) { List<SymbolicValue> parameterValues = new ArrayList<>(values); parameterValues.remove(values.size() - 1); Collections.reverse(parameterValues); return parameterValues; } private static boolean isNonNullMethod(Symbol symbol) { return !symbol.isUnknown() && symbol.metadata().isAnnotatedWith("javax.annotation.Nonnull"); } private void executeVariable(VariableTree variableTree, @Nullable Tree terminator) { ExpressionTree initializer = variableTree.initializer(); if (initializer == null) { SymbolicValue sv = null; if (terminator != null && terminator.is(Tree.Kind.FOR_EACH_STATEMENT)) { sv = constraintManager.createSymbolicValue(variableTree); } else if (variableTree.type().symbolType().is("boolean")) { sv = SymbolicValue.FALSE_LITERAL; } else if (variableTree.parent().is(Tree.Kind.CATCH)) { sv = constraintManager.createSymbolicValue(variableTree); programState = programState.addConstraint(sv, ObjectConstraint.notNull()); } else if (!variableTree.type().symbolType().isPrimitive()) { sv = SymbolicValue.NULL_LITERAL; } if (sv != null) { programState = programState.put(variableTree.symbol(), sv); } } else { ProgramState.Pop unstack = programState.unstackValue(1); programState = unstack.state; programState = programState.put(variableTree.symbol(), unstack.values.get(0)); } } private void executeTypeCast(TypeCastTree typeCast) { Type type = typeCast.type().symbolType(); if (type.isPrimitive()) { JavaType expType = (JavaType) typeCast.expression().symbolType(); // create SV to consume factory if any SymbolicValue castSV = constraintManager.createSymbolicValue(typeCast); // if exp type is a primitive and subtype of cast type, we can reuse the same symbolic value if (!expType.isPrimitive() || !new Types().isSubtype(expType, (JavaType) type)) { ProgramState.Pop unstack = programState.unstackValue(1); programState = unstack.state; programState = programState.stackValue(castSV); } } } private void executeAssignement(AssignmentExpressionTree tree) { ExpressionTree variable = tree.variable(); ProgramState.Pop unstack; SymbolicValue value; if (ExpressionUtils.isSimpleAssignment(tree)) { unstack = programState.unstackValue(1); value = unstack.values.get(0); } else { unstack = programState.unstackValue(2); value = constraintManager.createSymbolicValue(tree); } programState = unstack.state; programState = programState.stackValue(value); if (variable.is(Tree.Kind.IDENTIFIER)) { // only local variables or fields are added to table of values // FIXME SONARJAVA-1776 fields accessing using "this." should be handled programState = programState.put(((IdentifierTree) variable).symbol(), value); } } private void executeLogicalAssignment(AssignmentExpressionTree tree) { ExpressionTree variable = tree.variable(); if (variable.is(Tree.Kind.IDENTIFIER)) { ProgramState.Pop unstack = programState.unstackValue(2); SymbolicValue assignedTo = unstack.values.get(1); SymbolicValue value = unstack.values.get(0); programState = unstack.state; SymbolicValue symbolicValue = constraintManager.createSymbolicValue(tree); symbolicValue.computedFrom(ImmutableList.of(assignedTo, value)); programState = programState.stackValue(symbolicValue); programState = programState.put(((IdentifierTree) variable).symbol(), symbolicValue); } } private void executeArrayAccessExpression(ArrayAccessExpressionTree tree) { // unstack expression and dimension ProgramState.Pop unstack = programState.unstackValue(2); programState = unstack.state; programState = programState.stackValue(constraintManager.createSymbolicValue(tree)); } private void executeNewArray(NewArrayTree newArrayTree) { int numberDimensions = (int) newArrayTree.dimensions().stream().map(ArrayDimensionTree::expression).filter(Objects::nonNull).count(); programState = programState.unstackValue(numberDimensions).state; programState = programState.unstackValue(newArrayTree.initializers().size()).state; SymbolicValue svNewArray = constraintManager.createSymbolicValue(newArrayTree); programState = programState.stackValue(svNewArray); programState = svNewArray.setSingleConstraint(programState, ObjectConstraint.notNull()); } private void executeNewClass(NewClassTree tree) { NewClassTree newClassTree = tree; programState = programState.unstackValue(newClassTree.arguments().size()).state; // Enqueue exceptional paths node.programPoint.block.exceptions().forEach(b -> enqueue(new ExplodedGraph.ProgramPoint(b, 0), programState, !b.isCatchBlock())); SymbolicValue svNewClass = constraintManager.createSymbolicValue(newClassTree); programState = programState.stackValue(svNewClass); programState = svNewClass.setSingleConstraint(programState, ObjectConstraint.notNull()); } private void executeBinaryExpression(Tree tree) { // Consume two and produce one SV. ProgramState.Pop unstackBinary = programState.unstackValue(2); programState = unstackBinary.state; SymbolicValue symbolicValue = constraintManager.createSymbolicValue(tree); symbolicValue.computedFrom(unstackBinary.values); if(tree.is(Tree.Kind.PLUS)) { BinaryExpressionTree bt = (BinaryExpressionTree) tree; if (bt.leftOperand().symbolType().is("java.lang.String")) { Constraint leftConstraint = programState.getConstraint(unstackBinary.values.get(1)); if (leftConstraint != null && !leftConstraint.isNull()) { List<ProgramState> programStates = symbolicValue.setConstraint(programState, ObjectConstraint.notNull()); Preconditions.checkState(programStates.size() == 1); programState = programStates.get(0); } } else if(bt.rightOperand().symbolType().is("java.lang.String")) { Constraint rightConstraint = programState.getConstraint(unstackBinary.values.get(0)); if (rightConstraint != null && !rightConstraint.isNull()) { List<ProgramState> programStates = symbolicValue.setConstraint(programState, ObjectConstraint.notNull()); Preconditions.checkState(programStates.size() == 1); programState = programStates.get(0); } } } programState = programState.stackValue(symbolicValue); } private void executeUnaryExpression(Tree tree) { // consume one and produce one ProgramState.Pop unstackUnary = programState.unstackValue(1); programState = unstackUnary.state; SymbolicValue unarySymbolicValue = constraintManager.createSymbolicValue(tree); unarySymbolicValue.computedFrom(unstackUnary.values); if (tree.is(Tree.Kind.POSTFIX_DECREMENT, Tree.Kind.POSTFIX_INCREMENT)) { programState = programState.stackValue(unstackUnary.values.get(0)); } else { programState = programState.stackValue(unarySymbolicValue); } if (tree.is(Tree.Kind.POSTFIX_DECREMENT, Tree.Kind.POSTFIX_INCREMENT, Tree.Kind.PREFIX_DECREMENT, Tree.Kind.PREFIX_INCREMENT) && ((UnaryExpressionTree) tree).expression().is(Tree.Kind.IDENTIFIER)) { programState = programState.put(((IdentifierTree) ((UnaryExpressionTree) tree).expression()).symbol(), unarySymbolicValue); } } private void executeIdentifier(IdentifierTree tree) { Symbol symbol = tree.symbol(); SymbolicValue value = programState.getValue(symbol); if (value == null) { value = constraintManager.createSymbolicValue(tree); programState = programState.stackValue(value); learnIdentifierNullConstraints(tree, value); } else { programState = programState.stackValue(value); } programState = programState.put(symbol, value); } private void learnIdentifierNullConstraints(IdentifierTree tree, SymbolicValue sv) { Tree declaration = tree.symbol().declaration(); if (!isFinalField(tree.symbol()) || declaration == null) { return; } ExpressionTree initializer = ((VariableTree) declaration).initializer(); if (initializer == null) { return; } // only check final field with an initializer if (initializer.is(Tree.Kind.NULL_LITERAL)) { programState = programState.addConstraint(sv, ObjectConstraint.nullConstraint()); } else if (initializer.is(Tree.Kind.NEW_CLASS) || initializer.is(Tree.Kind.NEW_ARRAY)) { programState = programState.addConstraint(sv, ObjectConstraint.notNull()); } } private static boolean isFinalField(Symbol symbol) { return symbol.isVariableSymbol() && symbol.isFinal() && symbol.owner().isTypeSymbol(); } private void executeMemberSelect(MemberSelectExpressionTree mse) { if (!"class".equals(mse.identifier().name())) { ProgramState.Pop unstackMSE = programState.unstackValue(1); programState = unstackMSE.state; } SymbolicValue mseValue = constraintManager.createSymbolicValue(mse); programState = programState.stackValue(mseValue); } public void clearStack(Tree tree) { if (tree.parent().is(Tree.Kind.EXPRESSION_STATEMENT)) { programState = programState.clearStack(); } } private void setSymbolicValueOnFields(MethodInvocationTree tree) { if (isLocalMethodInvocation(tree) || THREAD_SLEEP_MATCHER.matches(tree)) { resetFieldValues(); } } private static boolean isLocalMethodInvocation(MethodInvocationTree tree) { ExpressionTree methodSelect = tree.methodSelect(); if (methodSelect.is(Tree.Kind.IDENTIFIER)) { return true; } else if (methodSelect.is(Tree.Kind.MEMBER_SELECT)) { MemberSelectExpressionTree memberSelectExpression = (MemberSelectExpressionTree) methodSelect; ExpressionTree target = memberSelectExpression.expression(); if (target.is(Tree.Kind.IDENTIFIER)) { IdentifierTree identifier = (IdentifierTree) target; return THIS_SUPER.contains(identifier.name()); } } return false; } private void resetFieldValues() { programState = programState.resetFieldValues(constraintManager); } private void logState(MethodInvocationTree mit) { if (mit.methodSelect().is(Tree.Kind.IDENTIFIER) && "printState".equals(((IdentifierTree) mit.methodSelect()).name())) { debugPrint(((JavaTree) mit).getLine(), node); } } private static void debugPrint(Object... toPrint) { if (DEBUG_MODE_ACTIVATED) { LOG.error(Joiner.on(" - ").join(toPrint)); } } public void enqueue(ExplodedGraph.ProgramPoint programPoint, ProgramState programState) { enqueue(programPoint, programState, false); } public void enqueue(ExplodedGraph.ProgramPoint newProgramPoint, ProgramState programState, boolean exitPath) { ExplodedGraph.ProgramPoint programPoint = newProgramPoint; int nbOfExecution = programState.numberOfTimeVisited(programPoint); if (nbOfExecution > MAX_EXEC_PROGRAM_POINT) { if (isRestartingForEachLoop(programPoint)) { // reached the max number of visit by program point, so take the false branch with current program state programPoint = new ExplodedGraph.ProgramPoint(programPoint.block.falseBlock(), 0); } else { debugPrint(programPoint); return; } } checkExplodedGraphTooBig(programState); ProgramState ps = programState.visitedPoint(programPoint, nbOfExecution + 1); ps.lastEvaluated = programState.getLastEvaluated(); ExplodedGraph.Node cachedNode = explodedGraph.getNode(programPoint, ps); if (!cachedNode.isNew && exitPath == cachedNode.exitPath) { // has been enqueued earlier cachedNode.addParent(node); return; } cachedNode.exitPath = exitPath; if(node != null) { cachedNode.happyPath = node.happyPath; } cachedNode.setParent(node); workList.addFirst(cachedNode); } private static boolean isRestartingForEachLoop(ExplodedGraph.ProgramPoint programPoint) { Tree terminator = programPoint.block.terminator(); return terminator != null && terminator.is(Tree.Kind.FOR_EACH_STATEMENT); } private void checkExplodedGraphTooBig(ProgramState programState) { // Arbitrary formula to avoid out of memory errors if (steps + workList.size() > MAX_STEPS / 2 && programState.constraintsSize() > 75) { throw new ExplodedGraphTooBigException("Program state constraints are too big : stopping Symbolic Execution for method " + methodTree.simpleName().name() + " in class " + methodTree.symbol().owner().name()); } } /** * This class ensures that the SE checks are placed in the correct order for the ExplodedGraphWalker * In addition, checks that are needed for a correct ExplodedGraphWalker processing are provided in all cases. * */ public static class ExplodedGraphWalkerFactory { private final ConditionAlwaysTrueOrFalseCheck alwaysTrueOrFalseChecker; @VisibleForTesting final List<SECheck> seChecks = new ArrayList<>(); public ExplodedGraphWalkerFactory(List<JavaFileScanner> scanners) { List<SECheck> checks = new ArrayList<>(); for (JavaFileScanner scanner : scanners) { if (scanner instanceof SECheck) { checks.add((SECheck) scanner); } } alwaysTrueOrFalseChecker = removeOrDefault(checks, new ConditionAlwaysTrueOrFalseCheck()); // This order of the mandatory SE checks is required by the ExplodedGraphWalker seChecks.add(alwaysTrueOrFalseChecker); seChecks.add(removeOrDefault(checks, new NullDereferenceCheck())); seChecks.add(removeOrDefault(checks, new DivisionByZeroCheck())); seChecks.add(removeOrDefault(checks, new UnclosedResourcesCheck())); seChecks.add(removeOrDefault(checks, new LocksNotUnlockedCheck())); seChecks.add(removeOrDefault(checks, new NonNullSetToNullCheck())); seChecks.add(removeOrDefault(checks, new NoWayOutLoopCheck())); seChecks.add(removeOrDefault(checks, new OptionalGetBeforeIsPresentCheck())); seChecks.addAll(checks); } public ExplodedGraphWalker createWalker(SymbolicExecutionVisitor symbolicExecutionVisitor) { return new ExplodedGraphWalker(alwaysTrueOrFalseChecker, seChecks, symbolicExecutionVisitor); } @SuppressWarnings("unchecked") private static <T extends SECheck> T removeOrDefault(List<SECheck> checks, T defaultInstance) { Iterator<SECheck> iterator = checks.iterator(); while (iterator.hasNext()) { SECheck check = iterator.next(); if (check.getClass().equals(defaultInstance.getClass())) { iterator.remove(); return (T) check; } } return defaultInstance; } } }