1 | //////////////////////////////////////////////////////////////////////////////// | |
2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
3 | // Copyright (C) 2001-2018 the original author or authors. | |
4 | // | |
5 | // This library is free software; you can redistribute it and/or | |
6 | // modify it under the terms of the GNU Lesser General Public | |
7 | // License as published by the Free Software Foundation; either | |
8 | // version 2.1 of the License, or (at your option) any later version. | |
9 | // | |
10 | // This library is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | // Lesser General Public License for more details. | |
14 | // | |
15 | // You should have received a copy of the GNU Lesser General Public | |
16 | // License along with this library; if not, write to the Free Software | |
17 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
18 | //////////////////////////////////////////////////////////////////////////////// | |
19 | ||
20 | package com.puppycrawl.tools.checkstyle.checks.coding; | |
21 | ||
22 | import java.util.Arrays; | |
23 | import java.util.HashSet; | |
24 | import java.util.Set; | |
25 | import java.util.stream.Collectors; | |
26 | ||
27 | import antlr.collections.AST; | |
28 | import com.puppycrawl.tools.checkstyle.FileStatefulCheck; | |
29 | import com.puppycrawl.tools.checkstyle.api.AbstractCheck; | |
30 | import com.puppycrawl.tools.checkstyle.api.DetailAST; | |
31 | import com.puppycrawl.tools.checkstyle.api.FullIdent; | |
32 | import com.puppycrawl.tools.checkstyle.api.TokenTypes; | |
33 | import com.puppycrawl.tools.checkstyle.utils.CommonUtils; | |
34 | ||
35 | /** | |
36 | * <p> | |
37 | * Checks for illegal instantiations where a factory method is preferred. | |
38 | * </p> | |
39 | * <p> | |
40 | * Rationale: Depending on the project, for some classes it might be | |
41 | * preferable to create instances through factory methods rather than | |
42 | * calling the constructor. | |
43 | * </p> | |
44 | * <p> | |
45 | * A simple example is the java.lang.Boolean class, to save memory and CPU | |
46 | * cycles it is preferable to use the predefined constants TRUE and FALSE. | |
47 | * Constructor invocations should be replaced by calls to Boolean.valueOf(). | |
48 | * </p> | |
49 | * <p> | |
50 | * Some extremely performance sensitive projects may require the use of factory | |
51 | * methods for other classes as well, to enforce the usage of number caches or | |
52 | * object pools. | |
53 | * </p> | |
54 | * <p> | |
55 | * Limitations: It is currently not possible to specify array classes. | |
56 | * </p> | |
57 | * <p> | |
58 | * An example of how to configure the check is: | |
59 | * </p> | |
60 | * <pre> | |
61 | * <module name="IllegalInstantiation"/> | |
62 | * </pre> | |
63 | * @author lkuehne | |
64 | */ | |
65 | @FileStatefulCheck | |
66 | public class IllegalInstantiationCheck | |
67 | extends AbstractCheck { | |
68 | ||
69 | /** | |
70 | * A key is pointing to the warning message text in "messages.properties" | |
71 | * file. | |
72 | */ | |
73 | public static final String MSG_KEY = "instantiation.avoid"; | |
74 | ||
75 | /** {@link java.lang} package as string. */ | |
76 | private static final String JAVA_LANG = "java.lang."; | |
77 | ||
78 | /** The imports for the file. */ | |
79 | private final Set<FullIdent> imports = new HashSet<>(); | |
80 | ||
81 | /** The class names defined in the file. */ | |
82 | private final Set<String> classNames = new HashSet<>(); | |
83 | ||
84 | /** The instantiations in the file. */ | |
85 | private final Set<DetailAST> instantiations = new HashSet<>(); | |
86 | ||
87 | /** Set of fully qualified class names. E.g. "java.lang.Boolean" */ | |
88 | private Set<String> classes = new HashSet<>(); | |
89 | ||
90 | /** Name of the package. */ | |
91 | private String pkgName; | |
92 | ||
93 | @Override | |
94 | public int[] getDefaultTokens() { | |
95 |
1
1. getDefaultTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/IllegalInstantiationCheck::getDefaultTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return getAcceptableTokens(); |
96 | } | |
97 | ||
98 | @Override | |
99 | public int[] getAcceptableTokens() { | |
100 |
1
1. getAcceptableTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/IllegalInstantiationCheck::getAcceptableTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return new int[] { |
101 | TokenTypes.IMPORT, | |
102 | TokenTypes.LITERAL_NEW, | |
103 | TokenTypes.PACKAGE_DEF, | |
104 | TokenTypes.CLASS_DEF, | |
105 | }; | |
106 | } | |
107 | ||
108 | @Override | |
109 | public int[] getRequiredTokens() { | |
110 |
1
1. getRequiredTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/IllegalInstantiationCheck::getRequiredTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return new int[] { |
111 | TokenTypes.IMPORT, | |
112 | TokenTypes.LITERAL_NEW, | |
113 | TokenTypes.PACKAGE_DEF, | |
114 | }; | |
115 | } | |
116 | ||
117 | @Override | |
118 | public void beginTree(DetailAST rootAST) { | |
119 | pkgName = null; | |
120 |
1
1. beginTree : removed call to java/util/Set::clear → SURVIVED |
imports.clear(); |
121 |
1
1. beginTree : removed call to java/util/Set::clear → SURVIVED |
instantiations.clear(); |
122 |
1
1. beginTree : removed call to java/util/Set::clear → SURVIVED |
classNames.clear(); |
123 | } | |
124 | ||
125 | @Override | |
126 | public void visitToken(DetailAST ast) { | |
127 | switch (ast.getType()) { | |
128 | case TokenTypes.LITERAL_NEW: | |
129 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/IllegalInstantiationCheck::processLiteralNew → KILLED |
processLiteralNew(ast); |
130 | break; | |
131 | case TokenTypes.PACKAGE_DEF: | |
132 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/IllegalInstantiationCheck::processPackageDef → KILLED |
processPackageDef(ast); |
133 | break; | |
134 | case TokenTypes.IMPORT: | |
135 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/IllegalInstantiationCheck::processImport → KILLED |
processImport(ast); |
136 | break; | |
137 | case TokenTypes.CLASS_DEF: | |
138 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/IllegalInstantiationCheck::processClassDef → SURVIVED |
processClassDef(ast); |
139 | break; | |
140 | default: | |
141 | throw new IllegalArgumentException("Unknown type " + ast); | |
142 | } | |
143 | } | |
144 | ||
145 | @Override | |
146 | public void finishTree(DetailAST rootAST) { | |
147 |
1
1. finishTree : removed call to java/util/Set::forEach → KILLED |
instantiations.forEach(this::postProcessLiteralNew); |
148 | } | |
149 | ||
150 | /** | |
151 | * Collects classes defined in the source file. Required | |
152 | * to avoid false alarms for local vs. java.lang classes. | |
153 | * | |
154 | * @param ast the class def token. | |
155 | */ | |
156 | private void processClassDef(DetailAST ast) { | |
157 | final DetailAST identToken = ast.findFirstToken(TokenTypes.IDENT); | |
158 | final String className = identToken.getText(); | |
159 | classNames.add(className); | |
160 | } | |
161 | ||
162 | /** | |
163 | * Perform processing for an import token. | |
164 | * @param ast the import token | |
165 | */ | |
166 | private void processImport(DetailAST ast) { | |
167 | final FullIdent name = FullIdent.createFullIdentBelow(ast); | |
168 | // Note: different from UnusedImportsCheck.processImport(), | |
169 | // '.*' imports are also added here | |
170 | imports.add(name); | |
171 | } | |
172 | ||
173 | /** | |
174 | * Perform processing for an package token. | |
175 | * @param ast the package token | |
176 | */ | |
177 | private void processPackageDef(DetailAST ast) { | |
178 | final DetailAST packageNameAST = ast.getLastChild() | |
179 | .getPreviousSibling(); | |
180 | final FullIdent packageIdent = | |
181 | FullIdent.createFullIdent(packageNameAST); | |
182 | pkgName = packageIdent.getText(); | |
183 | } | |
184 | ||
185 | /** | |
186 | * Collects a "new" token. | |
187 | * @param ast the "new" token | |
188 | */ | |
189 | private void processLiteralNew(DetailAST ast) { | |
190 |
1
1. processLiteralNew : negated conditional → KILLED |
if (ast.getParent().getType() != TokenTypes.METHOD_REF) { |
191 | instantiations.add(ast); | |
192 | } | |
193 | } | |
194 | ||
195 | /** | |
196 | * Processes one of the collected "new" tokens when walking tree | |
197 | * has finished. | |
198 | * @param newTokenAst the "new" token. | |
199 | */ | |
200 | private void postProcessLiteralNew(DetailAST newTokenAst) { | |
201 | final DetailAST typeNameAst = newTokenAst.getFirstChild(); | |
202 | final AST nameSibling = typeNameAst.getNextSibling(); | |
203 |
1
1. postProcessLiteralNew : negated conditional → KILLED |
if (nameSibling.getType() != TokenTypes.ARRAY_DECLARATOR) { |
204 | // ast != "new Boolean[]" | |
205 | final FullIdent typeIdent = FullIdent.createFullIdent(typeNameAst); | |
206 | final String typeName = typeIdent.getText(); | |
207 | final String fqClassName = getIllegalInstantiation(typeName); | |
208 |
1
1. postProcessLiteralNew : negated conditional → KILLED |
if (fqClassName != null) { |
209 | final int lineNo = newTokenAst.getLineNo(); | |
210 | final int colNo = newTokenAst.getColumnNo(); | |
211 |
1
1. postProcessLiteralNew : removed call to com/puppycrawl/tools/checkstyle/checks/coding/IllegalInstantiationCheck::log → KILLED |
log(lineNo, colNo, MSG_KEY, fqClassName); |
212 | } | |
213 | } | |
214 | } | |
215 | ||
216 | /** | |
217 | * Checks illegal instantiations. | |
218 | * @param className instantiated class, may or may not be qualified | |
219 | * @return the fully qualified class name of className | |
220 | * or null if instantiation of className is OK | |
221 | */ | |
222 | private String getIllegalInstantiation(String className) { | |
223 | String fullClassName = null; | |
224 | ||
225 |
1
1. getIllegalInstantiation : negated conditional → KILLED |
if (classes.contains(className)) { |
226 | fullClassName = className; | |
227 | } | |
228 | else { | |
229 | final int pkgNameLen; | |
230 | ||
231 |
1
1. getIllegalInstantiation : negated conditional → KILLED |
if (pkgName == null) { |
232 | pkgNameLen = 0; | |
233 | } | |
234 | else { | |
235 | pkgNameLen = pkgName.length(); | |
236 | } | |
237 | ||
238 | for (String illegal : classes) { | |
239 |
1
1. getIllegalInstantiation : negated conditional → KILLED |
if (isStandardClass(className, illegal) |
240 |
1
1. getIllegalInstantiation : negated conditional → KILLED |
|| isSamePackage(className, pkgNameLen, illegal)) { |
241 | fullClassName = illegal; | |
242 | } | |
243 | else { | |
244 | fullClassName = checkImportStatements(className); | |
245 | } | |
246 | ||
247 |
1
1. getIllegalInstantiation : negated conditional → KILLED |
if (fullClassName != null) { |
248 | break; | |
249 | } | |
250 | } | |
251 | } | |
252 |
1
1. getIllegalInstantiation : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/IllegalInstantiationCheck::getIllegalInstantiation to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return fullClassName; |
253 | } | |
254 | ||
255 | /** | |
256 | * Check import statements. | |
257 | * @param className name of the class | |
258 | * @return value of illegal instantiated type | |
259 | */ | |
260 | private String checkImportStatements(String className) { | |
261 | String illegalType = null; | |
262 | // import statements | |
263 | for (FullIdent importLineText : imports) { | |
264 | String importArg = importLineText.getText(); | |
265 |
1
1. checkImportStatements : negated conditional → KILLED |
if (importArg.endsWith(".*")) { |
266 |
1
1. checkImportStatements : Replaced integer subtraction with addition → KILLED |
importArg = importArg.substring(0, importArg.length() - 1) |
267 | + className; | |
268 | } | |
269 |
1
1. checkImportStatements : negated conditional → KILLED |
if (CommonUtils.baseClassName(importArg).equals(className) |
270 |
1
1. checkImportStatements : negated conditional → KILLED |
&& classes.contains(importArg)) { |
271 | illegalType = importArg; | |
272 | break; | |
273 | } | |
274 | } | |
275 |
1
1. checkImportStatements : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/IllegalInstantiationCheck::checkImportStatements to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return illegalType; |
276 | } | |
277 | ||
278 | /** | |
279 | * Check that type is of the same package. | |
280 | * @param className class name | |
281 | * @param pkgNameLen package name | |
282 | * @param illegal illegal value | |
283 | * @return true if type of the same package | |
284 | */ | |
285 | private boolean isSamePackage(String className, int pkgNameLen, String illegal) { | |
286 | // class from same package | |
287 | ||
288 | // the top level package (pkgName == null) is covered by the | |
289 | // "illegalInstances.contains(className)" check above | |
290 | ||
291 | // the test is the "no garbage" version of | |
292 | // illegal.equals(pkgName + "." + className) | |
293 |
2
1. isSamePackage : negated conditional → KILLED 2. isSamePackage : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return pkgName != null |
294 |
3
1. isSamePackage : Replaced integer subtraction with addition → KILLED 2. isSamePackage : Replaced integer subtraction with addition → KILLED 3. isSamePackage : negated conditional → KILLED |
&& className.length() == illegal.length() - pkgNameLen - 1 |
295 |
1
1. isSamePackage : negated conditional → KILLED |
&& illegal.charAt(pkgNameLen) == '.' |
296 |
1
1. isSamePackage : negated conditional → KILLED |
&& illegal.endsWith(className) |
297 |
1
1. isSamePackage : negated conditional → KILLED |
&& illegal.startsWith(pkgName); |
298 | } | |
299 | ||
300 | /** | |
301 | * Is class of the same package. | |
302 | * @param className class name | |
303 | * @return true if same package class | |
304 | */ | |
305 | private boolean isSamePackage(String className) { | |
306 | boolean isSamePackage = false; | |
307 | try { | |
308 | final ClassLoader classLoader = getClassLoader(); | |
309 |
1
1. isSamePackage : negated conditional → KILLED |
if (classLoader != null) { |
310 | final String fqName = pkgName + "." + className; | |
311 | classLoader.loadClass(fqName); | |
312 | // no ClassNotFoundException, fqName is a known class | |
313 | isSamePackage = true; | |
314 | } | |
315 | } | |
316 | catch (final ClassNotFoundException ignored) { | |
317 | // not a class from the same package | |
318 | isSamePackage = false; | |
319 | } | |
320 |
1
1. isSamePackage : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return isSamePackage; |
321 | } | |
322 | ||
323 | /** | |
324 | * Is Standard Class. | |
325 | * @param className class name | |
326 | * @param illegal illegal value | |
327 | * @return true if type is standard | |
328 | */ | |
329 | private boolean isStandardClass(String className, String illegal) { | |
330 | boolean isStandardClass = false; | |
331 | // class from java.lang | |
332 |
2
1. isStandardClass : Replaced integer subtraction with addition → KILLED 2. isStandardClass : negated conditional → KILLED |
if (illegal.length() - JAVA_LANG.length() == className.length() |
333 |
1
1. isStandardClass : negated conditional → KILLED |
&& illegal.endsWith(className) |
334 |
1
1. isStandardClass : negated conditional → KILLED |
&& illegal.startsWith(JAVA_LANG)) { |
335 | // java.lang needs no import, but a class without import might | |
336 | // also come from the same file or be in the same package. | |
337 | // E.g. if a class defines an inner class "Boolean", | |
338 | // the expression "new Boolean()" refers to that class, | |
339 | // not to java.lang.Boolean | |
340 | ||
341 | final boolean isSameFile = classNames.contains(className); | |
342 | final boolean isSamePackage = isSamePackage(className); | |
343 | ||
344 |
2
1. isStandardClass : negated conditional → KILLED 2. isStandardClass : negated conditional → KILLED |
if (!isSameFile && !isSamePackage) { |
345 | isStandardClass = true; | |
346 | } | |
347 | } | |
348 |
1
1. isStandardClass : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return isStandardClass; |
349 | } | |
350 | ||
351 | /** | |
352 | * Sets the classes that are illegal to instantiate. | |
353 | * @param names a comma separate list of class names | |
354 | */ | |
355 | public void setClasses(String... names) { | |
356 | classes = Arrays.stream(names).collect(Collectors.toSet()); | |
357 | } | |
358 | ||
359 | } | |
Mutations | ||
95 |
1.1 |
|
100 |
1.1 |
|
110 |
1.1 |
|
120 |
1.1 |
|
121 |
1.1 |
|
122 |
1.1 |
|
129 |
1.1 |
|
132 |
1.1 |
|
135 |
1.1 |
|
138 |
1.1 |
|
147 |
1.1 |
|
190 |
1.1 |
|
203 |
1.1 |
|
208 |
1.1 |
|
211 |
1.1 |
|
225 |
1.1 |
|
231 |
1.1 |
|
239 |
1.1 |
|
240 |
1.1 |
|
247 |
1.1 |
|
252 |
1.1 |
|
265 |
1.1 |
|
266 |
1.1 |
|
269 |
1.1 |
|
270 |
1.1 |
|
275 |
1.1 |
|
293 |
1.1 2.2 |
|
294 |
1.1 2.2 3.3 |
|
295 |
1.1 |
|
296 |
1.1 |
|
297 |
1.1 |
|
309 |
1.1 |
|
320 |
1.1 |
|
332 |
1.1 2.2 |
|
333 |
1.1 |
|
334 |
1.1 |
|
344 |
1.1 2.2 |
|
348 |
1.1 |