/* * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.*; import com.sun.tools.classfile.*; /** * The {@code ClassFileVisitor} reads a class file using the * {@code com.sun.tools.classfile} library. It iterates over the methods * in a class, and checks MethodParameters attributes against JLS * requirements, as well as assumptions about the javac implementations. * <p> * It enforces the following rules: * <ul> * <li>All non-synthetic methods with arguments must have the * MethodParameters attribute. </li> * <li>At most one MethodParameters attribute per method.</li> * <li>An empty MethodParameters attribute is not allowed (i.e. no * attribute for methods taking no parameters).</li> * <li>The number of recorded parameter names much equal the number * of parameters, including any implicit or synthetic parameters generated * by the compiler.</li> * <li>Although the spec allow recording parameters with no name, the javac * implementation is assumed to record a name for all parameters. That is, * the Methodparameters attribute must record a non-zero, valid constant * pool index for each parameter.</li> * <li>Check presence, expected names (e.g. this$N, $enum$name, ...) and flags * (e.g. ACC_SYNTHETIC, ACC_MANDATED) for compiler generated parameters.</li> * <li>Names of explicit parameters must reflect the names in the Java source. * This is checked by assuming a design pattern where any name is permitted * for the first explicit parameter. For subsequent parameters the following * rule is checked: <i>param[n] == ++param[n-1].charAt(0) + param[n-1]</i> * </ul> */ class ClassFileVisitor extends MethodParametersTester.Visitor { MethodParametersTester tester; public String cname; public boolean isEnum; public boolean isInterface; public boolean isInner; public boolean isPublic; public boolean isStatic; public boolean isAnon; public ClassFile classFile; public ClassFileVisitor(MethodParametersTester tester) { super(tester); } public void error(String msg) { super.error("classfile: " + msg); } public void warn(String msg) { super.warn("classfile: " + msg); } /** * Read the class and determine some key characteristics, like if it's * an enum, or inner class, etc. */ void visitClass(final String cname, final File cfile, final StringBuilder sb) throws Exception { this.cname = cname; classFile = ClassFile.read(cfile); isEnum = classFile.access_flags.is(AccessFlags.ACC_ENUM); isInterface = classFile.access_flags.is(AccessFlags.ACC_INTERFACE); isPublic = classFile.access_flags.is(AccessFlags.ACC_PUBLIC); isInner = false; isStatic = false; isAnon = false; Attribute attr = classFile.getAttribute("InnerClasses"); if (attr != null) attr.accept(new InnerClassVisitor(), null); isAnon = isInner & isAnon; sb.append(isStatic ? "static " : "") .append(isPublic ? "public " : "") .append(isEnum ? "enum " : isInterface ? "interface " : "class ") .append(cname).append(" -- "); if (isInner) { sb.append(isAnon ? "anon" : "inner"); } sb.append("\n"); for (Method method : classFile.methods) { new MethodVisitor().visitMethod(method, sb); } } /** * Used to visit InnerClasses_attribute of a class, * to determne if this class is an local class, and anonymous * inner class or a none-static member class. These types of * classes all have an containing class instances field that * requires an implicit or synthetic constructor argument. */ class InnerClassVisitor extends AttributeVisitor<Void, Void> { public Void visitInnerClasses(InnerClasses_attribute iattr, Void v) { try{ for (InnerClasses_attribute.Info info : iattr.classes) { if (info.getInnerClassInfo(classFile.constant_pool) == null) continue; String in = info.getInnerClassInfo(classFile.constant_pool).getName(); if (in == null || !cname.equals(in)) continue; isInner = true; isAnon = null == info.getInnerName(classFile.constant_pool); isStatic = info.inner_class_access_flags.is(AccessFlags.ACC_STATIC); break; } } catch(Exception e) { throw new IllegalStateException(e); } return null; } } /** * Check the MethodParameters attribute of a method. */ class MethodVisitor extends AttributeVisitor<Void, StringBuilder> { public String mName; public Descriptor mDesc; public int mParams; public int mAttrs; public int mNumParams; public boolean mSynthetic; public boolean mIsConstructor; public boolean mIsClinit; public boolean mIsBridge; public boolean isFinal; public String prefix; void visitMethod(Method method, StringBuilder sb) throws Exception { mName = method.getName(classFile.constant_pool); mDesc = method.descriptor; mParams = mDesc.getParameterCount(classFile.constant_pool); mAttrs = method.attributes.attrs.length; mNumParams = -1; // no MethodParameters attribute found mSynthetic = method.access_flags.is(AccessFlags.ACC_SYNTHETIC); mIsConstructor = mName.equals("<init>"); mIsClinit = mName.equals("<clinit>"); prefix = cname + "." + mName + "() - "; mIsBridge = method.access_flags.is(AccessFlags.ACC_BRIDGE); if (mIsClinit) { sb = new StringBuilder(); // Discard output } sb.append(cname).append(".").append(mName).append("("); for (Attribute a : method.attributes) { a.accept(this, sb); } if (mNumParams == -1) { if (mSynthetic) { // We don't generate MethodParameters attribute for synthetic // methods, so we are creating a parameter pattern to match // ReflectionVisitor API output. for (int i = 0; i < mParams; i++) { if (i == 0) sb.append("arg").append(i); else sb.append(", arg").append(i); } sb.append(")/*synthetic*/"); } else { sb.append(")"); } } sb.append("\n"); // IMPL: methods with arguments must have a MethodParameters // attribute, except possibly some synthetic methods. if (mNumParams == -1 && mParams > 0 && ! mSynthetic) { error(prefix + "missing MethodParameters attribute"); } } public Void visitMethodParameters(MethodParameters_attribute mp, StringBuilder sb) { // SPEC: At most one MethodParameters attribute allowed if (mNumParams != -1) { error(prefix + "Multiple MethodParameters attributes"); return null; } mNumParams = mp.method_parameter_table_length; // SPEC: An empty attribute is not allowed! if (mNumParams == 0) { error(prefix + "0 length MethodParameters attribute"); return null; } // SPEC: one name per parameter. if (mNumParams != mParams) { error(prefix + "found " + mNumParams + " parameters, expected " + mParams); return null; } // IMPL: Whether MethodParameters attributes will be generated // for some synthetics is unresolved. For now, assume no. if (mSynthetic) { warn(prefix + "synthetic has MethodParameter attribute"); } String sep = ""; String userParam = null; for (int x = 0; x < mNumParams; x++) { isFinal = (mp.method_parameter_table[x].flags & AccessFlags.ACC_FINAL) != 0; // IMPL: Assume all parameters are named, something. int cpi = mp.method_parameter_table[x].name_index; if (cpi == 0) { error(prefix + "name expected, param[" + x + "]"); return null; } // SPEC: a non 0 index, must be valid! String param = null; try { param = classFile.constant_pool.getUTF8Value(cpi); if (isFinal) param = "final " + param; sb.append(sep).append(param); sep = ", "; } catch(ConstantPoolException e) { error(prefix + "invalid index " + cpi + " for param[" + x + "]"); return null; } // Check availability, flags and special names int check = checkParam(mp, param, x, sb, isFinal); if (check < 0) { return null; } // TEST: check test assumptions about parameter name. // Expected names are calculated starting with the // 2nd explicit (user given) parameter. // param[n] == ++param[n-1].charAt(0) + param[n-1] String expect = null; if (userParam != null) { char c = userParam.charAt(0); expect = (++c) + userParam; } if(isFinal && expect != null) expect = "final " + expect; if (check > 0) { if(isFinal) { userParam = param.substring(6); } else { userParam = param; } } if (check > 0 && expect != null && !param.equals(expect)) { error(prefix + "param[" + x + "]='" + param + "' expected '" + expect + "'"); return null; } } if (mSynthetic) { sb.append(")/*synthetic*/"); } else { sb.append(")"); } return null; } /* * Check a parameter for conformity to JLS and javac specific * assumptions. * Return -1, if an error is detected. Otherwise, return 0, if * the parameter is compiler generated, or 1 for an (presumably) * explicitly declared parameter. */ int checkParam(MethodParameters_attribute mp, String param, int index, StringBuilder sb, boolean isFinal) { boolean synthetic = (mp.method_parameter_table[index].flags & AccessFlags.ACC_SYNTHETIC) != 0; boolean mandated = (mp.method_parameter_table[index].flags & AccessFlags.ACC_MANDATED) != 0; // Setup expectations for flags and special names String expect = null; boolean allowMandated = false; boolean allowSynthetic = false; if (mSynthetic || synthetic) { // not an implementation gurantee, but okay for now expect = "arg" + index; // default } if (mIsConstructor) { if (isEnum) { if (index == 0) { expect = "\\$enum\\$name"; allowSynthetic = true; } else if(index == 1) { expect = "\\$enum\\$ordinal"; allowSynthetic = true; } } else if (index == 0) { if (isAnon) { expect = "this\\$[0-9]+"; allowMandated = true; if (isFinal) { expect = "final this\\$[0-9]+"; } } else if (isInner && !isStatic) { expect = "this\\$[0-9]+"; allowMandated = true; if (!isPublic) { // some but not all non-public inner classes // have synthetic argument. For now we give // the test a bit of slack and allow either. allowSynthetic = true; } if (isFinal) { expect = "final this\\$[0-9]+"; } } } if (synthetic && !mandated && !allowSynthetic) { //patch treatment for local captures if (isAnon || (isInner & !isStatic)) { expect = "val\\$.*"; allowSynthetic = true; if (isFinal) { expect = "final val\\$.*"; } } } } else if (isEnum && mNumParams == 1 && index == 0 && mName.equals("valueOf")) { expect = "name"; allowMandated = true; } else if (mIsBridge) { allowSynthetic = true; /* you can't expect an special name for bridges' parameters. * The name of the original parameters are now copied. */ expect = null; } if (mandated) sb.append("/*implicit*/"); if (synthetic) sb.append("/*synthetic*/"); // IMPL: our rules a somewhat fuzzy, sometimes allowing both mandated // and synthetic. However, a parameters cannot be both. if (mandated && synthetic) { error(prefix + "param[" + index + "] == \"" + param + "\" ACC_SYNTHETIC and ACC_MANDATED"); return -1; } // ... but must be either, if both "allowed". if (!(mandated || synthetic) && allowMandated && allowSynthetic) { error(prefix + "param[" + index + "] == \"" + param + "\" expected ACC_MANDATED or ACC_SYNTHETIC"); return -1; } // ... if only one is "allowed", we meant "required". if (!mandated && allowMandated && !allowSynthetic) { error(prefix + "param[" + index + "] == \"" + param + "\" expected ACC_MANDATED"); return -1; } if (!synthetic && !allowMandated && allowSynthetic) { error(prefix + "param[" + index + "] == \"" + param + "\" expected ACC_SYNTHETIC"); return -1; } // ... and not "allowed", means prohibited. if (mandated && !allowMandated) { error(prefix + "param[" + index + "] == \"" + param + "\" unexpected, is ACC_MANDATED"); return -1; } if (synthetic && !allowSynthetic) { error(prefix + "param[" + index + "] == \"" + param + "\" unexpected, is ACC_SYNTHETIC"); return -1; } // Test special name expectations if (expect != null) { if (param.matches(expect)) { return 0; } error(prefix + "param[" + index + "]='" + param + "' expected '" + expect + "'"); return -1; } // No further checking for synthetic methods. if (mSynthetic) { return 0; } // Otherwise, do check test parameter naming convention. return 1; } } }