test/tools/javac/MethodParameters/ClassFileVisitor.java

changeset 0
959103a6100f
child 2525
2eb010b6cb22
equal deleted inserted replaced
-1:000000000000 0:959103a6100f
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 */
23
24 import java.io.*;
25 import com.sun.tools.classfile.*;
26
27 /**
28 * The {@code ClassFileVisitor} reads a class file using the
29 * {@code com.sun.tools.classfile} library. It iterates over the methods
30 * in a class, and checks MethodParameters attributes against JLS
31 * requirements, as well as assumptions about the javac implementations.
32 * <p>
33 * It enforces the following rules:
34 * <ul>
35 * <li>All non-synthetic methods with arguments must have the
36 * MethodParameters attribute. </li>
37 * <li>At most one MethodParameters attribute per method.</li>
38 * <li>An empty MethodParameters attribute is not allowed (i.e. no
39 * attribute for methods taking no parameters).</li>
40 * <li>The number of recorded parameter names much equal the number
41 * of parameters, including any implicit or synthetic parameters generated
42 * by the compiler.</li>
43 * <li>Although the spec allow recording parameters with no name, the javac
44 * implementation is assumed to record a name for all parameters. That is,
45 * the Methodparameters attribute must record a non-zero, valid constant
46 * pool index for each parameter.</li>
47 * <li>Check presence, expected names (e.g. this$N, $enum$name, ...) and flags
48 * (e.g. ACC_SYNTHETIC, ACC_MANDATED) for compiler generated parameters.</li>
49 * <li>Names of explicit parameters must reflect the names in the Java source.
50 * This is checked by assuming a design pattern where any name is permitted
51 * for the first explicit parameter. For subsequent parameters the following
52 * rule is checked: <i>param[n] == ++param[n-1].charAt(0) + param[n-1]</i>
53 * </ul>
54 */
55 class ClassFileVisitor extends Tester.Visitor {
56
57 Tester tester;
58
59 public String cname;
60 public boolean isEnum;
61 public boolean isInterface;
62 public boolean isInner;
63 public boolean isPublic;
64 public boolean isStatic;
65 public boolean isAnon;
66 public ClassFile classFile;
67
68
69 public ClassFileVisitor(Tester tester) {
70 super(tester);
71 }
72
73 public void error(String msg) {
74 super.error("classfile: " + msg);
75 }
76
77 public void warn(String msg) {
78 super.warn("classfile: " + msg);
79 }
80
81 /**
82 * Read the class and determine some key characteristics, like if it's
83 * an enum, or inner class, etc.
84 */
85 void visitClass(final String cname, final File cfile, final StringBuilder sb) throws Exception {
86 this.cname = cname;
87 classFile = ClassFile.read(cfile);
88 isEnum = classFile.access_flags.is(AccessFlags.ACC_ENUM);
89 isInterface = classFile.access_flags.is(AccessFlags.ACC_INTERFACE);
90 isPublic = classFile.access_flags.is(AccessFlags.ACC_PUBLIC);
91 isInner = false;
92 isStatic = false;
93 isAnon = false;
94
95 Attribute attr = classFile.getAttribute("InnerClasses");
96 if (attr != null) attr.accept(new InnerClassVisitor(), null);
97 isAnon = isInner & isAnon;
98
99 sb.append(isStatic ? "static " : "")
100 .append(isPublic ? "public " : "")
101 .append(isEnum ? "enum " : isInterface ? "interface " : "class ")
102 .append(cname).append(" -- ");
103 if (isInner) {
104 sb.append(isAnon ? "anon" : "inner");
105 }
106 sb.append("\n");
107
108 for (Method method : classFile.methods) {
109 new MethodVisitor().visitMethod(method, sb);
110 }
111 }
112
113 /**
114 * Used to visit InnerClasses_attribute of a class,
115 * to determne if this class is an local class, and anonymous
116 * inner class or a none-static member class. These types of
117 * classes all have an containing class instances field that
118 * requires an implicit or synthetic constructor argument.
119 */
120 class InnerClassVisitor extends AttributeVisitor<Void, Void> {
121 public Void visitInnerClasses(InnerClasses_attribute iattr, Void v) {
122 try{
123 for (InnerClasses_attribute.Info info : iattr.classes) {
124 if (info.getInnerClassInfo(classFile.constant_pool) == null) continue;
125 String in = info.getInnerClassInfo(classFile.constant_pool).getName();
126 if (in == null || !cname.equals(in)) continue;
127 isInner = true;
128 isAnon = null == info.getInnerName(classFile.constant_pool);
129 isStatic = info.inner_class_access_flags.is(AccessFlags.ACC_STATIC);
130 break;
131 }
132 } catch(Exception e) {
133 throw new IllegalStateException(e);
134 }
135 return null;
136 }
137 }
138
139 /**
140 * Check the MethodParameters attribute of a method.
141 */
142 class MethodVisitor extends AttributeVisitor<Void, StringBuilder> {
143
144 public String mName;
145 public Descriptor mDesc;
146 public int mParams;
147 public int mAttrs;
148 public int mNumParams;
149 public boolean mSynthetic;
150 public boolean mIsConstructor;
151 public boolean mIsClinit;
152 public boolean mIsBridge;
153 public boolean isFinal;
154 public String prefix;
155
156 void visitMethod(Method method, StringBuilder sb) throws Exception {
157
158 mName = method.getName(classFile.constant_pool);
159 mDesc = method.descriptor;
160 mParams = mDesc.getParameterCount(classFile.constant_pool);
161 mAttrs = method.attributes.attrs.length;
162 mNumParams = -1; // no MethodParameters attribute found
163 mSynthetic = method.access_flags.is(AccessFlags.ACC_SYNTHETIC);
164 mIsConstructor = mName.equals("<init>");
165 mIsClinit = mName.equals("<clinit>");
166 prefix = cname + "." + mName + "() - ";
167 mIsBridge = method.access_flags.is(AccessFlags.ACC_BRIDGE);
168
169 if (mIsClinit) {
170 sb = new StringBuilder(); // Discard output
171 }
172 sb.append(cname).append(".").append(mName).append("(");
173
174 for (Attribute a : method.attributes) {
175 a.accept(this, sb);
176 }
177 if (mNumParams == -1) {
178 if (mSynthetic) {
179 // We don't generate MethodParameters attribute for synthetic
180 // methods, so we are creating a parameter pattern to match
181 // ReflectionVisitor API output.
182 for (int i = 0; i < mParams; i++) {
183 if (i == 0)
184 sb.append("arg").append(i);
185 else
186 sb.append(", arg").append(i);
187 }
188 sb.append(")/*synthetic*/");
189 } else {
190 sb.append(")");
191 }
192 }
193 sb.append("\n");
194
195 // IMPL: methods with arguments must have a MethodParameters
196 // attribute, except possibly some synthetic methods.
197 if (mNumParams == -1 && mParams > 0 && ! mSynthetic) {
198 error(prefix + "missing MethodParameters attribute");
199 }
200 }
201
202 public Void visitMethodParameters(MethodParameters_attribute mp,
203 StringBuilder sb) {
204
205 // SPEC: At most one MethodParameters attribute allowed
206 if (mNumParams != -1) {
207 error(prefix + "Multiple MethodParameters attributes");
208 return null;
209 }
210
211 mNumParams = mp.method_parameter_table_length;
212
213 // SPEC: An empty attribute is not allowed!
214 if (mNumParams == 0) {
215 error(prefix + "0 length MethodParameters attribute");
216 return null;
217 }
218
219 // SPEC: one name per parameter.
220 if (mNumParams != mParams) {
221 error(prefix + "found " + mNumParams +
222 " parameters, expected " + mParams);
223 return null;
224 }
225
226 // IMPL: Whether MethodParameters attributes will be generated
227 // for some synthetics is unresolved. For now, assume no.
228 if (mSynthetic) {
229 warn(prefix + "synthetic has MethodParameter attribute");
230 }
231
232 String sep = "";
233 String userParam = null;
234 for (int x = 0; x < mNumParams; x++) {
235 isFinal = (mp.method_parameter_table[x].flags & AccessFlags.ACC_FINAL) != 0;
236 // IMPL: Assume all parameters are named, something.
237 int cpi = mp.method_parameter_table[x].name_index;
238 if (cpi == 0) {
239 error(prefix + "name expected, param[" + x + "]");
240 return null;
241 }
242
243 // SPEC: a non 0 index, must be valid!
244 String param = null;
245 try {
246 param = classFile.constant_pool.getUTF8Value(cpi);
247 if (isFinal)
248 param = "final " + param;
249 sb.append(sep).append(param);
250 sep = ", ";
251 } catch(ConstantPoolException e) {
252 error(prefix + "invalid index " + cpi + " for param["
253 + x + "]");
254 return null;
255 }
256
257
258 // Check availability, flags and special names
259 int check = checkParam(mp, param, x, sb, isFinal);
260 if (check < 0) {
261 return null;
262 }
263
264 // TEST: check test assumptions about parameter name.
265 // Expected names are calculated starting with the
266 // 2nd explicit (user given) parameter.
267 // param[n] == ++param[n-1].charAt(0) + param[n-1]
268 String expect = null;
269 if (userParam != null) {
270 char c = userParam.charAt(0);
271 expect = (++c) + userParam;
272 }
273 if(isFinal && expect != null)
274 expect = "final " + expect;
275 if (check > 0) {
276 if(isFinal) {
277 userParam = param.substring(6);
278 } else {
279 userParam = param;
280 }
281 }
282 if (expect != null && !param.equals(expect)) {
283 error(prefix + "param[" + x + "]='"
284 + param + "' expected '" + expect + "'");
285 return null;
286 }
287 }
288 if (mSynthetic) {
289 sb.append(")/*synthetic*/");
290 } else {
291 sb.append(")");
292 }
293 return null;
294 }
295
296 /*
297 * Check a parameter for conformity to JLS and javac specific
298 * assumptions.
299 * Return -1, if an error is detected. Otherwise, return 0, if
300 * the parameter is compiler generated, or 1 for an (presumably)
301 * explicitly declared parameter.
302 */
303 int checkParam(MethodParameters_attribute mp, String param, int index,
304 StringBuilder sb, boolean isFinal) {
305
306 boolean synthetic = (mp.method_parameter_table[index].flags
307 & AccessFlags.ACC_SYNTHETIC) != 0;
308 boolean mandated = (mp.method_parameter_table[index].flags
309 & AccessFlags.ACC_MANDATED) != 0;
310
311 // Setup expectations for flags and special names
312 String expect = null;
313 boolean allowMandated = false;
314 boolean allowSynthetic = false;
315 if (mSynthetic || synthetic) {
316 // not an implementation gurantee, but okay for now
317 expect = "arg" + index; // default
318 }
319 if (mIsConstructor) {
320 if (isEnum) {
321 if (index == 0) {
322 expect = "\\$enum\\$name";
323 allowSynthetic = true;
324 } else if(index == 1) {
325 expect = "\\$enum\\$ordinal";
326 allowSynthetic = true;
327 }
328 } else if (index == 0) {
329 if (isAnon) {
330 expect = "this\\$[0-9]+";
331 allowMandated = true;
332 if (isFinal) {
333 expect = "final this\\$[0-9]+";
334 }
335 } else if (isInner && !isStatic) {
336 expect = "this\\$[0-9]+";
337 allowMandated = true;
338 if (!isPublic) {
339 // some but not all non-public inner classes
340 // have synthetic argument. For now we give
341 // the test a bit of slack and allow either.
342 allowSynthetic = true;
343 }
344 if (isFinal) {
345 expect = "final this\\$[0-9]+";
346 }
347 }
348 }
349 } else if (isEnum && mNumParams == 1 && index == 0 && mName.equals("valueOf")) {
350 expect = "name";
351 allowMandated = true;
352 } else if (mIsBridge) {
353 allowSynthetic = true;
354 /* you can't expect an special name for bridges' parameters.
355 * The name of the original parameters are now copied.
356 */
357 expect = null;
358 }
359 if (mandated) sb.append("/*implicit*/");
360 if (synthetic) sb.append("/*synthetic*/");
361
362 // IMPL: our rules a somewhat fuzzy, sometimes allowing both mandated
363 // and synthetic. However, a parameters cannot be both.
364 if (mandated && synthetic) {
365 error(prefix + "param[" + index + "] == \"" + param
366 + "\" ACC_SYNTHETIC and ACC_MANDATED");
367 return -1;
368 }
369 // ... but must be either, if both "allowed".
370 if (!(mandated || synthetic) && allowMandated && allowSynthetic) {
371 error(prefix + "param[" + index + "] == \"" + param
372 + "\" expected ACC_MANDATED or ACC_SYNTHETIC");
373 return -1;
374 }
375
376 // ... if only one is "allowed", we meant "required".
377 if (!mandated && allowMandated && !allowSynthetic) {
378 error(prefix + "param[" + index + "] == \"" + param
379 + "\" expected ACC_MANDATED");
380 return -1;
381 }
382 if (!synthetic && !allowMandated && allowSynthetic) {
383 error(prefix + "param[" + index + "] == \"" + param
384 + "\" expected ACC_SYNTHETIC");
385 return -1;
386 }
387
388 // ... and not "allowed", means prohibited.
389 if (mandated && !allowMandated) {
390 error(prefix + "param[" + index + "] == \"" + param
391 + "\" unexpected, is ACC_MANDATED");
392 return -1;
393 }
394 if (synthetic && !allowSynthetic) {
395 error(prefix + "param[" + index + "] == \"" + param
396 + "\" unexpected, is ACC_SYNTHETIC");
397 return -1;
398 }
399
400 // Test special name expectations
401 if (expect != null) {
402 if (param.matches(expect)) {
403 return 0;
404 }
405 error(prefix + "param[" + index + "]='" + param +
406 "' expected '" + expect + "'");
407 return -1;
408 }
409
410 // No further checking for synthetic methods.
411 if (mSynthetic) {
412 return 0;
413 }
414
415 // Otherwise, do check test parameter naming convention.
416 return 1;
417 }
418 }
419 }

mercurial