001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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 * To configure the check so that it only checks for objects that explicitly initialize to null:
060 * </p>
061 * <pre>
062 * &lt;module name=&quot;ExplicitInitialization&quot;&gt;
063 *   &lt;property name=&quot;onlyObjectReferences&quot; value=&quot;true&quot;/&gt;
064 * &lt;/module&gt;
065 * </pre>
066 * <p>
067 * Example:
068 * </p>
069 * <pre>
070 * public class Test {
071 *   private int a = 0;
072 *   private int b = 1;
073 *   private int c = 2;
074 *
075 *   private boolean a = true;
076 *   private boolean b = false;
077 *   private boolean c = true;
078 *   private boolean d = false;
079 *   private boolean e = false;
080 *
081 *   private A a = new A();
082 *   private A b = null; // violation
083 *   private C c = null; // violation
084 *   private D d = new D();
085 *
086 *   int ar1[] = null; // violation
087 *   int ar2[] = new int[];
088 *   int ar3[];
089 *   private Bar&lt;String&gt; bar = null; // violation
090 *   private Bar&lt;String&gt;[] barArray = null; // violation
091 *
092 *   public static void main( String [] args ) {
093 *   }
094 * }
095 * </pre>
096 * <p>
097 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
098 * </p>
099 * <p>
100 * Violation Message Keys:
101 * </p>
102 * <ul>
103 * <li>
104 * {@code explicit.init}
105 * </li>
106 * </ul>
107 *
108 * @since 3.2
109 */
110@StatelessCheck
111public class ExplicitInitializationCheck extends AbstractCheck {
112
113    /**
114     * A key is pointing to the warning message text in "messages.properties"
115     * file.
116     */
117    public static final String MSG_KEY = "explicit.init";
118
119    /**
120     * Control whether only explicit initializations made to null for objects should be checked.
121     **/
122    private boolean onlyObjectReferences;
123
124    @Override
125    public final int[] getDefaultTokens() {
126        return getRequiredTokens();
127    }
128
129    @Override
130    public final int[] getRequiredTokens() {
131        return new int[] {TokenTypes.VARIABLE_DEF};
132    }
133
134    @Override
135    public final int[] getAcceptableTokens() {
136        return getRequiredTokens();
137    }
138
139    /**
140     * Setter to control whether only explicit initializations made to null
141     * for objects should be checked.
142     *
143     * @param onlyObjectReferences whether only explicit initialization made to null
144     *                             should be checked
145     */
146    public void setOnlyObjectReferences(boolean onlyObjectReferences) {
147        this.onlyObjectReferences = onlyObjectReferences;
148    }
149
150    @Override
151    public void visitToken(DetailAST ast) {
152        if (!isSkipCase(ast)) {
153            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
154            final DetailAST exprStart =
155                assign.getFirstChild().getFirstChild();
156            if (exprStart.getType() == TokenTypes.LITERAL_NULL) {
157                final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
158                log(ident, MSG_KEY, ident.getText(), "null");
159            }
160            if (!onlyObjectReferences) {
161                validateNonObjects(ast);
162            }
163        }
164    }
165
166    /**
167     * Checks for explicit initializations made to 'false', '0' and '\0'.
168     *
169     * @param ast token being checked for explicit initializations
170     */
171    private void validateNonObjects(DetailAST ast) {
172        final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
173        final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
174        final DetailAST exprStart =
175                assign.getFirstChild().getFirstChild();
176        final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
177        final int primitiveType = type.getFirstChild().getType();
178        if (primitiveType == TokenTypes.LITERAL_BOOLEAN
179                && exprStart.getType() == TokenTypes.LITERAL_FALSE) {
180            log(ident, MSG_KEY, ident.getText(), "false");
181        }
182        if (isNumericType(primitiveType) && isZero(exprStart)) {
183            log(ident, MSG_KEY, ident.getText(), "0");
184        }
185        if (primitiveType == TokenTypes.LITERAL_CHAR
186                && isZeroChar(exprStart)) {
187            log(ident, MSG_KEY, ident.getText(), "\\0");
188        }
189    }
190
191    /**
192     * Examine char literal for initializing to default value.
193     *
194     * @param exprStart expression
195     * @return true is literal is initialized by zero symbol
196     */
197    private static boolean isZeroChar(DetailAST exprStart) {
198        return isZero(exprStart)
199            || "'\\0'".equals(exprStart.getText());
200    }
201
202    /**
203     * Checks for cases that should be skipped: no assignment, local variable, final variables.
204     *
205     * @param ast Variable def AST
206     * @return true is that is a case that need to be skipped.
207     */
208    private static boolean isSkipCase(DetailAST ast) {
209        boolean skipCase = true;
210
211        // do not check local variables and
212        // fields declared in interface/annotations
213        if (!ScopeUtil.isLocalVariableDef(ast)
214                && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
215            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
216
217            if (assign != null) {
218                final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
219                skipCase = modifiers.findFirstToken(TokenTypes.FINAL) != null;
220            }
221        }
222        return skipCase;
223    }
224
225    /**
226     * Determine if a given type is a numeric type.
227     *
228     * @param type code of the type for check.
229     * @return true if it's a numeric type.
230     * @see TokenTypes
231     */
232    private static boolean isNumericType(int type) {
233        return type == TokenTypes.LITERAL_BYTE
234                || type == TokenTypes.LITERAL_SHORT
235                || type == TokenTypes.LITERAL_INT
236                || type == TokenTypes.LITERAL_FLOAT
237                || type == TokenTypes.LITERAL_LONG
238                || type == TokenTypes.LITERAL_DOUBLE;
239    }
240
241    /**
242     * Checks if given node contains numeric constant for zero.
243     *
244     * @param expr node to check.
245     * @return true if given node contains numeric constant for zero.
246     */
247    private static boolean isZero(DetailAST expr) {
248        final int type = expr.getType();
249        final boolean isZero;
250        switch (type) {
251            case TokenTypes.NUM_FLOAT:
252            case TokenTypes.NUM_DOUBLE:
253            case TokenTypes.NUM_INT:
254            case TokenTypes.NUM_LONG:
255                final String text = expr.getText();
256                isZero = Double.compare(CheckUtil.parseDouble(text, type), 0.0) == 0;
257                break;
258            default:
259                isZero = false;
260        }
261        return isZero;
262    }
263
264}