Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: df768a2a8f05127d8a9d6568b3c8712f82aecddd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/*******************************************************************************
 * 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));
			}
		}
	}
}

Back to the top