001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
027import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
028
029/**
030 * <p>
031 * Checks if any class or object member is explicitly initialized
032 * to default for its type value ({@code null} for object
033 * references, zero for numeric types and {@code char}
034 * and {@code false} for {@code boolean}.
035 * </p>
036 * <p>
037 * Rationale: Each instance variable gets
038 * initialized twice, to the same value. Java
039 * initializes each instance variable to its default
040 * value ({@code 0} or {@code null}) before performing any
041 * initialization specified in the code.
042 * So there is a minor inefficiency.
043 * </p>
044 * <ul>
045 * <li>
046 * Property {@code onlyObjectReferences} - control whether only explicit
047 * initializations made to null for objects should be checked.
048 * Type is {@code boolean}.
049 * Default value is {@code false}.
050 * </li>
051 * </ul>
052 * <p>
053 * To configure the check:
054 * </p>
055 * <pre>
056 * &lt;module name=&quot;ExplicitInitialization&quot;/&gt;
057 * </pre>
058 * <p>
059 * Example:
060 * </p>
061 * <pre>
062 * public class Test {
063 *   private int intField1 = 0; // violation
064 *   private int intField2 = 1;
065 *   private int intField3;
066 *
067 *   private char charField1 = '\0'; // violation
068 *   private char charField2 = 'b';
069 *   private char charField3;
070 *
071 *   private boolean boolField1 = false; // violation
072 *   private boolean boolField2 = true;
073 *   private boolean boolField3;
074 *
075 *   private Obj objField1 = null; // violation
076 *   private Obj objField2 = new Obj();
077 *   private Obj objField3;
078 *
079 *   private int arrField1[] = null; // violation
080 *   private int arrField2[] = new int[10];
081 *   private int arrField3[];
082 * }
083 * </pre>
084 * <p>
085 * To configure the check so that it only checks for objects that explicitly initialize to null:
086 * </p>
087 * <pre>
088 * &lt;module name=&quot;ExplicitInitialization&quot;&gt;
089 *   &lt;property name=&quot;onlyObjectReferences&quot; value=&quot;true&quot;/&gt;
090 * &lt;/module&gt;
091 * </pre>
092 * <p>
093 * Example:
094 * </p>
095 * <pre>
096 * public class Test {
097 *   private int intField1 = 0; // ignored
098 *   private int intField2 = 1;
099 *   private int intField3;
100 *
101 *   private char charField1 = '\0'; // ignored
102 *   private char charField2 = 'b';
103 *   private char charField3;
104 *
105 *   private boolean boolField1 = false; // ignored
106 *   private boolean boolField2 = true;
107 *   private boolean boolField3;
108 *
109 *   private Obj objField1 = null; // violation
110 *   private Obj objField2 = new Obj();
111 *   private Obj objField3;
112 *
113 *   private int arrField1[] = null; // violation
114 *   private int arrField2[] = new int[10];
115 *   private int arrField3[];
116 * }
117 * </pre>
118 * <p>
119 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
120 * </p>
121 * <p>
122 * Violation Message Keys:
123 * </p>
124 * <ul>
125 * <li>
126 * {@code explicit.init}
127 * </li>
128 * </ul>
129 *
130 * @since 3.2
131 */
132@StatelessCheck
133public class ExplicitInitializationCheck extends AbstractCheck {
134
135    /**
136     * A key is pointing to the warning message text in "messages.properties"
137     * file.
138     */
139    public static final String MSG_KEY = "explicit.init";
140
141    /**
142     * Control whether only explicit initializations made to null for objects should be checked.
143     **/
144    private boolean onlyObjectReferences;
145
146    @Override
147    public final int[] getDefaultTokens() {
148        return getRequiredTokens();
149    }
150
151    @Override
152    public final int[] getRequiredTokens() {
153        return new int[] {TokenTypes.VARIABLE_DEF};
154    }
155
156    @Override
157    public final int[] getAcceptableTokens() {
158        return getRequiredTokens();
159    }
160
161    /**
162     * Setter to control whether only explicit initializations made to null
163     * for objects should be checked.
164     *
165     * @param onlyObjectReferences whether only explicit initialization made to null
166     *                             should be checked
167     */
168    public void setOnlyObjectReferences(boolean onlyObjectReferences) {
169        this.onlyObjectReferences = onlyObjectReferences;
170    }
171
172    @Override
173    public void visitToken(DetailAST ast) {
174        if (!isSkipCase(ast)) {
175            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
176            final DetailAST exprStart =
177                assign.getFirstChild().getFirstChild();
178            if (exprStart.getType() == TokenTypes.LITERAL_NULL) {
179                final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
180                log(ident, MSG_KEY, ident.getText(), "null");
181            }
182            if (!onlyObjectReferences) {
183                validateNonObjects(ast);
184            }
185        }
186    }
187
188    /**
189     * Checks for explicit initializations made to 'false', '0' and '\0'.
190     *
191     * @param ast token being checked for explicit initializations
192     */
193    private void validateNonObjects(DetailAST ast) {
194        final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
195        final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
196        final DetailAST exprStart =
197                assign.getFirstChild().getFirstChild();
198        final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
199        final int primitiveType = type.getFirstChild().getType();
200        if (primitiveType == TokenTypes.LITERAL_BOOLEAN
201                && exprStart.getType() == TokenTypes.LITERAL_FALSE) {
202            log(ident, MSG_KEY, ident.getText(), "false");
203        }
204        if (isNumericType(primitiveType) && isZero(exprStart)) {
205            log(ident, MSG_KEY, ident.getText(), "0");
206        }
207        if (primitiveType == TokenTypes.LITERAL_CHAR
208                && isZeroChar(exprStart)) {
209            log(ident, MSG_KEY, ident.getText(), "\\0");
210        }
211    }
212
213    /**
214     * Examine char literal for initializing to default value.
215     *
216     * @param exprStart expression
217     * @return true is literal is initialized by zero symbol
218     */
219    private static boolean isZeroChar(DetailAST exprStart) {
220        return isZero(exprStart)
221            || "'\\0'".equals(exprStart.getText());
222    }
223
224    /**
225     * Checks for cases that should be skipped: no assignment, local variable, final variables.
226     *
227     * @param ast Variable def AST
228     * @return true is that is a case that need to be skipped.
229     */
230    private static boolean isSkipCase(DetailAST ast) {
231        boolean skipCase = true;
232
233        // do not check local variables and
234        // fields declared in interface/annotations
235        if (!ScopeUtil.isLocalVariableDef(ast)
236                && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
237            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
238
239            if (assign != null) {
240                final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
241                skipCase = modifiers.findFirstToken(TokenTypes.FINAL) != null;
242            }
243        }
244        return skipCase;
245    }
246
247    /**
248     * Determine if a given type is a numeric type.
249     *
250     * @param type code of the type for check.
251     * @return true if it's a numeric type.
252     * @see TokenTypes
253     */
254    private static boolean isNumericType(int type) {
255        return type == TokenTypes.LITERAL_BYTE
256                || type == TokenTypes.LITERAL_SHORT
257                || type == TokenTypes.LITERAL_INT
258                || type == TokenTypes.LITERAL_FLOAT
259                || type == TokenTypes.LITERAL_LONG
260                || type == TokenTypes.LITERAL_DOUBLE;
261    }
262
263    /**
264     * Checks if given node contains numeric constant for zero.
265     *
266     * @param expr node to check.
267     * @return true if given node contains numeric constant for zero.
268     */
269    private static boolean isZero(DetailAST expr) {
270        final int type = expr.getType();
271        final boolean isZero;
272        switch (type) {
273            case TokenTypes.NUM_FLOAT:
274            case TokenTypes.NUM_DOUBLE:
275            case TokenTypes.NUM_INT:
276            case TokenTypes.NUM_LONG:
277                final String text = expr.getText();
278                isZero = Double.compare(CheckUtil.parseDouble(text, type), 0.0) == 0;
279                break;
280            default:
281                isZero = false;
282        }
283        return isZero;
284    }
285
286}