001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 * To configure the check: 054 * </p> 055 * <pre> 056 * <module name="ExplicitInitialization"/> 057 * </pre> 058 * <p> 059 * Example: 060 * </p> 061 * <pre> 062 * public class Test { 063 * private int intField1 = 0; // violation 064 * private int intField2 = 1; 065 * private int intField3; 066 * 067 * private char charField1 = '\0'; // violation 068 * private char charField2 = 'b'; 069 * private char charField3; 070 * 071 * private boolean boolField1 = false; // violation 072 * private boolean boolField2 = true; 073 * private boolean boolField3; 074 * 075 * private Obj objField1 = null; // violation 076 * private Obj objField2 = new Obj(); 077 * private Obj objField3; 078 * 079 * private int arrField1[] = null; // violation 080 * private int arrField2[] = new int[10]; 081 * private int arrField3[]; 082 * } 083 * </pre> 084 * <p> 085 * To configure the check so that it only checks for objects that explicitly initialize to null: 086 * </p> 087 * <pre> 088 * <module name="ExplicitInitialization"> 089 * <property name="onlyObjectReferences" value="true"/> 090 * </module> 091 * </pre> 092 * <p> 093 * Example: 094 * </p> 095 * <pre> 096 * public class Test { 097 * private int intField1 = 0; // ignored 098 * private int intField2 = 1; 099 * private int intField3; 100 * 101 * private char charField1 = '\0'; // ignored 102 * private char charField2 = 'b'; 103 * private char charField3; 104 * 105 * private boolean boolField1 = false; // ignored 106 * private boolean boolField2 = true; 107 * private boolean boolField3; 108 * 109 * private Obj objField1 = null; // violation 110 * private Obj objField2 = new Obj(); 111 * private Obj objField3; 112 * 113 * private int arrField1[] = null; // violation 114 * private int arrField2[] = new int[10]; 115 * private int arrField3[]; 116 * } 117 * </pre> 118 * <p> 119 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 120 * </p> 121 * <p> 122 * Violation Message Keys: 123 * </p> 124 * <ul> 125 * <li> 126 * {@code explicit.init} 127 * </li> 128 * </ul> 129 * 130 * @since 3.2 131 */ 132@StatelessCheck 133public class ExplicitInitializationCheck extends AbstractCheck { 134 135 /** 136 * A key is pointing to the warning message text in "messages.properties" 137 * file. 138 */ 139 public static final String MSG_KEY = "explicit.init"; 140 141 /** 142 * Control whether only explicit initializations made to null for objects should be checked. 143 **/ 144 private boolean onlyObjectReferences; 145 146 @Override 147 public final int[] getDefaultTokens() { 148 return getRequiredTokens(); 149 } 150 151 @Override 152 public final int[] getRequiredTokens() { 153 return new int[] {TokenTypes.VARIABLE_DEF}; 154 } 155 156 @Override 157 public final int[] getAcceptableTokens() { 158 return getRequiredTokens(); 159 } 160 161 /** 162 * Setter to control whether only explicit initializations made to null 163 * for objects should be checked. 164 * 165 * @param onlyObjectReferences whether only explicit initialization made to null 166 * should be checked 167 */ 168 public void setOnlyObjectReferences(boolean onlyObjectReferences) { 169 this.onlyObjectReferences = onlyObjectReferences; 170 } 171 172 @Override 173 public void visitToken(DetailAST ast) { 174 if (!isSkipCase(ast)) { 175 final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN); 176 final DetailAST exprStart = 177 assign.getFirstChild().getFirstChild(); 178 if (exprStart.getType() == TokenTypes.LITERAL_NULL) { 179 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT); 180 log(ident, MSG_KEY, ident.getText(), "null"); 181 } 182 if (!onlyObjectReferences) { 183 validateNonObjects(ast); 184 } 185 } 186 } 187 188 /** 189 * Checks for explicit initializations made to 'false', '0' and '\0'. 190 * 191 * @param ast token being checked for explicit initializations 192 */ 193 private void validateNonObjects(DetailAST ast) { 194 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT); 195 final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN); 196 final DetailAST exprStart = 197 assign.getFirstChild().getFirstChild(); 198 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 199 final int primitiveType = type.getFirstChild().getType(); 200 if (primitiveType == TokenTypes.LITERAL_BOOLEAN 201 && exprStart.getType() == TokenTypes.LITERAL_FALSE) { 202 log(ident, MSG_KEY, ident.getText(), "false"); 203 } 204 if (isNumericType(primitiveType) && isZero(exprStart)) { 205 log(ident, MSG_KEY, ident.getText(), "0"); 206 } 207 if (primitiveType == TokenTypes.LITERAL_CHAR 208 && isZeroChar(exprStart)) { 209 log(ident, MSG_KEY, ident.getText(), "\\0"); 210 } 211 } 212 213 /** 214 * Examine char literal for initializing to default value. 215 * 216 * @param exprStart expression 217 * @return true is literal is initialized by zero symbol 218 */ 219 private static boolean isZeroChar(DetailAST exprStart) { 220 return isZero(exprStart) 221 || "'\\0'".equals(exprStart.getText()); 222 } 223 224 /** 225 * Checks for cases that should be skipped: no assignment, local variable, final variables. 226 * 227 * @param ast Variable def AST 228 * @return true is that is a case that need to be skipped. 229 */ 230 private static boolean isSkipCase(DetailAST ast) { 231 boolean skipCase = true; 232 233 // do not check local variables and 234 // fields declared in interface/annotations 235 if (!ScopeUtil.isLocalVariableDef(ast) 236 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 237 final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN); 238 239 if (assign != null) { 240 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 241 skipCase = modifiers.findFirstToken(TokenTypes.FINAL) != null; 242 } 243 } 244 return skipCase; 245 } 246 247 /** 248 * Determine if a given type is a numeric type. 249 * 250 * @param type code of the type for check. 251 * @return true if it's a numeric type. 252 * @see TokenTypes 253 */ 254 private static boolean isNumericType(int type) { 255 return type == TokenTypes.LITERAL_BYTE 256 || type == TokenTypes.LITERAL_SHORT 257 || type == TokenTypes.LITERAL_INT 258 || type == TokenTypes.LITERAL_FLOAT 259 || type == TokenTypes.LITERAL_LONG 260 || type == TokenTypes.LITERAL_DOUBLE; 261 } 262 263 /** 264 * Checks if given node contains numeric constant for zero. 265 * 266 * @param expr node to check. 267 * @return true if given node contains numeric constant for zero. 268 */ 269 private static boolean isZero(DetailAST expr) { 270 final int type = expr.getType(); 271 final boolean isZero; 272 switch (type) { 273 case TokenTypes.NUM_FLOAT: 274 case TokenTypes.NUM_DOUBLE: 275 case TokenTypes.NUM_INT: 276 case TokenTypes.NUM_LONG: 277 final String text = expr.getText(); 278 isZero = Double.compare(CheckUtil.parseDouble(text, type), 0.0) == 0; 279 break; 280 default: 281 isZero = false; 282 } 283 return isZero; 284 } 285 286}