001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.CheckUtils;
027import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
028
029/**
030 * <p>
031 * Checks if any class or object member 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 (0 or null) before performing any
041 * initialization specified in the code.  So in this case,
042 * x gets initialized to 0 twice, and bar gets initialized
043 * to null twice.  So there is a minor inefficiency.  This style of
044 * coding is a hold-over from C/C++ style coding,
045 * and it shows that the developer isn't really confident that
046 * Java really initializes instance variables to default
047 * values.
048 * </p>
049 *
050 */
051@StatelessCheck
052public class ExplicitInitializationCheck extends AbstractCheck {
053
054    /**
055     * A key is pointing to the warning message text in "messages.properties"
056     * file.
057     */
058    public static final String MSG_KEY = "explicit.init";
059
060    /** Whether only explicit initialization made to null should be checked.**/
061    private boolean onlyObjectReferences;
062
063    @Override
064    public final int[] getDefaultTokens() {
065        return getRequiredTokens();
066    }
067
068    @Override
069    public final int[] getRequiredTokens() {
070        return new int[] {TokenTypes.VARIABLE_DEF};
071    }
072
073    @Override
074    public final int[] getAcceptableTokens() {
075        return getRequiredTokens();
076    }
077
078    /**
079     * Sets whether only explicit initialization made to null should be checked.
080     * @param onlyObjectReferences whether only explicit initialization made to null
081     *                             should be checked
082     */
083    public void setOnlyObjectReferences(boolean onlyObjectReferences) {
084        this.onlyObjectReferences = onlyObjectReferences;
085    }
086
087    @Override
088    public void visitToken(DetailAST ast) {
089        if (!isSkipCase(ast)) {
090            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
091            final DetailAST exprStart =
092                assign.getFirstChild().getFirstChild();
093            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
094            if (isObjectType(type)
095                && exprStart.getType() == TokenTypes.LITERAL_NULL) {
096                final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
097                log(ident, MSG_KEY, ident.getText(), "null");
098            }
099            if (!onlyObjectReferences) {
100                validateNonObjects(ast);
101            }
102        }
103    }
104
105    /**
106     * Checks for explicit initializations made to 'false', '0' and '\0'.
107     * @param ast token being checked for explicit initializations
108     */
109    private void validateNonObjects(DetailAST ast) {
110        final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
111        final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
112        final DetailAST exprStart =
113                assign.getFirstChild().getFirstChild();
114        final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
115        final int primitiveType = type.getFirstChild().getType();
116        if (primitiveType == TokenTypes.LITERAL_BOOLEAN
117                && exprStart.getType() == TokenTypes.LITERAL_FALSE) {
118            log(ident, MSG_KEY, ident.getText(), "false");
119        }
120        if (isNumericType(primitiveType) && isZero(exprStart)) {
121            log(ident, MSG_KEY, ident.getText(), "0");
122        }
123        if (primitiveType == TokenTypes.LITERAL_CHAR
124                && isZeroChar(exprStart)) {
125            log(ident, MSG_KEY, ident.getText(), "\\0");
126        }
127    }
128
129    /**
130     * Examine char literal for initializing to default value.
131     * @param exprStart expression
132     * @return true is literal is initialized by zero symbol
133     */
134    private static boolean isZeroChar(DetailAST exprStart) {
135        return isZero(exprStart)
136            || exprStart.getType() == TokenTypes.CHAR_LITERAL
137            && "'\\0'".equals(exprStart.getText());
138    }
139
140    /**
141     * Checks for cases that should be skipped: no assignment, local variable, final variables.
142     * @param ast Variable def AST
143     * @return true is that is a case that need to be skipped.
144     */
145    private static boolean isSkipCase(DetailAST ast) {
146        boolean skipCase = true;
147
148        // do not check local variables and
149        // fields declared in interface/annotations
150        if (!ScopeUtils.isLocalVariableDef(ast)
151                && !ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
152            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
153
154            if (assign != null) {
155                final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
156                skipCase = modifiers.findFirstToken(TokenTypes.FINAL) != null;
157            }
158        }
159        return skipCase;
160    }
161
162    /**
163     * Determines if a given type is an object type.
164     * @param type type to check.
165     * @return true if it is an object type.
166     */
167    private static boolean isObjectType(DetailAST type) {
168        final int objectType = type.getFirstChild().getType();
169        return objectType == TokenTypes.IDENT || objectType == TokenTypes.DOT
170                || objectType == TokenTypes.ARRAY_DECLARATOR;
171    }
172
173    /**
174     * Determine if a given type is a numeric type.
175     * @param type code of the type for check.
176     * @return true if it's a numeric type.
177     * @see TokenTypes
178     */
179    private static boolean isNumericType(int type) {
180        return type == TokenTypes.LITERAL_BYTE
181                || type == TokenTypes.LITERAL_SHORT
182                || type == TokenTypes.LITERAL_INT
183                || type == TokenTypes.LITERAL_FLOAT
184                || type == TokenTypes.LITERAL_LONG
185                || type == TokenTypes.LITERAL_DOUBLE;
186    }
187
188    /**
189     * Checks if given node contains numeric constant for zero.
190     *
191     * @param expr node to check.
192     * @return true if given node contains numeric constant for zero.
193     */
194    private static boolean isZero(DetailAST expr) {
195        final int type = expr.getType();
196        final boolean isZero;
197        switch (type) {
198            case TokenTypes.NUM_FLOAT:
199            case TokenTypes.NUM_DOUBLE:
200            case TokenTypes.NUM_INT:
201            case TokenTypes.NUM_LONG:
202                final String text = expr.getText();
203                isZero = Double.compare(CheckUtils.parseDouble(text, type), 0.0) == 0;
204                break;
205            default:
206                isZero = false;
207        }
208        return isZero;
209    }
210
211}