Thu, 07 Mar 2013 10:04:28 +0000
8009138: javac, equals-hashCode warning tuning
Reviewed-by: mcimadamore
1 /*
2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
24 import com.sun.tools.classfile.*;
25 import java.io.*;
26 import javax.lang.model.element.*;
27 import java.util.*;
29 /**
30 * The {@code ClassFileVisitor} reads a class file using the
31 * {@code com.sun.tools.classfile} library. It iterates over the methods
32 * in a class, and checks MethodParameters attributes against JLS
33 * requirements, as well as assumptions about the javac implementations.
34 * <p>
35 * It enforces the following rules:
36 * <ul>
37 * <li>All non-synthetic methods with arguments must have the
38 * MethodParameters attribute. </li>
39 * <li>At most one MethodParameters attribute per method.</li>
40 * <li>An empty MethodParameters attribute is not allowed (i.e. no
41 * attribute for methods taking no parameters).</li>
42 * <li>The number of recorded parameter names much equal the number
43 * of parameters, including any implicit or synthetic parameters generated
44 * by the compiler.</li>
45 * <li>Although the spec allow recording parameters with no name, the javac
46 * implementation is assumed to record a name for all parameters. That is,
47 * the Methodparameters attribute must record a non-zero, valid constant
48 * pool index for each parameter.</li>
49 * <li>Check presence, expected names (e.g. this$N, $enum$name, ...) and flags
50 * (e.g. ACC_SYNTHETIC, ACC_MANDATED) for compiler generated parameters.</li>
51 * <li>Names of explicit parameters must reflect the names in the Java source.
52 * This is checked by assuming a design pattern where any name is permitted
53 * for the first explicit parameter. For subsequent parameters the following
54 * rule is checked: <i>param[n] == ++param[n-1].charAt(0) + param[n-1]</i>
55 * </ul>
56 */
57 class ClassFileVisitor extends Tester.Visitor {
59 Tester tester;
61 public String cname;
62 public boolean isEnum;
63 public boolean isInterface;
64 public boolean isInner;
65 public boolean isPublic;
66 public boolean isStatic;
67 public boolean isAnon;
68 public ClassFile classFile;
71 public ClassFileVisitor(Tester tester) {
72 super(tester);
73 }
75 public void error(String msg) {
76 super.error("classfile: " + msg);
77 }
79 public void warn(String msg) {
80 super.warn("classfile: " + msg);
81 }
83 /**
84 * Read the class and determine some key characteristics, like if it's
85 * an enum, or inner class, etc.
86 */
87 void visitClass(final String cname, final File cfile, final StringBuilder sb)
88 throws Exception {
89 this.cname = cname;
90 classFile = ClassFile.read(cfile);
91 isEnum = classFile.access_flags.is(AccessFlags.ACC_ENUM);
92 isInterface = classFile.access_flags.is(AccessFlags.ACC_INTERFACE);
93 isPublic = classFile.access_flags.is(AccessFlags.ACC_PUBLIC);
94 isInner = false;
95 isStatic = true;
96 isAnon = false;
98 Attribute attr = classFile.getAttribute("InnerClasses");
99 if (attr != null) attr.accept(new InnerClassVisitor(), null);
100 isAnon = isInner & isAnon;
102 sb.append(isStatic ? "static " : "")
103 .append(isPublic ? "public " : "")
104 .append(isEnum ? "enum " : isInterface ? "interface " : "class ")
105 .append(cname).append(" -- ")
106 .append(isInner? "inner " : "" )
107 .append(isAnon ? "anon" : "")
108 .append("\n");;
110 for (Method method : classFile.methods) {
111 new MethodVisitor().visitMethod(method, sb);
112 }
113 }
115 /**
116 * Used to visit InnerClasses_attribute of a class,
117 * to determne if this class is an local class, and anonymous
118 * inner class or a none-static member class. These types of
119 * classes all have an containing class instances field that
120 * requires an implicit or synthetic constructor argument.
121 */
122 class InnerClassVisitor extends AttributeVisitor<Void, Void> {
123 public Void visitInnerClasses(InnerClasses_attribute iattr, Void v) {
124 try{
125 for (InnerClasses_attribute.Info info : iattr.classes) {
126 if (info.getInnerClassInfo(classFile.constant_pool) == null) continue;
127 String in = info.getInnerClassInfo(classFile.constant_pool).getName();
128 if (in == null || !cname.equals(in)) continue;
129 isInner = true;
130 isAnon = null == info.getInnerName(classFile.constant_pool);
131 isStatic = info.inner_class_access_flags.is(AccessFlags.ACC_STATIC);
132 break;
133 }
134 } catch(Exception e) {
135 throw new IllegalStateException(e);
136 }
137 return null;
138 }
139 }
141 /**
142 * Check the MethodParameters attribute of a method.
143 */
144 class MethodVisitor extends AttributeVisitor<Void, StringBuilder> {
146 public String mName;
147 public Descriptor mDesc;
148 public int mParams;
149 public int mAttrs;
150 public int mNumParams;
151 public boolean mSynthetic;
152 public boolean mIsConstructor;
153 public String prefix;
155 void visitMethod(Method method, StringBuilder sb) throws Exception {
157 mName = method.getName(classFile.constant_pool);
158 mDesc = method.descriptor;
159 mParams = mDesc.getParameterCount(classFile.constant_pool);
160 mAttrs = method.attributes.attrs.length;
161 mNumParams = -1; // no MethodParameters attribute found
162 mSynthetic = method.access_flags.is(AccessFlags.ACC_SYNTHETIC);
163 mIsConstructor = mName.equals("<init>");
164 prefix = cname + "." + mName + "() - ";
166 sb.append(cname).append(".").append(mName).append("(");
168 for (Attribute a : method.attributes) {
169 a.accept(this, sb);
170 }
171 if (mNumParams == -1) {
172 if (mSynthetic) {
173 sb.append("<none>)!!");
174 } else {
175 sb.append("<none>)");
176 }
177 }
178 sb.append("\n");
180 // IMPL: methods with arguments must have a MethodParameters
181 // attribute, except possibly some synthetic methods.
182 if (mNumParams == -1 && mParams > 0 && ! mSynthetic) {
183 error(prefix + "missing MethodParameters attribute");
184 }
185 }
187 public Void visitMethodParameters(MethodParameters_attribute mp,
188 StringBuilder sb) {
190 // SPEC: At most one MethodParameters attribute allowed
191 if (mNumParams != -1) {
192 error(prefix + "Multiple MethodParameters attributes");
193 return null;
194 }
196 mNumParams = mp.method_parameter_table_length;
198 // SPEC: An empty attribute is not allowed!
199 if (mNumParams == 0) {
200 error(prefix + "0 length MethodParameters attribute");
201 return null;
202 }
204 // SPEC: one name per parameter.
205 if (mNumParams != mParams) {
206 error(prefix + "found " + mNumParams +
207 " parameters, expected " + mParams);
208 return null;
209 }
211 // IMPL: Whether MethodParameters attributes will be generated
212 // for some synthetics is unresolved. For now, assume no.
213 if (mSynthetic) {
214 warn(prefix + "synthetic has MethodParameter attribute");
215 }
217 String sep = "";
218 String userParam = null;
219 for (int x = 0; x < mNumParams; x++) {
221 // IMPL: Assume all parameters are named, something.
222 int cpi = mp.method_parameter_table[x].name_index;
223 if (cpi == 0) {
224 error(prefix + "name expected, param[" + x + "]");
225 return null;
226 }
228 // SPEC: a non 0 index, must be valid!
229 String param = null;
230 try {
231 param = classFile.constant_pool.getUTF8Value(cpi);
232 sb.append(sep).append(param);
233 sep = ", ";
234 } catch(ConstantPoolException e) {
235 error(prefix + "invalid index " + cpi + " for param["
236 + x + "]");
237 return null;
238 }
241 // Check availability, flags and special names
242 int check = checkParam(mp, param, x, sb);
243 if (check < 0) {
244 return null;
245 }
247 // TEST: check test assumptions about parameter name.
248 // Expected names are calculated starting with the
249 // 2nd explicit (user given) parameter.
250 // param[n] == ++param[n-1].charAt(0) + param[n-1]
251 String expect = null;
252 if (userParam != null) {
253 char c = userParam.charAt(0);
254 expect = (++c) + userParam;
255 }
256 if (check > 0) {
257 userParam = param;
258 }
259 if (expect != null && !param.equals(expect)) {
260 error(prefix + "param[" + x + "]='"
261 + param + "' expected '" + expect + "'");
262 return null;
263 }
264 }
265 if (mSynthetic) {
266 sb.append(")!!");
267 } else {
268 sb.append(")");
269 }
270 return null;
271 }
273 /*
274 * Check a parameter for conformity to JLS and javac specific
275 * assumptions.
276 * Return -1, if an error is detected. Otherwise, return 0, if
277 * the parameter is compiler generated, or 1 for an (presumably)
278 * explicitly declared parameter.
279 */
280 int checkParam(MethodParameters_attribute mp, String param, int index,
281 StringBuilder sb) {
283 boolean synthetic = (mp.method_parameter_table[index].flags
284 & AccessFlags.ACC_SYNTHETIC) != 0;
285 boolean mandated = (mp.method_parameter_table[index].flags
286 & AccessFlags.ACC_MANDATED) != 0;
288 // Setup expectations for flags and special names
289 String expect = null;
290 boolean allowMandated = false;
291 boolean allowSynthetic = false;
292 if (mSynthetic || synthetic) {
293 // not an implementation gurantee, but okay for now
294 expect = "arg" + index; // default
295 }
296 if (mIsConstructor) {
297 if (isEnum) {
298 if (index == 0) {
299 expect = "\\$enum\\$name";
300 allowSynthetic = true;
301 } else if(index == 1) {
302 expect = "\\$enum\\$ordinal";
303 allowSynthetic = true;
304 }
305 } else if (index == 0) {
306 if (isAnon) {
307 allowMandated = true;
308 expect = "this\\$[0-n]*";
309 } else if (isInner && !isStatic) {
310 allowMandated = true;
311 if (!isPublic) {
312 // some but not all non-public inner classes
313 // have synthetic argument. For now we give
314 // the test a bit of slack and allow either.
315 allowSynthetic = true;
316 }
317 expect = "this\\$[0-n]*";
318 }
319 } else if (isAnon) {
320 // not an implementation gurantee, but okay for now
321 expect = "x[0-n]*";
322 }
323 } else if (isEnum && mNumParams == 1 && index == 0 && mName.equals("valueOf")) {
324 expect = "name";
325 allowMandated = true;
326 }
327 if (mandated) sb.append("!");
328 if (synthetic) sb.append("!!");
330 // IMPL: our rules a somewhat fuzzy, sometimes allowing both mandated
331 // and synthetic. However, a parameters cannot be both.
332 if (mandated && synthetic) {
333 error(prefix + "param[" + index + "] == \"" + param
334 + "\" ACC_SYNTHETIC and ACC_MANDATED");
335 return -1;
336 }
337 // ... but must be either, if both "allowed".
338 if (!(mandated || synthetic) && allowMandated && allowSynthetic) {
339 error(prefix + "param[" + index + "] == \"" + param
340 + "\" expected ACC_MANDATED or ACC_SYNTHETIC");
341 return -1;
342 }
344 // ... if only one is "allowed", we meant "required".
345 if (!mandated && allowMandated && !allowSynthetic) {
346 error(prefix + "param[" + index + "] == \"" + param
347 + "\" expected ACC_MANDATED");
348 return -1;
349 }
350 if (!synthetic && !allowMandated && allowSynthetic) {
351 error(prefix + "param[" + index + "] == \"" + param
352 + "\" expected ACC_SYNTHETIC");
353 return -1;
354 }
356 // ... and not "allowed", means prohibited.
357 if (mandated && !allowMandated) {
358 error(prefix + "param[" + index + "] == \"" + param
359 + "\" unexpected, is ACC_MANDATED");
360 return -1;
361 }
362 if (synthetic && !allowSynthetic) {
363 error(prefix + "param[" + index + "] == \"" + param
364 + "\" unexpected, is ACC_SYNTHETIC");
365 return -1;
366 }
368 // Test special name expectations
369 if (expect != null) {
370 if (param.matches(expect)) {
371 return 0;
372 }
373 error(prefix + "param[" + index + "]='" + param +
374 "' expected '" + expect + "'");
375 return -1;
376 }
378 // No further checking for synthetic methods.
379 if (mSynthetic) {
380 return 0;
381 }
383 // Otherwise, do check test parameter naming convention.
384 return 1;
385 }
386 }
387 }