1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 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 com.puppycrawl.tools.checkstyle.StatelessCheck;
23 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24 import com.puppycrawl.tools.checkstyle.api.DetailAST;
25 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
27 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
28
29 /**
30 * <div>
31 * Checks if any class or object member is explicitly initialized
32 * to default for its type value ({@code null} for object
33 * references, zero for numeric types and {@code char}
34 * and {@code false} for {@code boolean}.
35 * </div>
36 *
37 * <p>
38 * Rationale: Each instance variable gets
39 * initialized twice, to the same value. Java
40 * initializes each instance variable to its default
41 * value ({@code 0} or {@code null}) before performing any
42 * initialization specified in the code.
43 * So there is a minor inefficiency.
44 * </p>
45 *
46 * @since 3.2
47 */
48 @StatelessCheck
49 public class ExplicitInitializationCheck extends AbstractCheck {
50
51 /**
52 * A key is pointing to the warning message text in "messages.properties"
53 * file.
54 */
55 public static final String MSG_KEY = "explicit.init";
56
57 /**
58 * Control whether only explicit initializations made to null for objects should be checked.
59 **/
60 private boolean onlyObjectReferences;
61
62 @Override
63 public final int[] getDefaultTokens() {
64 return getRequiredTokens();
65 }
66
67 @Override
68 public final int[] getRequiredTokens() {
69 return new int[] {TokenTypes.VARIABLE_DEF};
70 }
71
72 @Override
73 public final int[] getAcceptableTokens() {
74 return getRequiredTokens();
75 }
76
77 /**
78 * Setter to control whether only explicit initializations made to null
79 * for objects should be checked.
80 *
81 * @param onlyObjectReferences whether only explicit initialization made to null
82 * should be checked
83 * @since 7.8
84 */
85 public void setOnlyObjectReferences(boolean onlyObjectReferences) {
86 this.onlyObjectReferences = onlyObjectReferences;
87 }
88
89 @Override
90 public void visitToken(DetailAST ast) {
91 if (!isSkipCase(ast)) {
92 final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
93 final DetailAST exprStart =
94 assign.getFirstChild().getFirstChild();
95 if (exprStart.getType() == TokenTypes.LITERAL_NULL) {
96 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
97 log(ident, MSG_KEY, ident.getText(), "null");
98 }
99 if (!onlyObjectReferences) {
100 validateNonObjects(ast);
101 }
102 }
103 }
104
105 /**
106 * Checks for explicit initializations made to 'false', '0' and '\0'.
107 *
108 * @param ast token being checked for explicit initializations
109 */
110 private void validateNonObjects(DetailAST ast) {
111 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
112 final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
113 final DetailAST exprStart =
114 assign.getFirstChild().getFirstChild();
115 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
116 final int primitiveType = type.getFirstChild().getType();
117 if (primitiveType == TokenTypes.LITERAL_BOOLEAN
118 && exprStart.getType() == TokenTypes.LITERAL_FALSE) {
119 log(ident, MSG_KEY, ident.getText(), "false");
120 }
121 if (isNumericType(primitiveType) && isZero(exprStart)) {
122 log(ident, MSG_KEY, ident.getText(), "0");
123 }
124 if (primitiveType == TokenTypes.LITERAL_CHAR
125 && isZeroChar(exprStart)) {
126 log(ident, MSG_KEY, ident.getText(), "\\0");
127 }
128 }
129
130 /**
131 * Examine char literal for initializing to default value.
132 *
133 * @param exprStart expression
134 * @return true is literal is initialized by zero symbol
135 */
136 private static boolean isZeroChar(DetailAST exprStart) {
137 return isZero(exprStart)
138 || "'\\0'".equals(exprStart.getText());
139 }
140
141 /**
142 * Checks for cases that should be skipped: no assignment, local variable, final variables.
143 *
144 * @param ast Variable def AST
145 * @return true is that is a case that need to be skipped.
146 */
147 private static boolean isSkipCase(DetailAST ast) {
148 boolean skipCase = true;
149
150 // do not check local variables and
151 // fields declared in interface/annotations
152 if (!ScopeUtil.isLocalVariableDef(ast)
153 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
154 final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
155
156 if (assign != null) {
157 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
158 skipCase = modifiers.findFirstToken(TokenTypes.FINAL) != null;
159 }
160 }
161 return skipCase;
162 }
163
164 /**
165 * Determine if a given type is a numeric type.
166 *
167 * @param type code of the type for check.
168 * @return true if it's a numeric type.
169 * @see TokenTypes
170 */
171 private static boolean isNumericType(int type) {
172 return type == TokenTypes.LITERAL_BYTE
173 || type == TokenTypes.LITERAL_SHORT
174 || type == TokenTypes.LITERAL_INT
175 || type == TokenTypes.LITERAL_FLOAT
176 || type == TokenTypes.LITERAL_LONG
177 || type == TokenTypes.LITERAL_DOUBLE;
178 }
179
180 /**
181 * Checks if given node contains numeric constant for zero.
182 *
183 * @param expr node to check.
184 * @return true if given node contains numeric constant for zero.
185 */
186 private static boolean isZero(DetailAST expr) {
187 final int type = expr.getType();
188 return switch (type) {
189 case TokenTypes.NUM_FLOAT, TokenTypes.NUM_DOUBLE, TokenTypes.NUM_INT,
190 TokenTypes.NUM_LONG -> {
191 final String text = expr.getText();
192 yield Double.compare(CheckUtil.parseDouble(text, type), 0.0) == 0;
193 }
194 default -> false;
195 };
196 }
197
198 }