1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-09-08 02:53:12 +02:00

Bug 545956 - Added checker for virtual methods in ctor/dtor

Change-Id: I63b8a40447e9a5b6080e046030677a13607c4ea3
Signed-off-by: Marco Stornelli <marco.stornelli@gmail.com>
This commit is contained in:
Marco Stornelli 2019-03-30 12:25:33 +01:00
parent 1065ee7688
commit 1204bf21b1
5 changed files with 477 additions and 0 deletions

View file

@ -163,3 +163,8 @@ problem.description.MissCaseProblem = This rule will flag switch statements with
problem.name.MissDefaultProblem = Missing default in switch
problem.messagePattern.MissDefaultProblem = When default case is missing the switch is not complete for all values
problem.description.MissDefaultProblem = This rule will flag switch statements without a default case
checker.name.VirtualMethodCallChecker = Virtual method call checker
problem.name.VirtualMethodCallProblem = Virtual method call in constructor/destructor
problem.messagePattern.VirtualMethodCallProblem = Calling a virtual method in constructor or destructor can cause crashes and unexpected behavior
problem.description.VirtualMethodCallProblem = This rule will flag constructor and destructor with virtual method calls

View file

@ -524,5 +524,20 @@
name="%problem.name.MissDefaultProblem">
</problem>
</checker>
<checker
class="org.eclipse.cdt.codan.internal.checkers.VirtualMethodCallChecker"
id="org.eclipse.cdt.codan.internal.checkers.VirtualMethodCallChecker"
name="%checker.name.VirtualMethodCallChecker">
<problem
category="org.eclipse.cdt.codan.core.categories.ProgrammingProblems"
defaultEnabled="false"
defaultSeverity="Error"
description="%problem.description.VirtualMethodCallProblem"
id="org.eclipse.cdt.codan.internal.checkers.VirtualMethodCallProblem"
markerType="org.eclipse.cdt.codan.core.codanProblem"
messagePattern="%problem.messagePattern.VirtualMethodCallProblem"
name="%problem.name.VirtualMethodCallProblem">
</problem>
</checker>
</extension>
</plugin>

View file

@ -0,0 +1,207 @@
/*******************************************************************************
* Copyright (c) 2019 Marco Stornelli
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.cdt.codan.internal.checkers;
import java.util.Stack;
import org.eclipse.cdt.codan.core.cxx.model.AbstractIndexAstChecker;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTFieldReference;
import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
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.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IASTUnaryExpression;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.ICompositeType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTConstructorChainInitializer;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUnaryExpression;
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.internal.core.dom.parser.cpp.ClassTypeHelper;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPDeferredClassInstance;
@SuppressWarnings("restriction")
public class VirtualMethodCallChecker extends AbstractIndexAstChecker {
public static final String VIRTUAL_CALL_ID = "org.eclipse.cdt.codan.internal.checkers.VirtualMethodCallProblem"; //$NON-NLS-1$
@Override
public void processAst(IASTTranslationUnit ast) {
ast.accept(new OnEachClass());
}
class OnEachClass extends ASTVisitor {
// NOTE: Classes can be nested and even can be declared in constructors of the other classes
private final Stack<Boolean> ctorDtorStack = new Stack<>();
OnEachClass() {
shouldVisitDeclarations = true;
shouldVisitExpressions = true;
shouldVisitDeclSpecifiers = true;
}
@Override
public int visit(IASTDeclSpecifier declSpec) {
if (declSpec instanceof ICPPASTCompositeTypeSpecifier && !ctorDtorStack.isEmpty()) {
ctorDtorStack.push(false);
}
return PROCESS_CONTINUE;
}
@Override
public int leave(IASTDeclSpecifier declSpec) {
if (declSpec instanceof ICPPASTCompositeTypeSpecifier && !ctorDtorStack.isEmpty()) {
ctorDtorStack.pop();
}
return PROCESS_CONTINUE;
}
@Override
public int visit(IASTDeclaration declaration) {
ICPPConstructor constructor = getConstructor(declaration);
if (constructor != null) {
ctorDtorStack.push(true);
} else {
ICPPMethod destructor = getDestructor(declaration);
if (destructor != null) {
ctorDtorStack.push(true);
}
}
return PROCESS_CONTINUE;
}
@Override
public int leave(IASTDeclaration declaration) {
if (getConstructor(declaration) != null || getDestructor(declaration) != null) {
ctorDtorStack.pop();
}
return PROCESS_CONTINUE;
}
@Override
public int visit(IASTExpression expression) {
if (!ctorDtorStack.empty() && ctorDtorStack.peek()) {
if (expression instanceof IASTFunctionCallExpression) {
IASTFunctionCallExpression fCall = (IASTFunctionCallExpression) expression;
IASTExpression fNameExp = fCall.getFunctionNameExpression();
IBinding fBinding = null;
IASTNode problemNode = expression;
if (fNameExp instanceof IASTIdExpression) {
IASTIdExpression fName = (IASTIdExpression) fNameExp;
fBinding = fName.getName().resolveBinding();
} else if (fNameExp instanceof IASTFieldReference) {
IASTFieldReference fName = (IASTFieldReference) fNameExp;
problemNode = fName.getFieldName();
if (referencesThis(fName.getFieldOwner()))
fBinding = fName.getFieldName().resolveBinding();
}
if (fBinding instanceof ICPPMethod) {
ICPPMethod method = (ICPPMethod) fBinding;
if (method.isPureVirtual() || ClassTypeHelper.isVirtual(method)) {
reportProblem(VIRTUAL_CALL_ID, problemNode);
}
}
}
}
return PROCESS_CONTINUE;
}
/**
* Checks that specified declaration is a class constructor
* (it is a class member and its name is equal to the class name)
*/
private ICPPConstructor getConstructor(IASTDeclaration decl) {
if (decl instanceof ICPPASTFunctionDefinition) {
ICPPASTFunctionDefinition functionDefinition = (ICPPASTFunctionDefinition) decl;
if (functionDefinition.isDeleted())
return null;
IBinding binding = functionDefinition.getDeclarator().getName().resolveBinding();
if (binding instanceof ICPPConstructor) {
ICPPConstructor constructor = (ICPPConstructor) binding;
// Skip defaulted copy and move constructors.
if (functionDefinition.isDefaulted() && SemanticQueries.isCopyOrMoveConstructor(constructor))
return null;
if (constructor.getClassOwner().getKey() == ICompositeType.k_union)
return null;
// Skip delegating constructors.
for (ICPPASTConstructorChainInitializer memberInitializer : functionDefinition
.getMemberInitializers()) {
IASTName memberName = memberInitializer.getMemberInitializerId();
if (memberName != null) {
IBinding memberBinding = memberName.resolveBinding();
ICPPClassType classType = null;
if (memberBinding instanceof ICPPConstructor) {
classType = ((ICPPConstructor) memberBinding).getClassOwner();
}
if (classType instanceof ICPPDeferredClassInstance) {
classType = ((ICPPDeferredClassInstance) classType).getClassTemplate();
}
if (classType != null && classType.isSameType(constructor.getClassOwner()))
return null;
}
}
return constructor;
}
}
return null;
}
/**
* Checks whether expression references this (directly, by pointer or by reference)
*/
private boolean referencesThis(IASTNode expr) {
if (expr instanceof IASTLiteralExpression) {
IASTLiteralExpression litArg = (IASTLiteralExpression) expr;
if (litArg.getKind() == IASTLiteralExpression.lk_this) {
return true;
}
} else if (expr instanceof ICPPASTUnaryExpression) {
ICPPASTUnaryExpression unExpr = (ICPPASTUnaryExpression) expr;
switch (unExpr.getOperator()) {
case IASTUnaryExpression.op_amper:
case IASTUnaryExpression.op_star:
case IASTUnaryExpression.op_bracketedPrimary:
return referencesThis(unExpr.getOperand());
}
}
return false;
}
/**
* Checks that specified declaration is a class destructor
*/
private ICPPMethod getDestructor(IASTDeclaration decl) {
if (decl instanceof ICPPASTFunctionDefinition) {
ICPPASTFunctionDefinition functionDefinition = (ICPPASTFunctionDefinition) decl;
if (functionDefinition.isDeleted())
return null;
IBinding binding = functionDefinition.getDeclarator().getName().resolveBinding();
if (binding instanceof ICPPMethod) {
ICPPMethod method = (ICPPMethod) binding;
if (method.isDestructor())
return method;
}
}
return null;
}
}
}

View file

@ -0,0 +1,248 @@
/*******************************************************************************
* Copyright (c) 2019 Marco Stornelli
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.cdt.codan.core.internal.checkers;
import org.eclipse.cdt.codan.core.tests.CheckerTestCase;
import org.eclipse.cdt.codan.internal.checkers.VirtualMethodCallChecker;
/**
* Test for {@link VirtualMethodCallChecker} class
*/
public class VirtualMethodCallCheckerTest extends CheckerTestCase {
private static final String ERR_VIRTUAL_ID = VirtualMethodCallChecker.VIRTUAL_CALL_ID;
@Override
public void setUp() throws Exception {
super.setUp();
enableProblems(ERR_VIRTUAL_ID);
}
@Override
public boolean isCpp() {
return true;
}
//class Foo {
//public:
//Foo();
//~Foo();
//virtual void bar();
//virtual void pure() = 0;
//virtual void notpure();
//};
//Foo::Foo() {
// pure();
//}
//Foo::~Foo() {
//}
//Foo::bar() {
//}
public void testWithPureInCtor() throws Exception {
loadCodeAndRun(getAboveComment());
checkErrorLine(10, ERR_VIRTUAL_ID);
}
//class Foo {
//public:
//Foo();
//~Foo();
//virtual void bar();
//virtual void pure() = 0;
//virtual void notpure();
//};
//Foo::Foo() {
// notpure();
//}
//Foo::~Foo() {
//}
//Foo::bar() {
//}
public void testWithNotPureInCtor() throws Exception {
loadCodeAndRun(getAboveComment());
checkErrorLine(10, ERR_VIRTUAL_ID);
}
//class Foo {
//public:
//Foo();
//~Foo();
//virtual void bar();
//virtual void pure() = 0;
//virtual void notpure();
//};
//Foo::Foo() {
//}
//Foo::~Foo() {
// pure();
//}
//Foo::bar() {
//}
public void testWithPureInDtor() throws Exception {
loadCodeAndRun(getAboveComment());
checkErrorLine(12, ERR_VIRTUAL_ID);
}
//class Foo {
//public:
//Foo();
//~Foo();
//virtual void bar();
//virtual void pure() = 0;
//virtual void notpure();
//};
//Foo::Foo() {
//}
//Foo::~Foo() {
// notpure();
//}
//Foo::bar() {
//}
public void testWithNotPureInDtor() throws Exception {
loadCodeAndRun(getAboveComment());
checkErrorLine(12, ERR_VIRTUAL_ID);
}
//class Foo {
//public:
//Foo();
//~Foo();
//virtual void bar();
//virtual void pure() = 0;
//virtual void notpure();
//};
//Foo::Foo() {
//}
//Foo::~Foo() {
//}
//Foo::bar() {
// pure();
// notpure();
//}
public void testWithVirtualInMethod() throws Exception {
loadCodeAndRun(getAboveComment());
checkNoErrorsOfKind(ERR_VIRTUAL_ID);
}
//class A {
//public:
// virtual void v() {}
//};
//class B {
//private:
//A a;
//public:
// B() { a.v(); }
//};
public void testVirtualMethodOtherClass() throws Exception {
loadCodeAndRun(getAboveComment());
checkNoErrorsOfKind(ERR_VIRTUAL_ID);
}
//class B {
//private:
//A a;
//public:
// B() { this->v(); }
// virtual void v() {}
//};
public void testVirtualMethodWithThis() throws Exception {
loadCodeAndRun(getAboveComment());
checkErrorLine(5, ERR_VIRTUAL_ID);
}
//class B {
//private:
//A a;
//public:
// B() { (*this).v(); }
// virtual void v() {}
//};
public void testVirtualMethodWithDerThis() throws Exception {
loadCodeAndRun(getAboveComment());
checkErrorLine(5, ERR_VIRTUAL_ID);
}
//class A {
//public:
// virtual void v() {}
//};
//class B: public A {
//public:
// B() { A::v(); }
//};
public void testVirtualMethodChildClass() throws Exception {
loadCodeAndRun(getAboveComment());
checkErrorLine(7, ERR_VIRTUAL_ID);
}
//class Foo {
//public:
// Foo() {
// class LocalClass {
// LocalClass() {}
// virtual void foo() {
// }
// void func() {
// foo();
// }
// virtual ~LocalClass() {}
// };
// }
//};
public void testVirtualMethodLocalClass() throws Exception {
loadCodeAndRun(getAboveComment());
checkNoErrorsOfKind(ERR_VIRTUAL_ID);
}
//class A {
//public:
// A() : A(5) { foo(); }
// A(int a) : a(a) { }
// virtual void foo();
//private:
// int a;
//};
public void testVirtualMethodDelCtor() throws Exception {
loadCodeAndRun(getAboveComment());
checkNoErrorsOfKind(ERR_VIRTUAL_ID);
}
//class A {
//public:
// A() {
// A a;
// a.foo();
// }
// virtual void foo();
//};
public void testVirtualMethodOtherInstance() throws Exception {
loadCodeAndRun(getAboveComment());
checkNoErrorsOfKind(ERR_VIRTUAL_ID);
}
//class A {
//public:
// A() {
// foo();
// }
// virtual void foo();
// class Nested {
// public:
// Nested() { bar(); };
// virtual void bar();
// }
//};
public void testVirtualMethodNested() throws Exception {
loadCodeAndRun(getAboveComment());
checkErrorLines(4, 9);
}
}

View file

@ -36,6 +36,7 @@ import org.eclipse.cdt.codan.core.internal.checkers.SuggestedParenthesisCheckerT
import org.eclipse.cdt.codan.core.internal.checkers.SuspiciousSemicolonCheckerTest;
import org.eclipse.cdt.codan.core.internal.checkers.SwitchCaseCheckerTest;
import org.eclipse.cdt.codan.core.internal.checkers.UnusedSymbolInFileScopeCheckerTest;
import org.eclipse.cdt.codan.core.internal.checkers.VirtualMethodCallCheckerTest;
import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.AssignmentInConditionQuickFixTest;
import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.CaseBreakQuickFixBreakTest;
import org.eclipse.cdt.codan.internal.checkers.ui.quickfix.CaseBreakQuickFixCommentTest;
@ -90,6 +91,7 @@ public class AutomatedIntegrationSuite extends TestSuite {
suite.addTestSuite(GotoStatementCheckerTest.class);
suite.addTestSuite(CopyrightCheckerTest.class);
suite.addTestSuite(SwitchCaseCheckerTest.class);
suite.addTestSuite(VirtualMethodCallCheckerTest.class);
// framework
suite.addTest(CodanFastTestSuite.suite());
// quick fixes