Java tutorial
/* * CPAchecker is a tool for configurable software verification. * This file is part of CPAchecker. * * Copyright (C) 2007-2014 Dirk Beyer * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * CPAchecker web page: * http://cpachecker.sosy-lab.org */ package org.sosy_lab.cpachecker.cfa.postprocessing.function; import static com.google.common.collect.FluentIterable.from; import static org.sosy_lab.cpachecker.util.CFAUtils.leavingEdges; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import org.sosy_lab.common.Pair; import org.sosy_lab.common.configuration.Configuration; import org.sosy_lab.common.configuration.InvalidConfigurationException; import org.sosy_lab.common.configuration.Option; import org.sosy_lab.common.configuration.Options; import org.sosy_lab.common.log.LogManager; import org.sosy_lab.cpachecker.cfa.CFACreationUtils; import org.sosy_lab.cpachecker.cfa.MutableCFA; import org.sosy_lab.cpachecker.cfa.ast.FileLocation; import org.sosy_lab.cpachecker.cfa.ast.ADeclaration; import org.sosy_lab.cpachecker.cfa.ast.AStatement; import org.sosy_lab.cpachecker.cfa.ast.c.CBinaryExpression; import org.sosy_lab.cpachecker.cfa.ast.c.CBinaryExpression.BinaryOperator; import org.sosy_lab.cpachecker.cfa.ast.c.CBinaryExpressionBuilder; import org.sosy_lab.cpachecker.cfa.ast.c.CExpression; import org.sosy_lab.cpachecker.cfa.ast.c.CFunctionCall; import org.sosy_lab.cpachecker.cfa.ast.c.CFunctionCallAssignmentStatement; import org.sosy_lab.cpachecker.cfa.ast.c.CFunctionCallExpression; import org.sosy_lab.cpachecker.cfa.ast.c.CFunctionCallStatement; import org.sosy_lab.cpachecker.cfa.ast.c.CFunctionDeclaration; import org.sosy_lab.cpachecker.cfa.ast.c.CIdExpression; import org.sosy_lab.cpachecker.cfa.ast.c.CPointerExpression; import org.sosy_lab.cpachecker.cfa.ast.c.CSimpleDeclaration; import org.sosy_lab.cpachecker.cfa.ast.c.CUnaryExpression; import org.sosy_lab.cpachecker.cfa.ast.c.CVariableDeclaration; import org.sosy_lab.cpachecker.cfa.model.BlankEdge; import org.sosy_lab.cpachecker.cfa.model.CFAEdge; import org.sosy_lab.cpachecker.cfa.model.CFANode; import org.sosy_lab.cpachecker.cfa.model.FunctionEntryNode; import org.sosy_lab.cpachecker.cfa.model.c.CAssumeEdge; import org.sosy_lab.cpachecker.cfa.model.c.CFunctionEntryNode; import org.sosy_lab.cpachecker.cfa.model.c.CStatementEdge; import org.sosy_lab.cpachecker.cfa.types.MachineModel; import org.sosy_lab.cpachecker.cfa.types.c.CFunctionType; import org.sosy_lab.cpachecker.cfa.types.c.CPointerType; import org.sosy_lab.cpachecker.cfa.types.c.CType; import org.sosy_lab.cpachecker.cfa.types.c.CVoidType; import org.sosy_lab.cpachecker.util.CFATraversal; import org.sosy_lab.cpachecker.util.CFAUtils; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; /** * This class is responsible for replacing calls via function pointers like (*fp)() * with code similar to the following: * if (fp == &f) * f(); * else if (fp == &g) * f(); * else * (*fp)(); * * The set of candidate functions used is configurable. * No actual call edges to the other functions are introduced. * The inserted function call statements look just like regular functions call statements. * The edge in the "else" branch is optional and configurable. */ @Options public class CFunctionPointerResolver { @Option(secure = true, name = "analysis.functionPointerEdgesForUnknownPointer", description = "Create edge for skipping a function pointer call if its value is unknown.") private boolean createUndefinedFunctionCall = true; private enum FunctionSet { // The items here need to be declared in the order they should be used when checking function. ALL, //all defined functions considered (Warning: some CPAs require at least EQ_PARAM_SIZES) USED_IN_CODE, //includes only functions which address is taken in the code EQ_PARAM_COUNT, //all functions with matching number of parameters considered EQ_PARAM_SIZES, //all functions with parameters with matching sizes EQ_PARAM_TYPES, //all functions with matching number and types of parameters considered (implies EQ_PARAM_SIZES) RETURN_VALUE, //void functions are not considered for assignments } @Option(secure = true, name = "analysis.functionPointerTargets", description = "potential targets for call edges created for function pointer calls") private Set<FunctionSet> functionSets = ImmutableSet.of(FunctionSet.USED_IN_CODE, FunctionSet.EQ_PARAM_SIZES, FunctionSet.RETURN_VALUE); private final Collection<FunctionEntryNode> candidateFunctions; private final Predicate<Pair<CFunctionCall, CFunctionType>> matchingFunctionCall; private final MutableCFA cfa; private final LogManager logger; public CFunctionPointerResolver(MutableCFA pCfa, List<Pair<ADeclaration, String>> pGlobalVars, Configuration config, LogManager pLogger) throws InvalidConfigurationException { cfa = pCfa; logger = pLogger; config.inject(this); matchingFunctionCall = getFunctionSetPredicate(functionSets); if (functionSets.contains(FunctionSet.USED_IN_CODE)) { CReferencedFunctionsCollector varCollector = new CReferencedFunctionsCollector(); for (CFANode node : cfa.getAllNodes()) { for (CFAEdge edge : leavingEdges(node)) { varCollector.visitEdge(edge); } } for (Pair<ADeclaration, String> decl : pGlobalVars) { if (decl.getFirst() instanceof CVariableDeclaration) { CVariableDeclaration varDecl = (CVariableDeclaration) decl.getFirst(); varCollector.visitDeclaration(varDecl); } } Set<String> addressedFunctions = varCollector.getCollectedFunctions(); candidateFunctions = from(Sets.intersection(addressedFunctions, cfa.getAllFunctionNames())) .transform(Functions.forMap(cfa.getAllFunctions())).toList(); if (logger.wouldBeLogged(Level.ALL)) { logger.log(Level.ALL, "Possible target functions of function pointers:\n", Joiner.on('\n').join(candidateFunctions)); } } else { candidateFunctions = cfa.getAllFunctionHeads(); } } private Predicate<Pair<CFunctionCall, CFunctionType>> getFunctionSetPredicate( Collection<FunctionSet> pFunctionSets) { // note that this set is sorted according to the declaration order of the enum EnumSet<FunctionSet> functionSets = EnumSet.copyOf(pFunctionSets); if (functionSets.contains(FunctionSet.EQ_PARAM_TYPES) || functionSets.contains(FunctionSet.EQ_PARAM_SIZES)) { functionSets.add(FunctionSet.EQ_PARAM_COUNT); // TYPES and SIZES need COUNT checked first } List<Predicate<Pair<CFunctionCall, CFunctionType>>> predicates = new ArrayList<>(); for (FunctionSet functionSet : functionSets) { switch (functionSet) { case ALL: // do nothing break; case EQ_PARAM_COUNT: predicates.add(new Predicate<Pair<CFunctionCall, CFunctionType>>() { @Override public boolean apply(Pair<CFunctionCall, CFunctionType> pInput) { boolean result = checkParamSizes(pInput.getFirst().getFunctionCallExpression(), pInput.getSecond()); if (!result) { logger.log(Level.FINEST, "Function call", pInput.getFirst().toASTString(), "does not match function", pInput.getSecond(), "because of number of parameters."); } return result; } }); break; case EQ_PARAM_SIZES: predicates.add(new Predicate<Pair<CFunctionCall, CFunctionType>>() { @Override public boolean apply(Pair<CFunctionCall, CFunctionType> pInput) { return checkReturnAndParamSizes(pInput.getFirst().getFunctionCallExpression(), pInput.getSecond()); } }); break; case EQ_PARAM_TYPES: predicates.add(new Predicate<Pair<CFunctionCall, CFunctionType>>() { @Override public boolean apply(Pair<CFunctionCall, CFunctionType> pInput) { return checkReturnAndParamTypes(pInput.getFirst().getFunctionCallExpression(), pInput.getSecond()); } }); break; case RETURN_VALUE: predicates.add(new Predicate<Pair<CFunctionCall, CFunctionType>>() { @Override public boolean apply(Pair<CFunctionCall, CFunctionType> pInput) { return checkReturnValue(pInput.getFirst(), pInput.getSecond()); } }); break; case USED_IN_CODE: // Not necessary, only matching functions are in the // candidateFunctions set break; default: throw new AssertionError(); } } return Predicates.and(predicates); } /** * This method traverses the whole CFA, * potentially replacing function pointer calls with regular function calls. */ public void resolveFunctionPointers() { // 1.Step: get all function calls final FunctionPointerCallCollector visitor = new FunctionPointerCallCollector(); for (FunctionEntryNode functionStartNode : cfa.getAllFunctionHeads()) { CFATraversal.dfs().traverseOnce(functionStartNode, visitor); } // 2.Step: replace functionCalls with functioncall- and return-edges // This loop replaces function pointer calls inside the given function with regular function calls. for (final CStatementEdge edge : visitor.functionPointerCalls) { replaceFunctionPointerCall((CFunctionCall) edge.getStatement(), edge); } } /** This Visitor collects all functioncalls for functionPointers. * It should visit the CFA of each functions before creating super-edges (functioncall- and return-edges). */ private class FunctionPointerCallCollector extends CFATraversal.DefaultCFAVisitor { final List<CStatementEdge> functionPointerCalls = new ArrayList<>(); @Override public CFATraversal.TraversalProcess visitEdge(final CFAEdge pEdge) { if (pEdge instanceof CStatementEdge) { final CStatementEdge edge = (CStatementEdge) pEdge; final AStatement stmt = edge.getStatement(); if (stmt instanceof CFunctionCall && isFunctionPointerCall((CFunctionCall) stmt)) { functionPointerCalls.add(edge); } } return CFATraversal.TraversalProcess.CONTINUE; } } private boolean isFunctionPointerCall(CFunctionCall call) { CFunctionCallExpression callExpr = call.getFunctionCallExpression(); if (callExpr.getDeclaration() != null) { // "f()" where "f" is a declared function return false; } CExpression nameExpr = callExpr.getFunctionNameExpression(); if (nameExpr instanceof CIdExpression && ((CIdExpression) nameExpr).getDeclaration() == null) { // "f()" where "f" is an undefined identifier // Someone calls an undeclared function. return false; } // Either "exp()" where "exp" is a more complicated expression, // or "f()" where "f" is a variable. return true; } /** * This method replaces a single function pointer call with a function call series. */ private void replaceFunctionPointerCall(CFunctionCall functionCall, CStatementEdge statement) { CFunctionCallExpression fExp = functionCall.getFunctionCallExpression(); logger.log(Level.FINEST, "Function pointer call", fExp); CExpression nameExp = fExp.getFunctionNameExpression(); Collection<CFunctionEntryNode> funcs = getFunctionSet(functionCall); if (funcs.isEmpty()) { // no possible targets, we leave the CFA unchanged and print a warning logger.logf(Level.WARNING, "%s: Function pointer %s with type %s is called," + " but no possible target functions were found.", statement.getFileLocation(), nameExp.toASTString(), nameExp.getExpressionType().toASTString("*")); return; } logger.log(Level.FINEST, "Inserting edges for the function pointer", nameExp.toASTString(), "with type", nameExp.getExpressionType().toASTString("*"), "to the functions", from(funcs).transform(new Function<CFANode, String>() { @Override public String apply(CFANode pInput) { return pInput.getFunctionName(); } })); FileLocation fileLocation = statement.getFileLocation(); CFANode start = statement.getPredecessor(); CFANode end = statement.getSuccessor(); // delete old edge CFACreationUtils.removeEdgeFromNodes(statement); if (nameExp instanceof CPointerExpression) { CExpression operand = ((CPointerExpression) nameExp).getOperand(); CType operandType = operand.getExpressionType().getCanonicalType(); if (operandType instanceof CPointerType && ((CPointerType) operandType).getType() instanceof CFunctionType) { // *fp is the same as fp nameExp = operand; } } CFANode rootNode = start; for (FunctionEntryNode fNode : funcs) { CFANode thenNode = newCFANode(start.getFunctionName()); CFANode elseNode = newCFANode(start.getFunctionName()); CIdExpression func = new CIdExpression(nameExp.getFileLocation(), (CType) fNode.getFunctionDefinition().getType(), fNode.getFunctionName(), (CSimpleDeclaration) fNode.getFunctionDefinition()); CUnaryExpression amper = new CUnaryExpression(nameExp.getFileLocation(), func.getExpressionType(), func, CUnaryExpression.UnaryOperator.AMPER); final CBinaryExpressionBuilder binExprBuilder = new CBinaryExpressionBuilder(cfa.getMachineModel(), logger); CBinaryExpression condition = binExprBuilder.buildBinaryExpressionUnchecked(nameExp, amper, BinaryOperator.EQUALS); addConditionEdges(condition, rootNode, thenNode, elseNode, fileLocation); CFANode retNode = newCFANode(start.getFunctionName()); //create special summary edge //thenNode-->retNode String pRawStatement = "pointer call(" + fNode.getFunctionName() + ") " + statement.getRawStatement(); //replace function call by pointer expression with regular call by name (in functionCall and edge.getStatement()) CFunctionCall regularCall = createRegularCall(functionCall, fNode); createCallEdge(fileLocation, pRawStatement, thenNode, retNode, regularCall); //retNode-->end BlankEdge be = new BlankEdge("skip", statement.getFileLocation(), retNode, end, "skip"); CFACreationUtils.addEdgeUnconditionallyToCFA(be); rootNode = elseNode; } //rootNode --> end if (createUndefinedFunctionCall) { CStatementEdge summaryStatementEdge = new CStatementEdge(statement.getRawStatement(), statement.getStatement(), statement.getFileLocation(), rootNode, end); rootNode.addLeavingEdge(summaryStatementEdge); end.addEnteringEdge(summaryStatementEdge); } else { //no way to skip the function call //remove last edge to rootNode for (CFAEdge edge : CFAUtils.enteringEdges(rootNode)) { CFACreationUtils.removeEdgeFromNodes(edge); } cfa.removeNode(rootNode); } } private CIdExpression createIdExpression(CExpression nameExp, FunctionEntryNode fNode) { return new CIdExpression(nameExp.getFileLocation(), nameExp.getExpressionType(), fNode.getFunctionName(), (CSimpleDeclaration) fNode.getFunctionDefinition()); } private CFunctionCall createRegularCall(CFunctionCall functionCall, FunctionEntryNode fNode) { CFunctionCallExpression oldCallExpr = functionCall.getFunctionCallExpression(); CFunctionCallExpression newCallExpr = new CFunctionCallExpression(oldCallExpr.getFileLocation(), oldCallExpr.getExpressionType(), createIdExpression(oldCallExpr.getFunctionNameExpression(), fNode), oldCallExpr.getParameterExpressions(), (CFunctionDeclaration) fNode.getFunctionDefinition()); if (functionCall instanceof CFunctionCallAssignmentStatement) { CFunctionCallAssignmentStatement asgn = (CFunctionCallAssignmentStatement) functionCall; return new CFunctionCallAssignmentStatement(functionCall.getFileLocation(), asgn.getLeftHandSide(), newCallExpr); } else if (functionCall instanceof CFunctionCallStatement) { return new CFunctionCallStatement(functionCall.getFileLocation(), newCallExpr); } else { throw new AssertionError("Unknown CFunctionCall subclass."); } } /** * @category helper */ private CFANode newCFANode(final String functionName) { assert cfa != null; CFANode nextNode = new CFANode(functionName); cfa.addNode(nextNode); return nextNode; } private void createCallEdge(FileLocation fileLocation, String pRawStatement, CFANode predecessorNode, CFANode successorNode, CFunctionCall functionCall) { CStatementEdge callEdge = new CStatementEdge(pRawStatement, functionCall, fileLocation, predecessorNode, successorNode); CFACreationUtils.addEdgeUnconditionallyToCFA(callEdge); } /** This method adds 2 edges to the cfa: * 1. trueEdge from rootNode to thenNode and * 2. falseEdge from rootNode to elseNode. * @category conditions */ private void addConditionEdges(CExpression condition, CFANode rootNode, CFANode thenNode, CFANode elseNode, FileLocation fileLocation) { // edge connecting condition with thenNode final CAssumeEdge trueEdge = new CAssumeEdge(condition.toASTString(), fileLocation, rootNode, thenNode, condition, true); CFACreationUtils.addEdgeToCFA(trueEdge, logger); // edge connecting condition with elseNode final CAssumeEdge falseEdge = new CAssumeEdge("!(" + condition.toASTString() + ")", fileLocation, rootNode, elseNode, condition, false); CFACreationUtils.addEdgeToCFA(falseEdge, logger); } private List<CFunctionEntryNode> getFunctionSet(final CFunctionCall call) { return from(candidateFunctions).filter(CFunctionEntryNode.class) .filter(Predicates.compose(matchingFunctionCall, new Function<CFunctionEntryNode, Pair<CFunctionCall, CFunctionType>>() { @Override public Pair<CFunctionCall, CFunctionType> apply(CFunctionEntryNode f) { return Pair.of(call, f.getFunctionDefinition().getType()); } })) .toList(); } private boolean checkReturnAndParamSizes(CFunctionCallExpression functionCallExpression, CFunctionType functionType) { final MachineModel machine = cfa.getMachineModel(); try { CType declRet = functionType.getReturnType(); CType actRet = functionCallExpression.getExpressionType(); if (machine.getSizeof(declRet) != machine.getSizeof(actRet)) { logger.log(Level.FINEST, "Function call", functionCallExpression.toASTString(), "with type", actRet, "does not match function", functionType, "with return type", declRet, "because of return types with different sizes."); return false; } List<CType> declParams = functionType.getParameters(); List<CExpression> exprParams = functionCallExpression.getParameterExpressions(); for (int i = 0; i < declParams.size(); i++) { CType dt = declParams.get(i); CType et = exprParams.get(i).getExpressionType(); if (machine.getSizeof(dt) != machine.getSizeof(et)) { logger.log(Level.FINEST, "Function call", functionCallExpression.toASTString(), "does not match function", functionType, "because actual parameter", i, "has type", et, "instead of", dt, "(differing sizes)."); return false; } } } catch (IllegalArgumentException e) { // We can't get size of CProblemTypes, and they actually occur for valid C code. // This is ugly, but we have no chance but catch this exception and return true here. logger.logUserException(Level.INFO, e, functionType.toASTString("") + " " + functionCallExpression); return true; } return true; } private boolean checkReturnAndParamTypes(CFunctionCallExpression functionCallExpression, CFunctionType functionType) { CType declRet = functionType.getReturnType(); CType actRet = functionCallExpression.getExpressionType(); if (!isCompatibleType(declRet, actRet)) { logger.log(Level.FINEST, "Function call", functionCallExpression.toASTString(), "with type", actRet, "does not match function", functionType, "with return type", declRet); return false; } List<CType> declParams = functionType.getParameters(); List<CExpression> exprParams = functionCallExpression.getParameterExpressions(); for (int i = 0; i < declParams.size(); i++) { CType dt = declParams.get(i); CType et = exprParams.get(i).getExpressionType(); if (!isCompatibleType(dt, et)) { logger.log(Level.FINEST, "Function call", functionCallExpression.toASTString(), "does not match function", functionType, "because actual parameter", i, "has type", et, "instead of", dt); return false; } } return true; } /** * Exclude void functions if the return value of the function is used in an assignment. */ private boolean checkReturnValue(CFunctionCall call, CFunctionType functionType) { if (call instanceof CFunctionCallAssignmentStatement) { CType returnType = functionType.getReturnType().getCanonicalType(); if (returnType instanceof CVoidType) { return false; } } return true; } /** * Check whether two types are assignment compatible. * * @param declaredType The type that is declared (e.g., as variable type). * @param actualType The type that is actually used (e.g., as type of an expression). * @return True if a value of actualType may be assigned to a variable of declaredType. */ private boolean isCompatibleType(CType declaredType, CType actualType) { // TODO this needs to be implemented // Type equality is too strong. // After this is implemented, change the default of functionSets // to USED_IN_CODE, EQ_PARAM_TYPES return declaredType.getCanonicalType().equals(actualType.getCanonicalType()); } private final boolean checkParamSizes(CFunctionCallExpression functionCallExpression, CFunctionType functionType) { //get the parameter expression List<CExpression> parameters = functionCallExpression.getParameterExpressions(); // check if the number of function parameters are right int declaredParameters = functionType.getParameters().size(); int actualParameters = parameters.size(); return (functionType.takesVarArgs() && declaredParameters <= actualParameters) || (declaredParameters == actualParameters); } }