| /******************************************************************************* |
| * Copyright (c) 2011, 2013 Andrew Gvozdev 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: |
| * Andrew Gvozdev - initial API and implementation |
| * Sergey Prigogin (Google) |
| *******************************************************************************/ |
| package org.eclipse.cdt.codan.internal.checkers; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import org.eclipse.cdt.codan.checkers.CodanCheckersActivator; |
| import org.eclipse.cdt.codan.core.cxx.CxxAstUtils; |
| import org.eclipse.cdt.codan.core.cxx.model.AbstractIndexAstChecker; |
| import org.eclipse.cdt.codan.core.model.IProblem; |
| import org.eclipse.cdt.codan.core.model.IProblemWorkingCopy; |
| import org.eclipse.cdt.codan.core.param.ListProblemPreference; |
| import org.eclipse.cdt.core.dom.ast.ASTVisitor; |
| import org.eclipse.cdt.core.dom.ast.IASTASMDeclaration; |
| import org.eclipse.cdt.core.dom.ast.IASTAttributeOwner; |
| import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier; |
| import org.eclipse.cdt.core.dom.ast.IASTDeclaration; |
| import org.eclipse.cdt.core.dom.ast.IASTDeclarator; |
| import org.eclipse.cdt.core.dom.ast.IASTEqualsInitializer; |
| import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator; |
| import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition; |
| import org.eclipse.cdt.core.dom.ast.IASTInitializer; |
| import org.eclipse.cdt.core.dom.ast.IASTInitializerClause; |
| import org.eclipse.cdt.core.dom.ast.IASTLiteralExpression; |
| import org.eclipse.cdt.core.dom.ast.IASTName; |
| import org.eclipse.cdt.core.dom.ast.IASTNode; |
| import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration; |
| import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; |
| import org.eclipse.cdt.core.dom.ast.IBinding; |
| import org.eclipse.cdt.core.dom.ast.IFunction; |
| import org.eclipse.cdt.core.dom.ast.IProblemBinding; |
| import org.eclipse.cdt.core.dom.ast.IProblemType; |
| import org.eclipse.cdt.core.dom.ast.IType; |
| import org.eclipse.cdt.core.dom.ast.IVariable; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTConstructorInitializer; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPDeferredFunction; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod; |
| import org.eclipse.cdt.core.parser.util.AttributeUtil; |
| |
| /** |
| * Checker looking for unused function or variable declarations. |
| */ |
| public class UnusedSymbolInFileScopeChecker extends AbstractIndexAstChecker { |
| public static final String ER_UNUSED_VARIABLE_DECLARATION_ID = "org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem"; //$NON-NLS-1$ |
| public static final String ER_UNUSED_FUNCTION_DECLARATION_ID = "org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem"; //$NON-NLS-1$ |
| public static final String ER_UNUSED_STATIC_FUNCTION_ID = "org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem"; //$NON-NLS-1$ |
| public static final String PARAM_MACRO_ID = "macro"; //$NON-NLS-1$ |
| public static final String PARAM_EXCEPT_ARG_LIST = "exceptions"; //$NON-NLS-1$ |
| /* |
| * Various attributes that when present for a symbol should make it considered as used. |
| */ |
| private static final String[] USAGE_ATTRIBUTES = new String[] { "__unused__", "unused", "constructor", "destructor" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| |
| private Map<IBinding, IASTDeclarator> externFunctionDeclarations = new HashMap<IBinding, IASTDeclarator>(); |
| private Map<IBinding, IASTDeclarator> staticFunctionDeclarations = new HashMap<IBinding, IASTDeclarator>(); |
| private Map<IBinding, IASTDeclarator> staticFunctionDefinitions = new HashMap<IBinding, IASTDeclarator>(); |
| private Map<IBinding, IASTDeclarator> externVariableDeclarations = new HashMap<IBinding, IASTDeclarator>(); |
| private Map<IBinding, IASTDeclarator> staticVariableDeclarations = new HashMap<IBinding, IASTDeclarator>(); |
| private Set<IBinding> declarationsWithUsageAttributes = new HashSet<>(); |
| private IProblemWorkingCopy unusedVariableProblem = null; |
| |
| @Override |
| public boolean runInEditor() { |
| return true; |
| } |
| |
| @Override |
| public void initPreferences(IProblemWorkingCopy problem) { |
| super.initPreferences(problem); |
| addPreference(problem, PARAM_MACRO_ID, CheckersMessages.StatementHasNoEffectChecker_ParameterMacro, Boolean.TRUE); |
| if (problem.getId().equals(ER_UNUSED_VARIABLE_DECLARATION_ID)) { |
| unusedVariableProblem = problem; |
| ListProblemPreference pref = addListPreference(problem, PARAM_EXCEPT_ARG_LIST, |
| CheckersMessages.UnusedSymbolInFileScopeChecker_Exceptions, |
| CheckersMessages.UnusedSymbolInFileScopeChecker_CharacterSequence); |
| pref.addChildValue("@(#)"); //$NON-NLS-1$ |
| pref.addChildValue("$Id"); //$NON-NLS-1$ |
| } |
| } |
| |
| private void clearCandidates() { |
| externFunctionDeclarations.clear(); |
| staticFunctionDeclarations.clear(); |
| staticFunctionDefinitions.clear(); |
| externVariableDeclarations.clear(); |
| staticVariableDeclarations.clear(); |
| } |
| |
| private boolean isAnyCandidate() { |
| return !externFunctionDeclarations.isEmpty() || |
| !staticFunctionDeclarations.isEmpty() || |
| !staticFunctionDefinitions.isEmpty() || |
| !externVariableDeclarations.isEmpty() || |
| !staticVariableDeclarations.isEmpty(); |
| } |
| |
| @Override |
| public void processAst(IASTTranslationUnit ast) { |
| if (ast.isHeaderUnit()) |
| return; |
| |
| clearCandidates(); |
| collectCandidates(ast); |
| |
| if (isAnyCandidate()) { |
| filterOutUsedElements(ast); |
| reportProblems(); |
| } |
| declarationsWithUsageAttributes.clear(); |
| } |
| |
| private void collectCandidates(IASTTranslationUnit ast) { |
| try { |
| ast.accept(new ASTVisitor() { |
| { |
| shouldVisitDeclarations = true; |
| } |
| |
| @Override |
| public int visit(IASTDeclaration element) { |
| if (element instanceof IASTSimpleDeclaration) { |
| // declarations |
| IASTSimpleDeclaration simpleDeclaration = (IASTSimpleDeclaration) element; |
| |
| IASTDeclSpecifier declSpec = simpleDeclaration.getDeclSpecifier(); |
| boolean hasUsageAttrib = hasUsageAttribute(declSpec); |
| IASTDeclarator[] declarators = simpleDeclaration.getDeclarators(); |
| for (IASTDeclarator decl : declarators) { |
| IASTName astName = decl.getName(); |
| if (astName != null) { |
| IBinding binding = astName.resolveBinding(); |
| if (hasUsageAttrib || hasUsageAttribute(decl)) { |
| declarationsWithUsageAttributes.add(binding); |
| } |
| int storageClass = simpleDeclaration.getDeclSpecifier().getStorageClass(); |
| |
| if (binding instanceof IFunction) { |
| if (storageClass == IASTDeclSpecifier.sc_extern || |
| storageClass == IASTDeclSpecifier.sc_unspecified) { |
| if (shouldReportInMacro(ER_UNUSED_FUNCTION_DECLARATION_ID) || |
| !CxxAstUtils.isInMacro(astName)) { |
| externFunctionDeclarations.put(binding, decl); |
| } |
| } else if (storageClass == IASTDeclSpecifier.sc_static) { |
| if (shouldReportInMacro(ER_UNUSED_STATIC_FUNCTION_ID) || |
| !CxxAstUtils.isInMacro(astName)) { |
| staticFunctionDeclarations.put(binding, decl); |
| } |
| } |
| } else if (binding instanceof IVariable) { |
| if (shouldReportInMacro(ER_UNUSED_VARIABLE_DECLARATION_ID) || |
| !CxxAstUtils.isInMacro(astName)) { |
| if (storageClass == IASTDeclSpecifier.sc_extern) { |
| // Initializer makes "extern" declaration to become definition do not count these |
| if (decl.getInitializer() == null) { |
| externVariableDeclarations.put(binding, decl); |
| } |
| } else if (storageClass == IASTDeclSpecifier.sc_static) { |
| IType type = ((IVariable) binding).getType(); |
| // Account for class constructor and avoid possible false positive |
| if (!(type instanceof ICPPClassType) && !(type instanceof IProblemType)) { |
| // Check if initializer disqualifies it |
| IASTInitializer initializer = decl.getInitializer(); |
| IASTInitializerClause clause = null; |
| if (initializer instanceof IASTEqualsInitializer) { |
| IASTEqualsInitializer equalsInitializer = (IASTEqualsInitializer) initializer; |
| clause = equalsInitializer.getInitializerClause(); |
| } else if (initializer instanceof ICPPASTConstructorInitializer) { |
| ICPPASTConstructorInitializer constructorInitializer = (ICPPASTConstructorInitializer) initializer; |
| IASTInitializerClause[] args = constructorInitializer.getArguments(); |
| if (args.length == 1) |
| clause = args[0]; |
| } |
| if (clause instanceof IASTLiteralExpression) { |
| IASTLiteralExpression literalExpression = (IASTLiteralExpression) clause; |
| String literal = literalExpression.toString(); |
| if (isFilteredOut(literal, unusedVariableProblem, PARAM_EXCEPT_ARG_LIST)) |
| continue; |
| } |
| |
| staticVariableDeclarations.put(binding, decl); |
| } |
| } |
| } |
| } |
| } |
| } |
| return PROCESS_SKIP; |
| } else if (element instanceof IASTFunctionDefinition) { |
| // definitions |
| IASTFunctionDefinition definition = (IASTFunctionDefinition) element; |
| |
| IASTFunctionDeclarator declarator = definition.getDeclarator(); |
| IASTName astName = declarator.getName(); |
| if (astName != null) { |
| IBinding binding = astName.resolveBinding(); |
| if (hasUsageAttribute(definition.getDeclSpecifier())) { |
| declarationsWithUsageAttributes.add(binding); |
| } |
| |
| if (definition.getDeclSpecifier().getStorageClass() == IASTDeclSpecifier.sc_static && |
| !(astName instanceof ICPPASTQualifiedName)) { |
| staticFunctionDefinitions.put(binding, declarator); |
| } |
| |
| // externFunctionDeclarators filter out |
| externFunctionDeclarations.remove(binding); |
| // staticFunctionDeclarators filter out |
| staticFunctionDeclarations.remove(binding); |
| } |
| } |
| return PROCESS_SKIP; |
| } |
| |
| private boolean hasUsageAttribute(IASTAttributeOwner attributeOwner) { |
| return AttributeUtil.hasAttribute(attributeOwner, USAGE_ATTRIBUTES); |
| } |
| }); |
| } catch (Exception e) { |
| CodanCheckersActivator.log(e); |
| } |
| } |
| |
| private void filterOutUsedElements(IASTTranslationUnit ast) { |
| try { |
| ast.accept(new ASTVisitor() { |
| { |
| shouldVisitNames = true; |
| shouldVisitImplicitNames = true; |
| shouldVisitDeclarations = true; |
| } |
| |
| @Override |
| public int visit(IASTName name) { |
| IBinding binding = name.resolveBinding(); |
| if (binding instanceof ICPPMethod) |
| return PROCESS_CONTINUE; |
| |
| if (binding instanceof IProblemBinding) { |
| // avoid false positives related to unresolved names |
| String plainName = name.toString(); |
| filterOutByPlainName(externFunctionDeclarations, plainName); |
| filterOutByPlainName(staticFunctionDeclarations, plainName); |
| filterOutByPlainName(staticFunctionDefinitions, plainName); |
| filterOutByPlainName(externVariableDeclarations, plainName); |
| filterOutByPlainName(staticVariableDeclarations, plainName); |
| } |
| |
| if (binding instanceof ICPPDeferredFunction) { |
| // Function call inside a template - we don't know which overload(s) |
| // it might match, so to avoid false positives we consider all |
| // candidates to be potentially used. |
| ICPPFunction[] candidates = ((ICPPDeferredFunction) binding).getCandidates(); |
| if (candidates != null) { |
| for (ICPPFunction candidate : candidates) { |
| externFunctionDeclarations.remove(candidate); |
| staticFunctionDefinitions.remove(candidate); |
| } |
| } |
| } |
| |
| IASTNode parentNode = name.getParent(); |
| |
| if (!(parentNode instanceof IASTFunctionDefinition || parentNode instanceof IASTFunctionDeclarator)) { |
| externFunctionDeclarations.remove(binding); |
| staticFunctionDefinitions.remove(binding); |
| } |
| |
| if (parentNode instanceof IASTDeclarator) { |
| // Initializer makes "extern" declaration to become definition. |
| if (((IASTDeclarator) parentNode).getInitializer() != null) { |
| externVariableDeclarations.remove(binding); |
| } |
| } else { |
| externVariableDeclarations.remove(binding); |
| staticVariableDeclarations.remove(binding); |
| } |
| |
| if (declarationsWithUsageAttributes.contains(binding)) { |
| staticFunctionDeclarations.remove(binding); |
| externFunctionDeclarations.remove(binding); |
| staticFunctionDefinitions.remove(binding); |
| externVariableDeclarations.remove(binding); |
| staticVariableDeclarations.remove(binding); |
| } |
| |
| if (!isAnyCandidate()) |
| return PROCESS_ABORT; |
| |
| return PROCESS_CONTINUE; |
| } |
| |
| @Override |
| public int visit(IASTDeclaration declaration) { |
| // Bug 393129: A variable could be used inside assembly code. |
| if (declaration instanceof IASTASMDeclaration) { |
| String assembly = ((IASTASMDeclaration) declaration).getAssembly(); |
| filterOutByAssembly(externFunctionDeclarations, assembly); |
| filterOutByAssembly(staticFunctionDeclarations, assembly); |
| filterOutByAssembly(staticFunctionDefinitions, assembly); |
| filterOutByAssembly(externVariableDeclarations, assembly); |
| filterOutByAssembly(staticVariableDeclarations, assembly); |
| } |
| |
| if (!isAnyCandidate()) |
| return PROCESS_ABORT; |
| |
| return PROCESS_CONTINUE; |
| } |
| |
| private void filterOutByAssembly(Map<IBinding, IASTDeclarator> declarators, String assembly) { |
| Iterator<Entry<IBinding, IASTDeclarator>> iter = declarators.entrySet().iterator(); |
| while (iter.hasNext()) { |
| Entry<IBinding, IASTDeclarator> entry = iter.next(); |
| IASTDeclarator decl = entry.getValue(); |
| IASTName astName = getAstName(decl); |
| if (assembly.contains(astName.toString())) |
| iter.remove(); |
| } |
| } |
| |
| private void filterOutByPlainName(Map<IBinding, IASTDeclarator> declarators, String id) { |
| Iterator<Entry<IBinding, IASTDeclarator>> iter = declarators.entrySet().iterator(); |
| while (iter.hasNext()) { |
| Entry<IBinding, IASTDeclarator> entry = iter.next(); |
| IASTDeclarator decl = entry.getValue(); |
| IASTName astName = getAstName(decl); |
| if (id.equals(astName.toString())) |
| iter.remove(); |
| } |
| } |
| |
| }); |
| } catch (Exception e) { |
| CodanCheckersActivator.log(e); |
| } |
| } |
| |
| private IASTName getAstName(IASTDeclarator decl) { |
| IASTName astName = null; |
| do { |
| astName = decl.getName(); |
| if (astName != null && astName.getSimpleID().length > 0) |
| return astName; |
| |
| // resolve parenthesis if need to |
| decl = decl.getNestedDeclarator(); |
| } while (decl != null); |
| |
| return astName; |
| } |
| |
| private void reportProblems() { |
| List<IASTDeclarator> funcDeclarators = new ArrayList<IASTDeclarator>(); |
| funcDeclarators.addAll(externFunctionDeclarations.values()); |
| funcDeclarators.addAll(staticFunctionDeclarations.values()); |
| for (IASTDeclarator symbol : funcDeclarators) { |
| IASTName astName = getAstName(symbol); |
| if (astName != null) { |
| String symbolName = new String(astName.getSimpleID()); |
| reportProblem(ER_UNUSED_FUNCTION_DECLARATION_ID, astName, symbolName); |
| } |
| } |
| |
| List<IASTDeclarator> varDeclarators = new ArrayList<IASTDeclarator>(); |
| varDeclarators.addAll(externVariableDeclarations.values()); |
| varDeclarators.addAll(staticVariableDeclarations.values()); |
| for (IASTDeclarator symbol : varDeclarators) { |
| IASTName astName = getAstName(symbol); |
| if (astName != null) { |
| String symbolName = new String(astName.getSimpleID()); |
| reportProblem(ER_UNUSED_VARIABLE_DECLARATION_ID, astName, symbolName); |
| } |
| } |
| |
| List<IASTDeclarator> staticFuncDeclarators = new ArrayList<IASTDeclarator>(); |
| staticFuncDeclarators.addAll(staticFunctionDefinitions.values()); |
| for (IASTDeclarator symbol : staticFuncDeclarators) { |
| IASTName astName = getAstName(symbol); |
| if (astName != null) { |
| String symbolName = new String(astName.getSimpleID()); |
| reportProblem(ER_UNUSED_STATIC_FUNCTION_ID, astName, symbolName); |
| } |
| } |
| |
| clearCandidates(); // release memory |
| } |
| |
| private boolean isFilteredOut(String arg, IProblem problem, String exceptionListParamId) { |
| Object[] arr = (Object[]) getPreference(problem, exceptionListParamId); |
| for (int i = 0; i < arr.length; i++) { |
| String str = (String) arr[i]; |
| if (arg.contains(str)) |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean shouldReportInMacro(String errorId) { |
| return (Boolean) getPreference(getProblemById(errorId, getFile()), PARAM_MACRO_ID); |
| } |
| } |