/*******************************************************************************
* Copyright (c) 2011, 2013 Anton Gorenkov 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:
* Anton Gorenkov - initial implementation
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.codan.internal.checkers;
import java.util.HashMap;
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.CheckerLaunchMode;
import org.eclipse.cdt.codan.core.model.IProblemWorkingCopy;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.DOMException;
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.IASTIdExpression;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration;
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.IProblemBinding;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionCallExpression;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamedTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNewExpression;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.core.dom.ast.cpp.SemanticQueries;
import org.eclipse.cdt.core.parser.util.StringUtil;
/**
* Reports a problem if object of a class cannot be created because
* class is abstract (it self or its bases have one or more pure virtual
* functions).
*
* @author Anton Gorenkov
*/
public class AbstractClassInstantiationChecker extends AbstractIndexAstChecker {
public static final String ER_ID = "org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation"; //$NON-NLS-1$
private final HashMap<ICPPClassType, ICPPMethod[]> pureVirtualMethodsCache = new HashMap<ICPPClassType, ICPPMethod[]>();
@Override
public void initPreferences(IProblemWorkingCopy problem) {
super.initPreferences(problem);
// These checkers should not run on full or incremental build.
getLaunchModePreference(problem).enableInLaunchModes(CheckerLaunchMode.RUN_AS_YOU_TYPE,
CheckerLaunchMode.RUN_ON_DEMAND);
}
@Override
public void processAst(IASTTranslationUnit ast) {
try {
ast.accept(new OnEachClass());
} finally {
pureVirtualMethodsCache.clear();
}
}
class OnEachClass extends ASTVisitor {
OnEachClass() {
shouldVisitDeclarations = true;
shouldVisitExpressions = true;
shouldVisitParameterDeclarations = true;
}
@Override
public int visit(IASTDeclaration declaration) {
// Looking for the variables declarations.
if (declaration instanceof IASTSimpleDeclaration) {
// If there is at least one non-pointer and non-reference type...
IASTSimpleDeclaration simpleDecl = (IASTSimpleDeclaration) declaration;
IASTDeclSpecifier declSpec = simpleDecl.getDeclSpecifier();
if (declSpec.getStorageClass() != IASTDeclSpecifier.sc_typedef) {
for (IASTDeclarator declarator : simpleDecl.getDeclarators()) {
if (!hasPointerOrReference(declarator)) {
// ... check whether type is an abstract class.
checkClass(declSpec);
break;
}
}
}
}
return PROCESS_CONTINUE;
}
@Override
public int visit(IASTParameterDeclaration parameterDecl) {
// Looking for parameters declaration. Skip references & pointers.
if (!hasPointerOrReference(parameterDecl.getDeclarator())) {
checkClass(parameterDecl.getDeclSpecifier());
}
return PROCESS_CONTINUE;
}
/**
* Checks whether declarator contains a pinter or reference
*/
private boolean hasPointerOrReference(IASTDeclarator declarator) {
return declarator.getPointerOperators().length != 0;
}
private void checkClass(IASTDeclSpecifier declSpec) {
if (declSpec instanceof ICPPASTNamedTypeSpecifier) {
IASTName className = ((ICPPASTNamedTypeSpecifier) declSpec).getName();
IBinding binding = className.resolveBinding();
if (binding instanceof IType) {
// Resolve class and check whether it is abstract.
reportProblemsIfAbstract((IType) binding, className);
}
}
}
@Override
public int visit(IASTExpression expression) {
if (expression instanceof ICPPASTNewExpression) {
// New expression.
ICPPASTNewExpression newExpression = (ICPPASTNewExpression) expression;
if (!hasPointerOrReference(newExpression.getTypeId().getAbstractDeclarator())) {
// Try to resolve its implicit constructor
IASTDeclSpecifier declSpecifier = newExpression.getTypeId().getDeclSpecifier();
if (declSpecifier instanceof ICPPASTNamedTypeSpecifier) {
IASTName constructorName = ((ICPPASTNamedTypeSpecifier) declSpecifier).getName();
checkClassConstructor(constructorName);
}
}
} else if (expression instanceof ICPPASTFunctionCallExpression) {
// Direct constructor call.
ICPPASTFunctionCallExpression functionCall = (ICPPASTFunctionCallExpression) expression;
IASTExpression functionName = functionCall.getFunctionNameExpression();
if (functionName instanceof IASTIdExpression) {
IASTName constructorName = ((IASTIdExpression) functionName).getName();
checkClassConstructor(constructorName);
}
}
return PROCESS_CONTINUE;
}
/**
* Resolves constructor by AST Name, then get its owner class
* and check whether it is abstract. If it is - report problems
*/
private void checkClassConstructor(IASTName constructorName) {
IBinding binding = constructorName.resolveBinding();
if (binding instanceof ICPPConstructor) {
// Resolve class and check whether it is abstract.
reportProblemsIfAbstract(((ICPPConstructor) binding).getClassOwner(), constructorName);
} else if (binding instanceof IType) {
reportProblemsIfAbstract((IType) binding, constructorName);
}
}
/**
* Tries to resolve qualified name. If it is not available returns simple name.
*/
private String resolveName(ICPPBinding binding) {
try {
if (binding.isGloballyQualified()) {
return StringUtil.join(binding.getQualifiedName(), "::"); //$NON-NLS-1$
}
} catch (DOMException e) {
CodanCheckersActivator.log(e);
}
return binding.getName();
}
/**
* Checks whether specified type (class or typedef to the class) is an abstract class.
* If it is, reports violations on each pure virtual method
*/
private void reportProblemsIfAbstract(IType typeToCheck, IASTNode problemNode) {
IType unwindedType = CxxAstUtils.unwindTypedef(typeToCheck);
if (!(unwindedType instanceof ICPPClassType) || unwindedType instanceof IProblemBinding) {
return;
}
ICPPClassType classType = (ICPPClassType) unwindedType;
ICPPMethod[] pureVirtualMethods = pureVirtualMethodsCache.get(classType);
if (pureVirtualMethods == null) {
pureVirtualMethods = SemanticQueries.getPureVirtualMethods(classType, problemNode);
pureVirtualMethodsCache.put(classType, pureVirtualMethods);
}
for (ICPPMethod method : pureVirtualMethods) {
reportProblem(ER_ID, problemNode, resolveName(classType), resolveName(method));
}
}
}
}