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