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