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}