001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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     * @param onlyObjectReferences whether only explicit initialization made to null
131     *                             should be checked
132     */
133    public void setOnlyObjectReferences(boolean onlyObjectReferences) {
134        this.onlyObjectReferences = onlyObjectReferences;
135    }
136
137    @Override
138    public void visitToken(DetailAST ast) {
139        if (!isSkipCase(ast)) {
140            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
141            final DetailAST exprStart =
142                assign.getFirstChild().getFirstChild();
143            if (exprStart.getType() == TokenTypes.LITERAL_NULL) {
144                final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
145                log(ident, MSG_KEY, ident.getText(), "null");
146            }
147            if (!onlyObjectReferences) {
148                validateNonObjects(ast);
149            }
150        }
151    }
152
153    /**
154     * Checks for explicit initializations made to 'false', '0' and '\0'.
155     * @param ast token being checked for explicit initializations
156     */
157    private void validateNonObjects(DetailAST ast) {
158        final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
159        final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
160        final DetailAST exprStart =
161                assign.getFirstChild().getFirstChild();
162        final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
163        final int primitiveType = type.getFirstChild().getType();
164        if (primitiveType == TokenTypes.LITERAL_BOOLEAN
165                && exprStart.getType() == TokenTypes.LITERAL_FALSE) {
166            log(ident, MSG_KEY, ident.getText(), "false");
167        }
168        if (isNumericType(primitiveType) && isZero(exprStart)) {
169            log(ident, MSG_KEY, ident.getText(), "0");
170        }
171        if (primitiveType == TokenTypes.LITERAL_CHAR
172                && isZeroChar(exprStart)) {
173            log(ident, MSG_KEY, ident.getText(), "\\0");
174        }
175    }
176
177    /**
178     * Examine char literal for initializing to default value.
179     * @param exprStart expression
180     * @return true is literal is initialized by zero symbol
181     */
182    private static boolean isZeroChar(DetailAST exprStart) {
183        return isZero(exprStart)
184            || "'\\0'".equals(exprStart.getText());
185    }
186
187    /**
188     * Checks for cases that should be skipped: no assignment, local variable, final variables.
189     * @param ast Variable def AST
190     * @return true is that is a case that need to be skipped.
191     */
192    private static boolean isSkipCase(DetailAST ast) {
193        boolean skipCase = true;
194
195        // do not check local variables and
196        // fields declared in interface/annotations
197        if (!ScopeUtil.isLocalVariableDef(ast)
198                && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
199            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
200
201            if (assign != null) {
202                final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
203                skipCase = modifiers.findFirstToken(TokenTypes.FINAL) != null;
204            }
205        }
206        return skipCase;
207    }
208
209    /**
210     * Determine if a given type is a numeric type.
211     * @param type code of the type for check.
212     * @return true if it's a numeric type.
213     * @see TokenTypes
214     */
215    private static boolean isNumericType(int type) {
216        return type == TokenTypes.LITERAL_BYTE
217                || type == TokenTypes.LITERAL_SHORT
218                || type == TokenTypes.LITERAL_INT
219                || type == TokenTypes.LITERAL_FLOAT
220                || type == TokenTypes.LITERAL_LONG
221                || type == TokenTypes.LITERAL_DOUBLE;
222    }
223
224    /**
225     * Checks if given node contains numeric constant for zero.
226     *
227     * @param expr node to check.
228     * @return true if given node contains numeric constant for zero.
229     */
230    private static boolean isZero(DetailAST expr) {
231        final int type = expr.getType();
232        final boolean isZero;
233        switch (type) {
234            case TokenTypes.NUM_FLOAT:
235            case TokenTypes.NUM_DOUBLE:
236            case TokenTypes.NUM_INT:
237            case TokenTypes.NUM_LONG:
238                final String text = expr.getText();
239                isZero = Double.compare(CheckUtil.parseDouble(text, type), 0.0) == 0;
240                break;
241            default:
242                isZero = false;
243        }
244        return isZero;
245    }
246
247}