test/tools/javac/MethodParameters/ClassFileVisitor.java

Mon, 20 Jul 2015 11:41:52 -0700

author
asaha
date
Mon, 20 Jul 2015 11:41:52 -0700
changeset 2941
29ef5ee31b3d
parent 2733
7974f6da2d76
child 2893
ca5783d9a597
permissions
-rw-r--r--

Added tag jdk8u65-b06 for changeset ae5e31450299

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

mercurial