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