Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: a22cc9c15bac5f235737e2db575bfcf052a6f4db (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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
/*******************************************************************************
 * Copyright (c) 2011, 2012 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
 *     Marc-Andre Laperle
 *******************************************************************************/
package org.eclipse.cdt.codan.internal.checkers;

import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

import org.eclipse.cdt.codan.core.cxx.model.AbstractIndexAstChecker;
import org.eclipse.cdt.codan.core.model.IProblemWorkingCopy;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTBinaryExpression;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
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.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IASTUnaryExpression;
import org.eclipse.cdt.core.dom.ast.IBasicType;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.ICompositeType;
import org.eclipse.cdt.core.dom.ast.IEnumeration;
import org.eclipse.cdt.core.dom.ast.IField;
import org.eclipse.cdt.core.dom.ast.IPointerType;
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.ICPPASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUnaryExpression;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPReferenceType;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ClassTypeHelper;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVariableReadWriteFlags;
import org.eclipse.cdt.internal.core.pdom.dom.PDOMName;

/**
 * Checks that class members of simple types (int, float, pointers, 
 * enumeration types, ...) are properly initialized in constructor. 
 * Not initialized members may cause to unstable or random behavior 
 * of methods that are working with their value.
 * 
 * @author Anton Gorenkov
 */
public class ClassMembersInitializationChecker extends AbstractIndexAstChecker {
	public static final String ER_ID = "org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization"; //$NON-NLS-1$
	public static final String PARAM_SKIP = "skip"; //$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<Set<IField>> constructorsStack = new Stack<Set<IField>>();
		private boolean skipConstructorsWithFCalls = skipConstructorsWithFCalls();

		OnEachClass() {
			shouldVisitDeclarations = true;
			shouldVisitNames = true;
			shouldVisitExpressions = true;
		}

		@Override
		public int visit(IASTDeclaration declaration) {
			ICPPConstructor constructor = getConstructor(declaration);
			if (constructor != null) {
				Set<IField> fieldsInConstructor = constructorsStack.push(new HashSet<IField>());
				
				// Add all class fields
				for (IField field : ClassTypeHelper.getDeclaredFields(constructor.getClassOwner(), declaration)) {
					if (isSimpleType(field.getType()) && !field.isStatic()) {
						fieldsInConstructor.add(field);
					}
				}
			}
			return PROCESS_CONTINUE;
		}

		@Override
		public int leave(IASTDeclaration declaration) {
			if (getConstructor(declaration) != null) {
				for (IField field : constructorsStack.pop()) {
					reportProblem(ER_ID, declaration, field.getName());
				}
			}
			return PROCESS_CONTINUE;
		}
		
		@Override
		public int visit(IASTExpression expression) {
			boolean skipCurrentConstructor = false;
			
			if (skipConstructorsWithFCalls && !constructorsStack.empty() && expression instanceof IASTFunctionCallExpression) {
				Set<IField> actualConstructorFields = constructorsStack.peek();
				if (!actualConstructorFields.isEmpty()) {
					IASTFunctionCallExpression fCall = (IASTFunctionCallExpression) expression;
					IASTExpression fNameExp = fCall.getFunctionNameExpression();
					if (fNameExp instanceof IASTIdExpression) {
						IASTIdExpression fName = (IASTIdExpression) fNameExp;
						IBinding fBinding = fName.getName().resolveBinding();
						if (fBinding instanceof ICPPMethod) {
							ICPPMethod method = (ICPPMethod) fBinding;
							ICompositeType constructorOwner = actualConstructorFields.iterator().next().getCompositeTypeOwner();
							if (constructorOwner.equals(method.getClassOwner()) && !method.getType().isConst()) {
								skipCurrentConstructor = true;
							}
						} else if (fBinding instanceof ICPPFunction) {
							for (IASTInitializerClause argument : fCall.getArguments()) {
								if (referencesThis(argument)) {
									skipCurrentConstructor = true;
									break;
								}
							}
						}
					}
				}
			}
			
			// Bug 368420 - Skip constructor if pattern is *this = toBeCopied;
			if (expression instanceof IASTBinaryExpression) {
				IASTBinaryExpression binaryExpression = (IASTBinaryExpression) expression;
				if (referencesThis(binaryExpression.getOperand1()) && binaryExpression.getOperand1().isLValue()) {
					skipCurrentConstructor = true;
				}
			}
			
			if (skipCurrentConstructor && !constructorsStack.empty()) {
				constructorsStack.peek().clear();
			}
			return PROCESS_CONTINUE;
		}
		
		/**
		 * Checks whether expression references this (directly, by pointer or by reference)
		 */
		public 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;
		}

		@Override
		public int visit(IASTName name) {
			if (!constructorsStack.empty()) {
				Set<IField> actualConstructorFields = constructorsStack.peek();
				if (!actualConstructorFields.isEmpty()) {
					IBinding binding = name.resolveBinding();
					if (binding != null && !(binding instanceof IProblemBinding)) {
						IField equivalentFieldBinding = getContainedEquivalentBinding(
								actualConstructorFields, binding, name.getTranslationUnit().getIndex());
						if (equivalentFieldBinding != null) {
							if ((CPPVariableReadWriteFlags.getReadWriteFlags(name) & PDOMName.WRITE_ACCESS) != 0) {
								actualConstructorFields.remove(equivalentFieldBinding);
							}
						}
					}
				}
			}
			return PROCESS_CONTINUE;
		}
		
		private IField getContainedEquivalentBinding(Iterable<IField> fields, IBinding binding, IIndex index) {
			for (IField field : fields) {
				if (areEquivalentBindings(binding, field, index)) {
					return field;
				}
			}
			
			return null;
		}
		
		private boolean areEquivalentBindings(IBinding binding1, IBinding binding2, IIndex index) {
			if (binding1.equals(binding2)) {
				return true;
			}
			if ((binding1 instanceof IIndexBinding) != (binding2 instanceof IIndexBinding) && index != null) {
				if (binding1 instanceof IIndexBinding) {
					binding2 = index.adaptBinding(binding2);
				} else {
					binding1 = index.adaptBinding(binding1);
				}
				if (binding1 == null || binding2 == null) {
					return false;
				}
				if (binding1.equals(binding2)) {
					return true;
				}
			}
			return false;
		}
		
		/** Checks whether class member of the specified type should be initialized
		 * 
		 * @param type	Type to check
		 * @return true if type is:
		 *     - basic type (int, float, ...)
		 *     - pointer
		 *     - enum
		 *     - reference (should be initialized in initialization list)
		 *     - typedef to the another native type.
		 *     
		 * @note: Not supported types (but maybe should be):
		 *     - array
		 *     - union
		 *     - unknown type (need user preference?)
		 *     - template parameter (need user preference?)
		 */
		private boolean isSimpleType(IType type) {
			return (type instanceof IBasicType ||
					type instanceof IPointerType ||
					type instanceof IEnumeration ||
					type instanceof ICPPReferenceType ||
					(type instanceof ITypedef && isSimpleType(((ITypedef) type).getType())));
		}

		/** Checks that specified declaration is a class constructor 
		 *  (it is a class member and its name is equal to 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;
					if (constructor.getClassOwner().getKey() != ICompositeType.k_union) {
						return constructor;
					}
				}
			}
			
			return null;
		}
	}
	
	@Override
	public void initPreferences(IProblemWorkingCopy problem) {
		super.initPreferences(problem);
		addPreference(problem, PARAM_SKIP, CheckersMessages.ClassMembersInitializationChecker_SkipConstructorsWithFCalls, Boolean.TRUE);
	}

	public boolean skipConstructorsWithFCalls() {
		return (Boolean) getPreference(getProblemById(ER_ID, getFile()), PARAM_SKIP);
	}
}

Back to the top