/******************************************************************************* * Copyright (c) 2010, 2011 Gil Barash * 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: * Gil Barash - Initial implementation * Elena laskavaia - Rewrote checker to reduce false positives in complex cases * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.codan.internal.checkers; import org.eclipse.cdt.codan.core.cxx.CxxAstUtils; import org.eclipse.cdt.codan.core.cxx.model.AbstractIndexAstChecker; import org.eclipse.cdt.codan.core.model.ICheckerWithPreferences; import org.eclipse.cdt.codan.core.model.IProblemLocation; import org.eclipse.cdt.codan.core.model.IProblemLocationFactory; import org.eclipse.cdt.codan.core.model.IProblemWorkingCopy; import org.eclipse.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.IASTBreakStatement; import org.eclipse.cdt.core.dom.ast.IASTCaseStatement; import org.eclipse.cdt.core.dom.ast.IASTComment; import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement; import org.eclipse.cdt.core.dom.ast.IASTContinueStatement; import org.eclipse.cdt.core.dom.ast.IASTDefaultStatement; import org.eclipse.cdt.core.dom.ast.IASTExpressionStatement; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTGotoStatement; import org.eclipse.cdt.core.dom.ast.IASTIfStatement; import org.eclipse.cdt.core.dom.ast.IASTMacroExpansionLocation; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNodeLocation; 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.IASTTranslationUnit; public class CaseBreakChecker extends AbstractIndexAstChecker implements ICheckerWithPreferences { public static final String ER_ID = "org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem"; //$NON-NLS-1$ public static final String PARAM_LAST_CASE = "last_case_param"; //$NON-NLS-1$ public static final String PARAM_EMPTY_CASE = "empty_case_param"; //$NON-NLS-1$ public static final String PARAM_NO_BREAK_COMMENT = "no_break_comment"; //$NON-NLS-1$ public static final String DEFAULT_NO_BREAK_COMMENT = "no break"; //$NON-NLS-1$ private Boolean _checkLastCase; // Should we check the last case in the switch? private Boolean _checkEmptyCase; // Should we check an empty case (a case without any statements within it) private String _noBreakComment; // The comment suppressing this warning public CaseBreakChecker() { } /** * This visitor looks for "switch" statements and invokes "SwitchVisitor" on them. */ class SwitchFindingVisitor extends ASTVisitor { SwitchFindingVisitor() { shouldVisitStatements = true; } /** * @param statement * @return true iff the statement is on of: * - "break" (checks that the break actually exists the * "switch") * - "return" * - "continue" * - "goto" (does not check that the goto actually exists the * switch) * - "throw" * - "exit" */ protected boolean isBreakOrExitStatement(IASTStatement statement) { CxxAstUtils utils = CxxAstUtils.getInstance(); return (statement instanceof IASTBreakStatement) || statement instanceof IASTReturnStatement || statement instanceof IASTContinueStatement || statement instanceof IASTGotoStatement || utils.isThrowStatement(statement) || utils.isExitStatement(statement); } @Override public int visit(IASTStatement statement) { if (statement instanceof IASTSwitchStatement && !isProducedByMacroExpansion(statement)) { IASTSwitchStatement switchStmt = (IASTSwitchStatement) statement; IASTStatement body = switchStmt.getBody(); if (body instanceof IASTCompoundStatement) { // If not it is not really a switch IASTStatement[] statements = ((IASTCompoundStatement) body).getStatements(); IASTStatement prevCase = null; for (int i = 0; i < statements.length; i++) { IASTStatement curr = statements[i]; if (curr instanceof IASTSwitchStatement) { visit(curr); } IASTStatement next = null; if (i < statements.length - 1) next = statements[i + 1]; if (isCaseStatement(curr)) { prevCase = curr; } // Next is case or end of switch - means this one is the last if (prevCase != null && (isCaseStatement(next) || next == null)) { // Check that current statement end with break or any other exit statement if (!_checkEmptyCase && isCaseStatement(curr) && next != null) { continue; // Empty case and we don't care } if (!_checkLastCase && next == null) { continue; // Last case and we don't care } if (!isProducedByMacroExpansion(prevCase) && isFallThroughStamement(curr)) { IASTComment comment = null; if (next != null) { comment = getLeadingComment(next); } else { comment = getFreestandingComment(statement); if (comment == null) comment = getFreestandingComment(body); } if (comment != null) { String str = getTrimmedComment(comment); if (str.toLowerCase().contains(_noBreakComment.toLowerCase())) continue; } reportProblem(curr, prevCase); } } } } return PROCESS_SKIP; } return PROCESS_CONTINUE; } /** * @param statement * @return */ public boolean isCaseStatement(IASTStatement statement) { return statement instanceof IASTCaseStatement || statement instanceof IASTDefaultStatement; } /** * @param body * @return */ public boolean isFallThroughStamement(IASTStatement body) { if (body == null) return true; if (body instanceof IASTCompoundStatement) { IASTStatement[] statements = ((IASTCompoundStatement) body).getStatements(); if (statements.length > 0) { return isFallThroughStamement(statements[statements.length - 1]); } return true; } else if (isBreakOrExitStatement(body)) { return false; } else if (body instanceof IASTExpressionStatement) { return true; } else if (body instanceof IASTIfStatement) { IASTIfStatement ifs = (IASTIfStatement) body; return isFallThroughStamement(ifs.getThenClause()) || isFallThroughStamement(ifs.getElseClause()); } return true; // TODO } } public void reportProblem(IASTStatement curr, IASTStatement prevCase) { reportProblem(ER_ID, getProblemLocationAtEndOfNode(curr)); } private IProblemLocation getProblemLocationAtEndOfNode(IASTNode astNode) { IASTFileLocation astLocation = astNode.getFileLocation(); int line = astLocation.getEndingLineNumber(); IProblemLocationFactory locFactory = getRuntime().getProblemLocationFactory(); return locFactory.createProblemLocation(getFile(), -1, -1, line); } /** * Checks if the given statement is a result of macro expansion with a possible * exception for the trailing semicolon. * * @param statement the statement to check. * @return <code>true</code> if the statement is a result of macro expansion */ private boolean isProducedByMacroExpansion(IASTStatement statement) { IASTNodeLocation[] locations = statement.getNodeLocations(); return locations.length > 0 && locations[0] instanceof IASTMacroExpansionLocation && (locations.length == 1 || locations.length == 2 && locations[1].getNodeLength() == 1); } /** * @param comment * @return */ public String getTrimmedComment(IASTComment comment) { String str = new String(comment.getComment()); if (comment.isBlockComment()) str = str.substring(2, str.length() - 2); else str = str.substring(2); str = str.trim(); return str; } /** * @param statement * @return */ public IASTComment getLeadingComment(IASTStatement statement) { return getCommentMap().getLastLeadingCommentForNode(statement); } /** * @param statement * @return */ public IASTComment getFreestandingComment(IASTStatement statement) { return getCommentMap().getLastFreestandingCommentForNode(statement); } public void initPreferences(IProblemWorkingCopy problem) { super.initPreferences(problem); addPreference(problem, PARAM_NO_BREAK_COMMENT, CheckersMessages.CaseBreakChecker_DefaultNoBreakCommentDescription, DEFAULT_NO_BREAK_COMMENT); addPreference(problem, PARAM_LAST_CASE, CheckersMessages.CaseBreakChecker_LastCaseDescription, Boolean.TRUE); addPreference(problem, PARAM_EMPTY_CASE, CheckersMessages.CaseBreakChecker_EmptyCaseDescription, Boolean.FALSE); } public void processAst(IASTTranslationUnit ast) { _checkLastCase = (Boolean) getPreference(getProblemById(ER_ID, getFile()), PARAM_LAST_CASE); _checkEmptyCase = (Boolean) getPreference(getProblemById(ER_ID, getFile()), PARAM_EMPTY_CASE); _noBreakComment = (String) getPreference(getProblemById(ER_ID, getFile()), PARAM_NO_BREAK_COMMENT); SwitchFindingVisitor visitor = new SwitchFindingVisitor(); ast.accept(visitor); } }