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