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