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 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            if (exprStart.getType() == TokenTypes.LITERAL_NULL) {
094                final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
095                log(ident, MSG_KEY, ident.getText(), "null");
096            }
097            if (!onlyObjectReferences) {
098                validateNonObjects(ast);
099            }
100        }
101    }
102
103    /**
104     * Checks for explicit initializations made to 'false', '0' and '\0'.
105     * @param ast token being checked for explicit initializations
106     */
107    private void validateNonObjects(DetailAST ast) {
108        final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
109        final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
110        final DetailAST exprStart =
111                assign.getFirstChild().getFirstChild();
112        final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
113        final int primitiveType = type.getFirstChild().getType();
114        if (primitiveType == TokenTypes.LITERAL_BOOLEAN
115                && exprStart.getType() == TokenTypes.LITERAL_FALSE) {
116            log(ident, MSG_KEY, ident.getText(), "false");
117        }
118        if (isNumericType(primitiveType) && isZero(exprStart)) {
119            log(ident, MSG_KEY, ident.getText(), "0");
120        }
121        if (primitiveType == TokenTypes.LITERAL_CHAR
122                && isZeroChar(exprStart)) {
123            log(ident, MSG_KEY, ident.getText(), "\\0");
124        }
125    }
126
127    /**
128     * Examine char literal for initializing to default value.
129     * @param exprStart expression
130     * @return true is literal is initialized by zero symbol
131     */
132    private static boolean isZeroChar(DetailAST exprStart) {
133        return isZero(exprStart)
134            || exprStart.getType() == TokenTypes.CHAR_LITERAL
135            && "'\\0'".equals(exprStart.getText());
136    }
137
138    /**
139     * Checks for cases that should be skipped: no assignment, local variable, final variables.
140     * @param ast Variable def AST
141     * @return true is that is a case that need to be skipped.
142     */
143    private static boolean isSkipCase(DetailAST ast) {
144        boolean skipCase = true;
145
146        // do not check local variables and
147        // fields declared in interface/annotations
148        if (!ScopeUtil.isLocalVariableDef(ast)
149                && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
150            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
151
152            if (assign != null) {
153                final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
154                skipCase = modifiers.findFirstToken(TokenTypes.FINAL) != null;
155            }
156        }
157        return skipCase;
158    }
159
160    /**
161     * Determine if a given type is a numeric type.
162     * @param type code of the type for check.
163     * @return true if it's a numeric type.
164     * @see TokenTypes
165     */
166    private static boolean isNumericType(int type) {
167        return type == TokenTypes.LITERAL_BYTE
168                || type == TokenTypes.LITERAL_SHORT
169                || type == TokenTypes.LITERAL_INT
170                || type == TokenTypes.LITERAL_FLOAT
171                || type == TokenTypes.LITERAL_LONG
172                || type == TokenTypes.LITERAL_DOUBLE;
173    }
174
175    /**
176     * Checks if given node contains numeric constant for zero.
177     *
178     * @param expr node to check.
179     * @return true if given node contains numeric constant for zero.
180     */
181    private static boolean isZero(DetailAST expr) {
182        final int type = expr.getType();
183        final boolean isZero;
184        switch (type) {
185            case TokenTypes.NUM_FLOAT:
186            case TokenTypes.NUM_DOUBLE:
187            case TokenTypes.NUM_INT:
188            case TokenTypes.NUM_LONG:
189                final String text = expr.getText();
190                isZero = Double.compare(CheckUtil.parseDouble(text, type), 0.0) == 0;
191                break;
192            default:
193                isZero = false;
194        }
195        return isZero;
196    }
197
198}