001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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 *
046 * @since 3.2
047 */
048@StatelessCheck
049public class ExplicitInitializationCheck extends AbstractCheck {
050
051    /**
052     * A key is pointing to the warning message text in "messages.properties"
053     * file.
054     */
055    public static final String MSG_KEY = "explicit.init";
056
057    /**
058     * Control whether only explicit initializations made to null for objects should be checked.
059     **/
060    private boolean onlyObjectReferences;
061
062    @Override
063    public final int[] getDefaultTokens() {
064        return getRequiredTokens();
065    }
066
067    @Override
068    public final int[] getRequiredTokens() {
069        return new int[] {TokenTypes.VARIABLE_DEF};
070    }
071
072    @Override
073    public final int[] getAcceptableTokens() {
074        return getRequiredTokens();
075    }
076
077    /**
078     * Setter to control whether only explicit initializations made to null
079     * for objects should be checked.
080     *
081     * @param onlyObjectReferences whether only explicit initialization made to null
082     *                             should be checked
083     * @since 7.8
084     */
085    public void setOnlyObjectReferences(boolean onlyObjectReferences) {
086        this.onlyObjectReferences = onlyObjectReferences;
087    }
088
089    @Override
090    public void visitToken(DetailAST ast) {
091        if (!isSkipCase(ast)) {
092            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
093            final DetailAST exprStart =
094                assign.getFirstChild().getFirstChild();
095            if (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     *
108     * @param ast token being checked for explicit initializations
109     */
110    private void validateNonObjects(DetailAST ast) {
111        final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
112        final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
113        final DetailAST exprStart =
114                assign.getFirstChild().getFirstChild();
115        final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
116        final int primitiveType = type.getFirstChild().getType();
117        if (primitiveType == TokenTypes.LITERAL_BOOLEAN
118                && exprStart.getType() == TokenTypes.LITERAL_FALSE) {
119            log(ident, MSG_KEY, ident.getText(), "false");
120        }
121        if (isNumericType(primitiveType) && isZero(exprStart)) {
122            log(ident, MSG_KEY, ident.getText(), "0");
123        }
124        if (primitiveType == TokenTypes.LITERAL_CHAR
125                && isZeroChar(exprStart)) {
126            log(ident, MSG_KEY, ident.getText(), "\\0");
127        }
128    }
129
130    /**
131     * Examine char literal for initializing to default value.
132     *
133     * @param exprStart expression
134     * @return true is literal is initialized by zero symbol
135     */
136    private static boolean isZeroChar(DetailAST exprStart) {
137        return isZero(exprStart)
138            || "'\\0'".equals(exprStart.getText());
139    }
140
141    /**
142     * Checks for cases that should be skipped: no assignment, local variable, final variables.
143     *
144     * @param ast Variable def AST
145     * @return true is that is a case that need to be skipped.
146     */
147    private static boolean isSkipCase(DetailAST ast) {
148        boolean skipCase = true;
149
150        // do not check local variables and
151        // fields declared in interface/annotations
152        if (!ScopeUtil.isLocalVariableDef(ast)
153                && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
154            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
155
156            if (assign != null) {
157                final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
158                skipCase = modifiers.findFirstToken(TokenTypes.FINAL) != null;
159            }
160        }
161        return skipCase;
162    }
163
164    /**
165     * Determine if a given type is a numeric type.
166     *
167     * @param type code of the type for check.
168     * @return true if it's a numeric type.
169     * @see TokenTypes
170     */
171    private static boolean isNumericType(int type) {
172        return type == TokenTypes.LITERAL_BYTE
173                || type == TokenTypes.LITERAL_SHORT
174                || type == TokenTypes.LITERAL_INT
175                || type == TokenTypes.LITERAL_FLOAT
176                || type == TokenTypes.LITERAL_LONG
177                || type == TokenTypes.LITERAL_DOUBLE;
178    }
179
180    /**
181     * Checks if given node contains numeric constant for zero.
182     *
183     * @param expr node to check.
184     * @return true if given node contains numeric constant for zero.
185     */
186    private static boolean isZero(DetailAST expr) {
187        final int type = expr.getType();
188        return switch (type) {
189            case TokenTypes.NUM_FLOAT, TokenTypes.NUM_DOUBLE, TokenTypes.NUM_INT,
190                 TokenTypes.NUM_LONG -> {
191                final String text = expr.getText();
192                yield Double.compare(CheckUtil.parseDouble(text, type), 0.0) == 0;
193            }
194            default -> false;
195        };
196    }
197
198}