/******************************************************************************* * Copyright (c) 2009, 2015 Alena Laskavaia and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Alena Laskavaia - initial API and implementation * Tomasz Wesolowski - Bug 348387 *******************************************************************************/ package org.eclipse.cdt.codan.core.cxx.internal.model.cfg; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import org.eclipse.cdt.codan.core.cxx.Activator; import org.eclipse.cdt.codan.core.model.cfg.IBasicBlock; import org.eclipse.cdt.codan.core.model.cfg.IBranchNode; import org.eclipse.cdt.codan.core.model.cfg.ICfgData; import org.eclipse.cdt.codan.core.model.cfg.IConnectorNode; import org.eclipse.cdt.codan.core.model.cfg.IDecisionNode; import org.eclipse.cdt.codan.core.model.cfg.IExitNode; import org.eclipse.cdt.codan.core.model.cfg.IJumpNode; import org.eclipse.cdt.codan.core.model.cfg.IPlainNode; import org.eclipse.cdt.codan.core.model.cfg.ISingleOutgoing; import org.eclipse.cdt.codan.core.model.cfg.IStartNode; import org.eclipse.cdt.codan.internal.core.cfg.AbstractBasicBlock; import org.eclipse.cdt.codan.internal.core.cfg.ConnectorNode; import org.eclipse.cdt.codan.internal.core.cfg.DecisionNode; import org.eclipse.cdt.codan.internal.core.cfg.JumpNode; import org.eclipse.cdt.core.dom.ast.IASTBreakStatement; import org.eclipse.cdt.core.dom.ast.IASTCaseStatement; import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement; import org.eclipse.cdt.core.dom.ast.IASTContinueStatement; import org.eclipse.cdt.core.dom.ast.IASTDeclaration; import org.eclipse.cdt.core.dom.ast.IASTDeclarationStatement; import org.eclipse.cdt.core.dom.ast.IASTDefaultStatement; import org.eclipse.cdt.core.dom.ast.IASTDoStatement; import org.eclipse.cdt.core.dom.ast.IASTExpression; import org.eclipse.cdt.core.dom.ast.IASTExpressionStatement; import org.eclipse.cdt.core.dom.ast.IASTForStatement; import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression; import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition; import org.eclipse.cdt.core.dom.ast.IASTGotoStatement; import org.eclipse.cdt.core.dom.ast.IASTIfStatement; import org.eclipse.cdt.core.dom.ast.IASTLabelStatement; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNullStatement; import org.eclipse.cdt.core.dom.ast.IASTProblemStatement; import org.eclipse.cdt.core.dom.ast.IASTReturnStatement; import org.eclipse.cdt.core.dom.ast.IASTStatement; import org.eclipse.cdt.core.dom.ast.IASTSwitchStatement; import org.eclipse.cdt.core.dom.ast.IASTUnaryExpression; import org.eclipse.cdt.core.dom.ast.IASTWhileStatement; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCatchHandler; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTRangeBasedForStatement; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTryBlockStatement; import org.eclipse.cdt.internal.core.dom.parser.ValueFactory; import org.eclipse.osgi.util.NLS; /** * This class creates C control flow graph */ public class ControlFlowGraphBuilder { CxxStartNode start; Collection<IExitNode> exits; Collection<IBasicBlock> dead; CxxExitNode returnExit; CxxNodeFactory factory = new CxxNodeFactory(); IConnectorNode outerBreak; IConnectorNode outerContinue; Map<String, IBranchNode> labels = new LinkedHashMap<>(); /** * Builds the graph. */ public CxxControlFlowGraph build(IASTFunctionDefinition def) { IASTStatement body = def.getBody(); start = new CxxStartNode(); exits = new ArrayList<>(); dead = new LinkedHashSet<>(); IBasicBlock last = createSubGraph(start, body); for (IBranchNode label : labels.values()) { IConnectorNode conn = (IConnectorNode) label.getOutgoing(); if (conn.getIncomingSize() <= 1 && label.getIncoming() == null) dead.add(label); else { dead.remove(label); dead.remove(conn); } } if (!(last instanceof IExitNode || last instanceof IJumpNode || deadConnector(last))) { returnExit = factory.createExitNode(null); returnExit.setStartNode(start); addOutgoing(last, returnExit); exits.add(returnExit); for (IBasicBlock ds : dead) { IBasicBlock dl = findLast(ds); if (dl != null && !(dl instanceof IExitNode) && dl.getOutgoingSize() == 0 && dl != returnExit) { ((AbstractBasicBlock) dl).addOutgoing(returnExit); } } } CxxControlFlowGraph graph = new CxxControlFlowGraph(start, exits); graph.setUnconnectedNodes(dead); return graph; } private boolean deadConnector(IBasicBlock conn) { if (conn instanceof IJumpNode || conn instanceof IConnectorNode) { if (conn.getIncomingSize() == 0) { return true; } if (conn instanceof IJumpNode) { IJumpNode jm = (IJumpNode) conn; if (jm.isBackwardArc()) return false; } IBasicBlock[] conns = conn.getIncomingNodes(); for (int i = 0; i < conns.length; i++) { IBasicBlock bb = conns[i]; if (!deadConnector(bb)) return false; } return true; } return false; } private IBasicBlock findLast(IBasicBlock node) { if (node instanceof IJumpNode) return null; if (node.getOutgoingSize() == 0) return node; if (node instanceof ISingleOutgoing) { return findLast(((ISingleOutgoing) node).getOutgoing()); } else if (node instanceof IDecisionNode) { return findLast(((IDecisionNode) node).getMergeNode().getOutgoing()); } return node; } private IBasicBlock createSubGraph(IBasicBlock prev, IASTStatement body) { if (body instanceof IASTCompoundStatement) { IASTCompoundStatement comp = (IASTCompoundStatement) body; for (IASTStatement statement : comp.getStatements()) { prev = createSubGraph(prev, statement); } } else if (body instanceof IASTExpressionStatement || body instanceof IASTDeclarationStatement || body instanceof IASTNullStatement) { if (isThrowStatement(body) || isExitStatement(body)) { CxxExitNode node = createExitNode(prev, body); return node; } CxxPlainNode node = factory.createPlainNode(body); addOutgoing(prev, node); return node; } else if (body instanceof IASTIfStatement) { return createIf(prev, (IASTIfStatement) body); } else if (body instanceof IASTWhileStatement) { return createWhile(prev, (IASTWhileStatement) body); } else if (body instanceof IASTForStatement) { return createFor(prev, (IASTForStatement) body); } else if (body instanceof ICPPASTRangeBasedForStatement) { return createRangeBasedFor(prev, (ICPPASTRangeBasedForStatement) body); } else if (body instanceof IASTDoStatement) { return createDoWhile(prev, (IASTDoStatement) body); } else if (body instanceof IASTReturnStatement) { CxxExitNode node = createExitNode(prev, body); return node; } else if (body instanceof IASTBreakStatement) { if (outerBreak != null) return addJump(prev, outerBreak); return prev; } else if (body instanceof IASTContinueStatement) { if (outerContinue != null) return addJump(prev, outerContinue); return prev; } else if (body instanceof IASTSwitchStatement) { return createSwitch(prev, (IASTSwitchStatement) body); } else if (body instanceof IASTLabelStatement) { IASTLabelStatement ast = (IASTLabelStatement) body; String labelName = ast.getName().toString(); IBranchNode labNode = labels.get(labelName); IConnectorNode conn; if (labNode != null) { conn = (IConnectorNode) labNode.getOutgoing(); addOutgoing(prev, labNode); } else { conn = createLabelNodes(prev, labelName); } return createSubGraph(conn, ast.getNestedStatement()); } else if (body instanceof IASTGotoStatement) { IASTGotoStatement ast = (IASTGotoStatement) body; String labelName = ast.getName().toString(); IConnectorNode conn; IBranchNode labNode = labels.get(labelName); if (labNode != null) { conn = (IConnectorNode) labNode.getOutgoing(); } else { conn = createLabelNodes(null, labelName); } IJumpNode gotoNode = factory.createJumpNode(); boolean backward = labNode != null; // This is not accurate XXX ((JumpNode) gotoNode).setJump(conn, backward); ((ConnectorNode) conn).addIncoming(gotoNode); addOutgoing(prev, gotoNode); return gotoNode; } else if (body instanceof IASTProblemStatement) { CxxPlainNode node = factory.createPlainNode(body); addOutgoing(prev, node); return node; } else if (body == null) { // skip - sometimes body is empty such as no else } else if (body instanceof ICPPASTTryBlockStatement) { return createTry(prev, (ICPPASTTryBlockStatement) body); } else { Activator.log(NLS.bind(Messages.ControlFlowGraphBuilder_unsupported_statement_type, body.getClass().getSimpleName())); } return prev; } private IBasicBlock createTry(IBasicBlock prev, ICPPASTTryBlockStatement body) { DecisionNode ifNode = factory.createDecisionNode(body); addOutgoing(prev, ifNode); IConnectorNode mergeNode = factory.createConnectorNode(); ifNode.setMergeNode(mergeNode); IBranchNode tryBodyNode = factory.createBranchNode(IBranchNode.TRY_BODY); addOutgoing(ifNode, tryBodyNode); IBasicBlock tryBody = createSubGraph(tryBodyNode, body.getTryBody()); addJump(tryBody, mergeNode); ICPPASTCatchHandler[] catchHandlers = body.getCatchHandlers(); for (int i = 0; i < catchHandlers.length; i++) { ICPPASTCatchHandler handler = catchHandlers[i]; IBranchNode handlerNode; IASTDeclaration declaration = handler.getDeclaration(); if (declaration != null) { handlerNode = factory.createBranchNode(declaration); } else { handlerNode = factory.createBranchNode(IBranchNode.CATCH_ANY); } addOutgoing(ifNode, handlerNode); IBasicBlock els = createSubGraph(handlerNode, handler.getCatchBody()); addJump(els, mergeNode); } return mergeNode; } private boolean isThrowStatement(IASTNode body) { if (!(body instanceof IASTExpressionStatement)) return false; IASTExpression expression = ((IASTExpressionStatement) body).getExpression(); if (!(expression instanceof IASTUnaryExpression)) return false; return ((IASTUnaryExpression) expression).getOperator() == IASTUnaryExpression.op_throw; } private boolean isExitStatement(IASTNode body) { if (!(body instanceof IASTExpressionStatement)) return false; IASTExpression expression = ((IASTExpressionStatement) body).getExpression(); if (!(expression instanceof IASTFunctionCallExpression)) return false; IASTExpression functionNameExpression = ((IASTFunctionCallExpression) expression).getFunctionNameExpression(); return functionNameExpression.getRawSignature().equals("exit"); //$NON-NLS-1$ } protected CxxExitNode createExitNode(IBasicBlock prev, IASTNode body) { CxxExitNode node = factory.createExitNode(body); node.setStartNode(start); addOutgoing(prev, node); exits.add(node); return node; } /** * labeled statement consists of connector for jumps, * branch for label and statement * * @param prev * @param labelName * @return */ protected IConnectorNode createLabelNodes(IBasicBlock prev, String labelName) { IBranchNode branch = factory.createBranchNode(labelName); if (prev == null || prev instanceof IJumpNode || prev instanceof IExitNode) ;// don't do anything, leave dangling branch node else addOutgoing(prev, branch); labels.put(labelName, branch); IConnectorNode conn = factory.createConnectorNode(); addOutgoing(branch, conn); return conn; } protected IBasicBlock createIf(IBasicBlock prev, IASTIfStatement body) { DecisionNode ifNode = factory.createDecisionNode(body.getConditionExpression()); addOutgoing(prev, ifNode); IConnectorNode mergeNode = factory.createConnectorNode(); ifNode.setMergeNode(mergeNode); IBranchNode thenNode = factory.createBranchNode(IBranchNode.THEN); addOutgoing(ifNode, thenNode); IBasicBlock then = createSubGraph(thenNode, body.getThenClause()); addJump(then, mergeNode); IBranchNode elseNode = factory.createBranchNode(IBranchNode.ELSE); addOutgoing(ifNode, elseNode); IBasicBlock els = createSubGraph(elseNode, body.getElseClause()); addJump(els, mergeNode); fixConnector(mergeNode); return mergeNode; } protected void fixConnector(IConnectorNode mergeNode) { if (mergeNode.getIncomingSize()==0) dead.add(mergeNode); // dead connector node } private IBasicBlock createSwitch(IBasicBlock prev, IASTSwitchStatement body) { DecisionNode node = factory.createDecisionNode(body.getControllerExpression()); addOutgoing(prev, node); IConnectorNode mergeNode = factory.createConnectorNode(); node.setMergeNode(mergeNode); createSwitchBody(node, mergeNode, body.getBody()); fixConnector(mergeNode); return mergeNode; } private void createSwitchBody(IDecisionNode switchNode, IConnectorNode mergeNode, IASTStatement body) { if (!(body instanceof IASTCompoundStatement)) return; // bad IASTCompoundStatement comp = (IASTCompoundStatement) body; IBasicBlock prev = switchNode; IConnectorNode savedBreak = outerBreak; outerBreak = mergeNode; boolean encounteredDefault = false; try { for (IASTStatement statement : comp.getStatements()) { if (statement instanceof IASTCaseStatement || statement instanceof IASTDefaultStatement) { IBranchNode lbl = null; if (statement instanceof IASTCaseStatement) { lbl = factory.createBranchNode(statement); } else if (statement instanceof IASTDefaultStatement) { lbl = factory.createBranchNode(IBranchNode.DEFAULT); encounteredDefault = true; } if (!(prev instanceof IExitNode) && prev != switchNode) { IConnectorNode here = factory.createConnectorNode(); addJump(prev, here); addOutgoing(lbl, here); prev = here; } else { prev = lbl; } addOutgoing(switchNode, lbl); continue; } prev = createSubGraph(prev, statement); } } finally { outerBreak = savedBreak; } // If the switch didn't have an explicit 'default' case, we still have to // add an edge for the situation where no case was matched. if (!encounteredDefault) { if (!(prev instanceof IExitNode) && prev != switchNode) { addJump(prev, mergeNode); } IBranchNode defaultBranch = factory.createBranchNode(IBranchNode.DEFAULT); addOutgoing(switchNode, defaultBranch); prev = defaultBranch; } addJump(prev, mergeNode); } private IBasicBlock createFor(IBasicBlock prev, IASTForStatement forNode) { // Add initializer IPlainNode init = factory.createPlainNode(forNode.getInitializerStatement()); addOutgoing(prev, init); prev = init; // Add continue connector IConnectorNode beforeCheck = factory.createConnectorNode(); addOutgoing(prev, beforeCheck); // Decision node CxxDecisionNode decision = factory.createDecisionNode(forNode.getConditionExpression()); addOutgoing(beforeCheck, decision); // Add break connector IConnectorNode nBreak = factory.createConnectorNode(); decision.setMergeNode(nBreak); // Create body and jump to continue node IBranchNode loopStart = factory.createBranchNode(IBranchNode.THEN); addOutgoing(decision, loopStart); // Set break/continue IConnectorNode nContinue = factory.createConnectorNode(); IConnectorNode savedContinue = outerContinue; IConnectorNode savedBreak = outerBreak; outerContinue = nContinue; outerBreak = nBreak; IBasicBlock endBody = decision; try { endBody = createSubGraph(loopStart, forNode.getBody()); } finally { outerContinue = savedContinue; outerBreak = savedBreak; } // inc IPlainNode inc = factory.createPlainNode(forNode.getIterationExpression()); addOutgoing(endBody, nContinue); addOutgoing(nContinue, inc); // Connect with backward link addJump(inc, beforeCheck, true); // Add "else" branch IBranchNode loopEnd = factory.createBranchNode(IBranchNode.ELSE); addOutgoing(decision, loopEnd); addJump(loopEnd, nBreak); fixConnector(nBreak); return nBreak; } private IBasicBlock createRangeBasedFor(IBasicBlock prev, ICPPASTRangeBasedForStatement forNode) { // Add initializer IPlainNode init = factory.createPlainNode(forNode.getDeclaration()); addOutgoing(prev, init); prev = init; // Add continue connector IConnectorNode beforeCheck = factory.createConnectorNode(); addOutgoing(prev, beforeCheck); // Decision node CxxDecisionNode decision = factory.createDecisionNode(forNode.getInitializerClause()); // XXX test expression addOutgoing(beforeCheck, decision); // Add break connector IConnectorNode nBreak = factory.createConnectorNode(); decision.setMergeNode(nBreak); // Create body and jump to continue node IBranchNode loopStart = factory.createBranchNode(IBranchNode.THEN); addOutgoing(decision, loopStart); // Set break/continue IConnectorNode nContinue = factory.createConnectorNode(); IConnectorNode savedContinue = outerContinue; IConnectorNode savedBreak = outerBreak; outerContinue = nContinue; outerBreak = nBreak; IBasicBlock endBody = createSubGraph(loopStart, forNode.getBody()); outerContinue = savedContinue; outerBreak = savedBreak; // inc IPlainNode inc = factory.createPlainNode(); // XXX increment addOutgoing(endBody, nContinue); addOutgoing(nContinue, inc); // Connect with backward link addJump(inc, beforeCheck, true); // Add "else" branch IBranchNode loopEnd = factory.createBranchNode(IBranchNode.ELSE); addOutgoing(decision, loopEnd); addJump(loopEnd, nBreak); fixConnector(nBreak); return nBreak; } protected IBasicBlock createWhile(IBasicBlock prev, IASTWhileStatement body) { // Add continue connector IConnectorNode nContinue = factory.createConnectorNode(); addOutgoing(prev, nContinue); // Decision node CxxDecisionNode decision = factory.createDecisionNode(body.getCondition()); addOutgoing(nContinue, decision); // Add break connector IConnectorNode nBreak = factory.createConnectorNode(); decision.setMergeNode(nBreak); // Create body and jump to continue node IBranchNode loopStart = factory.createBranchNode(IBranchNode.THEN); addOutgoing(decision, loopStart); // Set break/continue IConnectorNode savedContinue = outerContinue; IConnectorNode savedBreak = outerBreak; outerContinue = nContinue; outerBreak = nBreak; IBasicBlock endBody = decision; try { endBody = createSubGraph(loopStart, body.getBody()); } finally { // Restore outerContinue = savedContinue; outerBreak = savedBreak; } // Backward jump addJump(endBody, nContinue, true); // Connect with else branch IBranchNode loopEnd = factory.createBranchNode(IBranchNode.ELSE); addOutgoing(decision, loopEnd); addJump(loopEnd, nBreak); fixConnector(nBreak); return nBreak; } protected IBasicBlock createDoWhile(IBasicBlock prev, IASTDoStatement body) { // Create body and jump to continue node IConnectorNode loopStart = factory.createConnectorNode(); addOutgoing(prev, loopStart); // continue/break IConnectorNode nContinue = factory.createConnectorNode(); IConnectorNode nBreak = factory.createConnectorNode(); IConnectorNode savedContinue = outerContinue; IConnectorNode savedBreak = outerBreak; outerContinue = nContinue; outerBreak = nBreak; IBasicBlock endBody = createSubGraph(loopStart, body.getBody()); // Restore outerContinue = savedContinue; outerBreak = savedBreak; // Add continue connector addOutgoing(endBody, nContinue); // Decision node CxxDecisionNode decision = factory.createDecisionNode(body.getCondition()); addOutgoing(nContinue, decision); // then branch IBranchNode thenNode = factory.createBranchNode(IBranchNode.THEN); addOutgoing(decision, thenNode); IJumpNode jumpToStart = factory.createJumpNode(); addOutgoing(thenNode, jumpToStart); ((JumpNode) jumpToStart).setBackward(true); // Connect with backward link addOutgoing(jumpToStart, loopStart); // Connect with else branch IBranchNode loopEnd = factory.createBranchNode(IBranchNode.ELSE); addOutgoing(decision, loopEnd); // Add break connector decision.setMergeNode(nBreak); addJump(loopEnd, nBreak); fixConnector(nBreak); return nBreak; } private IJumpNode addJump(IBasicBlock prev, IConnectorNode conn) { return addJump(prev, conn, false); } private IJumpNode addJump(IBasicBlock prev, IConnectorNode conn, boolean backward) { if (prev instanceof IJumpNode) return (IJumpNode) prev; if (prev instanceof IExitNode) return null; JumpNode jump = (JumpNode) factory.createJumpNode(); addOutgoing(prev, jump); jump.setJump(conn, backward); ((ConnectorNode) conn).addIncoming(jump); return jump; } private void addOutgoing(IBasicBlock prev, IBasicBlock node) { if (prev instanceof IExitNode || prev instanceof IJumpNode || prev == null) { dead.add(node); return; } if (prev instanceof IDecisionNode) { if (node instanceof IBranchNode) { IDecisionNode decisionNode = (IDecisionNode) prev; if (isConstant(decisionNode, 1) && ((IBranchNode) node).getLabel().equals(IBranchNode.ELSE)) { dead.add(node); return; } else if (isConstant(decisionNode, 0) && ((IBranchNode) node).getLabel().equals(IBranchNode.THEN)) { dead.add(node); return; } } else { dead.add(node); return; } } ((AbstractBasicBlock) prev).addOutgoing(node); if (!(node instanceof IStartNode)) ((AbstractBasicBlock) node).addIncoming(prev); } private boolean isConstant(IDecisionNode node, long testvalue) { if (node instanceof ICfgData) { IASTNode ast = (IASTNode) ((ICfgData) node).getData(); if (ast instanceof IASTExpression) { Number numericalValue = ValueFactory.getConstantNumericalValue((IASTExpression) ast); if (numericalValue != null) return numericalValue.longValue() == testvalue; } } return false; } }