View Javadoc
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 }