/******************************************************************************* * Copyright (c) 2009, 2011 Alena Laskavaia, Tomasz Wesolowski * 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 - extension *******************************************************************************/ package org.eclipse.cdt.codan.core.cxx; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import org.eclipse.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.IASTBinaryExpression; import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier; 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.IASTExpression; import org.eclipse.cdt.core.dom.ast.IASTExpressionStatement; import org.eclipse.cdt.core.dom.ast.IASTFieldReference; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression; import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator; import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition; import org.eclipse.cdt.core.dom.ast.IASTIdExpression; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNodeSelector; import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion; import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclSpecifier; import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration; import org.eclipse.cdt.core.dom.ast.IASTStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IASTUnaryExpression; import org.eclipse.cdt.core.dom.ast.IBasicType.Kind; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.IFunction; import org.eclipse.cdt.core.dom.ast.INodeFactory; import org.eclipse.cdt.core.dom.ast.IProblemBinding; import org.eclipse.cdt.core.dom.ast.IType; import org.eclipse.cdt.core.dom.ast.ITypedef; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName; import org.eclipse.cdt.core.dom.rewrite.DeclarationGenerator; import org.eclipse.cdt.core.index.IIndex; import org.eclipse.cdt.core.index.IIndexName; import org.eclipse.cdt.core.model.CoreModel; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Path; /** * Useful functions for doing code analysis on c/c++ AST */ public final class CxxAstUtils { public static class NameFinderVisitor extends ASTVisitor { public IASTName name; { shouldVisitNames = true; } @Override public int visit(IASTName name) { this.name = name; return PROCESS_ABORT; } } private class FunctionNameFinderVisitor extends NameFinderVisitor { { shouldVisitExpressions = true; } @Override public int visit(IASTExpression expression) { if(expression instanceof IASTFieldReference) { this.name = ((IASTFieldReference) expression).getFieldName(); return PROCESS_ABORT; } return super.visit(expression); } } private static CxxAstUtils instance; private CxxAstUtils() { // private constructor } public synchronized static CxxAstUtils getInstance() { if (instance == null) instance = new CxxAstUtils(); return instance; } public IType unwindTypedef(IType type) { if (!(type instanceof IBinding)) return type; IBinding typeName = (IBinding) type; // unwind typedef chain try { while (typeName instanceof ITypedef) { IType t = ((ITypedef) typeName).getType(); if (t instanceof IBinding) typeName = (IBinding) t; else return t; } } catch (Exception e) { // in CDT 6.0 getType throws DOMException Activator.log(e); } return (IType) typeName; } public static boolean isInMacro(IASTNode node) { IASTNodeSelector nodeSelector = node.getTranslationUnit().getNodeSelector(node.getTranslationUnit().getFilePath()); IASTFileLocation fileLocation = node.getFileLocation(); IASTPreprocessorMacroExpansion macro = nodeSelector.findEnclosingMacroExpansion(fileLocation.getNodeOffset(), fileLocation.getNodeLength()); return macro != null; } public IASTFunctionDefinition getEnclosingFunction(IASTNode node) { while (node != null && !(node instanceof IASTFunctionDefinition)) { node = node.getParent(); } return (IASTFunctionDefinition) node; } public IASTCompositeTypeSpecifier getEnclosingCompositeTypeSpecifier(IASTNode node) { while (node != null && !(node instanceof IASTCompositeTypeSpecifier)) { node = node.getParent(); } return (IASTCompositeTypeSpecifier) node; } public IASTStatement getEnclosingStatement(IASTNode node) { while (node != null && !(node instanceof IASTStatement)) { node = node.getParent(); } return (IASTStatement) node; } /** * @param astName * a name for the declaration * @param factory * the factory * @param index * @return */ public IASTDeclaration createDeclaration(IASTName astName, INodeFactory factory, IIndex index) { // Depending on context, either a type or a declaration is easier to // infer IType inferredType = null; IASTSimpleDeclaration declaration = null; inferredType = tryInferTypeFromBinaryExpr(astName); if (inferredType == null) declaration = tryInferTypeFromFunctionCall(astName, factory, index); // After the inference, create the statement is needed if (declaration != null) { // A declaration was generated return declaration; } else if (inferredType != null) { // A type was inferred, create the // declaration and return it DeclarationGenerator generator = DeclarationGenerator.create(factory); IASTDeclarator declarator = generator.createDeclaratorFromType(inferredType, astName.toCharArray()); IASTDeclSpecifier declspec = generator.createDeclSpecFromType(inferredType); IASTSimpleDeclaration simpleDeclaration = factory.newSimpleDeclaration(declspec); simpleDeclaration.addDeclarator(declarator); return simpleDeclaration; } else { // Fallback - return a `void` declaration IASTDeclarator declarator = factory.newDeclarator(astName.copy()); IASTSimpleDeclSpecifier declspec = factory.newSimpleDeclSpecifier(); declspec.setType(Kind.eInt); IASTSimpleDeclaration simpleDeclaration = factory.newSimpleDeclaration(declspec); simpleDeclaration.addDeclarator(declarator); return simpleDeclaration; } } /** * For any BinaryExpression, guess the type from the other operand. (A good * guess for =, ==; hard to get a better guess for others) * * @return inferred type or null if couldn't infer */ private IType tryInferTypeFromBinaryExpr(IASTName astName) { if (astName.getParent() instanceof IASTIdExpression && astName.getParent().getParent() instanceof IASTBinaryExpression) { IASTNode binaryExpr = astName.getParent().getParent(); for (IASTNode node : binaryExpr.getChildren()) { if (node != astName.getParent()) { // use this expression as type source return ((IASTExpression) node).getExpressionType(); } } } return null; } /** * For a function call, tries to find a matching function declaration. * Checks the argument count. * * @param index * * @return a generated declaration or null if not suitable */ private IASTSimpleDeclaration tryInferTypeFromFunctionCall(IASTName astName, INodeFactory factory, IIndex index) { if (astName.getParent() instanceof IASTIdExpression && astName.getParent().getParent() instanceof IASTFunctionCallExpression && astName.getParent().getPropertyInParent() == IASTFunctionCallExpression.ARGUMENT) { IASTFunctionCallExpression call = (IASTFunctionCallExpression) astName.getParent().getParent(); FunctionNameFinderVisitor visitor = new FunctionNameFinderVisitor(); call.getFunctionNameExpression().accept(visitor); IASTName funcname = visitor.name; int expectedParametersNum = 0; int targetParameterNum = -1; for (IASTNode n : call.getChildren()) { if (n.getPropertyInParent() == IASTFunctionCallExpression.ARGUMENT) { if (n instanceof IASTIdExpression && n.getChildren()[0] == astName) { targetParameterNum = expectedParametersNum; } expectedParametersNum++; } } if (targetParameterNum == -1) { return null; } IBinding[] bindings; { IBinding binding = funcname.resolveBinding(); if (binding instanceof IProblemBinding) { bindings = ((IProblemBinding) binding).getCandidateBindings(); } else { bindings = new IBinding[] { binding }; } } try { index.acquireReadLock(); Set<IIndexName> declSet = new HashSet<IIndexName>(); // fill declSet with proper declarations for (IBinding b : bindings) { if (b instanceof IFunction) { IFunction f = (IFunction) b; if (f.getParameters().length == expectedParametersNum) { // Consider this overload IIndexName[] decls = index.findDeclarations(b); declSet.addAll(Arrays.asList(decls)); } } } HashMap<ITranslationUnit, IASTTranslationUnit> astCache = new HashMap<ITranslationUnit, IASTTranslationUnit>(); for (IIndexName decl : declSet) { // for now, just use the first overload found ITranslationUnit tu = getTranslationUnitFromIndexName(decl); IASTTranslationUnit ast = null; if(astCache.containsKey(tu)) { ast = astCache.get(tu); } else { ast = tu.getAST(index, ITranslationUnit.AST_SKIP_INDEXED_HEADERS); astCache.put(tu, ast); } IASTName name = (IASTName) ast.getNodeSelector(null).findEnclosingNode(decl.getNodeOffset(), decl.getNodeLength()); IASTNode fdecl = name; while (fdecl instanceof IASTName) { fdecl = fdecl.getParent(); } assert (fdecl instanceof IASTFunctionDeclarator); // find the needed param number int nthParam = 0; for (IASTNode child : fdecl.getChildren()) { if (child instanceof IASTParameterDeclaration) { if (nthParam == targetParameterNum) { IASTParameterDeclaration pd = (IASTParameterDeclaration) child; IASTDeclSpecifier declspec = pd.getDeclSpecifier().copy(); IASTDeclarator declarator = pd.getDeclarator().copy(); setNameInNestedDeclarator(declarator, astName.copy()); IASTSimpleDeclaration declaration = factory.newSimpleDeclaration(declspec); declaration.addDeclarator(declarator); return declaration; } nthParam++; } } name.getParent(); } } catch (InterruptedException e) { // skip } catch (CoreException e) { Activator.log(e); } finally { index.releaseReadLock(); } } return null; } private void setNameInNestedDeclarator(IASTDeclarator declarator, IASTName astName) { while (declarator.getNestedDeclarator() != null) { declarator = declarator.getNestedDeclarator(); } declarator.setName(astName); } public ITranslationUnit getTranslationUnitFromIndexName(IIndexName decl) throws CoreException { Path path = new Path(decl.getFile().getLocation().getFullPath()); IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path); ITranslationUnit tu = (ITranslationUnit) CoreModel.getDefault().create(file); return tu; } /** * If the function definition belongs to a class, returns the class. * Otherwise, returns null. * * @param function * the function definition to check * @param index * the index to use for name lookup * @return Either a type specifier or null */ public IASTCompositeTypeSpecifier getCompositeTypeFromFunction(final IASTFunctionDefinition function, final IIndex index) { // return value to be set via visitor final IASTCompositeTypeSpecifier returnSpecifier[] = { null }; final HashMap<ITranslationUnit, IASTTranslationUnit> astCache = new HashMap<ITranslationUnit, IASTTranslationUnit>(); function.accept(new ASTVisitor() { { shouldVisitDeclarators = true; shouldVisitNames = true; } @Override public int visit(IASTName name) { if (!(name instanceof ICPPASTQualifiedName && name.getParent().getParent() == function)) return PROCESS_CONTINUE; ICPPASTQualifiedName qname = (ICPPASTQualifiedName) name; // A qualified name may have 1 name, but in our case needs to // have 2. // The pre-last name is either a namespace or a class. if (qname.getChildren().length < 2) { return PROCESS_CONTINUE; } IASTName namePart = (IASTName) qname.getChildren()[qname.getChildren().length - 2]; IBinding binding = namePart.resolveBinding(); try { index.acquireReadLock(); IIndexName[] declarations = index.findDeclarations(binding); // Check the declarations and use first suitable for (IIndexName decl : declarations) { ITranslationUnit tu = getTranslationUnitFromIndexName(decl); IASTTranslationUnit ast = null; if(astCache.containsKey(tu)) { ast = astCache.get(tu); } else { ast = tu.getAST(index, ITranslationUnit.AST_SKIP_INDEXED_HEADERS); astCache.put(tu, ast); } IASTNode node = ast.getNodeSelector(null).findEnclosingNode(decl.getNodeOffset(), decl.getNodeLength()); IASTCompositeTypeSpecifier specifier = getEnclosingCompositeTypeSpecifier(node); if (specifier != null) { returnSpecifier[0] = specifier; break; } } } catch (InterruptedException e) { return PROCESS_ABORT; } catch (CoreException e) { Activator.log(e); return PROCESS_ABORT; } finally { index.releaseReadLock(); } return PROCESS_ABORT; } }); return returnSpecifier[0]; } /** * @param body * @return */ public 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; } public 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$ } }