1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 /**
63 * Creates a new {@code ExplicitInitializationCheck} instance.
64 */
65 public ExplicitInitializationCheck() {
66 // no code by default
67 }
68
69 @Override
70 public final int[] getDefaultTokens() {
71 return getRequiredTokens();
72 }
73
74 @Override
75 public final int[] getRequiredTokens() {
76 return new int[] {TokenTypes.VARIABLE_DEF};
77 }
78
79 @Override
80 public final int[] getAcceptableTokens() {
81 return getRequiredTokens();
82 }
83
84 /**
85 * Setter to control whether only explicit initializations made to null
86 * for objects should be checked.
87 *
88 * @param onlyObjectReferences whether only explicit initialization made to null
89 * should be checked
90 * @since 7.8
91 */
92 public void setOnlyObjectReferences(boolean onlyObjectReferences) {
93 this.onlyObjectReferences = onlyObjectReferences;
94 }
95
96 @Override
97 public void visitToken(DetailAST ast) {
98 if (!isSkipCase(ast)) {
99 final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
100 final DetailAST exprStart =
101 assign.getFirstChild().getFirstChild();
102 if (exprStart.getType() == TokenTypes.LITERAL_NULL) {
103 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
104 log(ident, MSG_KEY, ident.getText(), "null");
105 }
106 if (!onlyObjectReferences) {
107 validateNonObjects(ast);
108 }
109 }
110 }
111
112 /**
113 * Checks for explicit initializations made to 'false', '0' and '\0'.
114 *
115 * @param ast token being checked for explicit initializations
116 */
117 private void validateNonObjects(DetailAST ast) {
118 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
119 final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
120 final DetailAST exprStart =
121 assign.getFirstChild().getFirstChild();
122 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
123 final int primitiveType = type.getFirstChild().getType();
124 if (primitiveType == TokenTypes.LITERAL_BOOLEAN
125 && exprStart.getType() == TokenTypes.LITERAL_FALSE) {
126 log(ident, MSG_KEY, ident.getText(), "false");
127 }
128 if (isNumericType(primitiveType) && isZero(exprStart)) {
129 log(ident, MSG_KEY, ident.getText(), "0");
130 }
131 if (primitiveType == TokenTypes.LITERAL_CHAR
132 && isZeroChar(exprStart)) {
133 log(ident, MSG_KEY, ident.getText(), "\\0");
134 }
135 }
136
137 /**
138 * Examine char literal for initializing to default value.
139 *
140 * @param exprStart expression
141 * @return true is literal is initialized by zero symbol
142 */
143 private static boolean isZeroChar(DetailAST exprStart) {
144 return isZero(exprStart)
145 || "'\\0'".equals(exprStart.getText());
146 }
147
148 /**
149 * Checks for cases that should be skipped: no assignment, local variable, final variables.
150 *
151 * @param ast Variable def AST
152 * @return true is that is a case that need to be skipped.
153 */
154 private static boolean isSkipCase(DetailAST ast) {
155 boolean skipCase = true;
156
157 // do not check local variables and
158 // fields declared in interface/annotations
159 if (!ScopeUtil.isLocalVariableDef(ast)
160 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
161 final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
162
163 if (assign != null) {
164 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
165 skipCase = modifiers.findFirstToken(TokenTypes.FINAL) != null;
166 }
167 }
168 return skipCase;
169 }
170
171 /**
172 * Determine if a given type is a numeric type.
173 *
174 * @param type code of the type for check.
175 * @return true if it's a numeric type.
176 * @see TokenTypes
177 */
178 private static boolean isNumericType(int type) {
179 return type == TokenTypes.LITERAL_BYTE
180 || type == TokenTypes.LITERAL_SHORT
181 || type == TokenTypes.LITERAL_INT
182 || type == TokenTypes.LITERAL_FLOAT
183 || type == TokenTypes.LITERAL_LONG
184 || type == TokenTypes.LITERAL_DOUBLE;
185 }
186
187 /**
188 * Checks if given node contains numeric constant for zero.
189 *
190 * @param expr node to check.
191 * @return true if given node contains numeric constant for zero.
192 */
193 private static boolean isZero(DetailAST expr) {
194 final int type = expr.getType();
195 return switch (type) {
196 case TokenTypes.NUM_FLOAT, TokenTypes.NUM_DOUBLE, TokenTypes.NUM_INT,
197 TokenTypes.NUM_LONG -> {
198 final String text = expr.getText();
199 yield Double.compare(CheckUtil.parseDouble(text, type), 0.0) == 0;
200 }
201 default -> false;
202 };
203 }
204
205 }