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