test/tools/javac/MethodParameters/ClassFileVisitor.java

Mon, 16 Oct 2017 16:07:48 +0800

author
aoqi
date
Mon, 16 Oct 2017 16:07:48 +0800
changeset 2893
ca5783d9a597
parent 2733
7974f6da2d76
parent 2525
2eb010b6cb22
permissions
-rw-r--r--

merge

     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 java.io.*;
    25 import com.sun.tools.classfile.*;
    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 {
    57     Tester tester;
    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;
    69     public ClassFileVisitor(Tester tester) {
    70         super(tester);
    71     }
    73     public void error(String msg) {
    74         super.error("classfile: " + msg);
    75     }
    77     public void warn(String msg) {
    78         super.warn("classfile: " + msg);
    79     }
    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;
    95         Attribute attr = classFile.getAttribute("InnerClasses");
    96         if (attr != null) attr.accept(new InnerClassVisitor(), null);
    97         isAnon = isInner & isAnon;
    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");
   108         for (Method method : classFile.methods) {
   109             new MethodVisitor().visitMethod(method, sb);
   110         }
   111     }
   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     }
   139     /**
   140      * Check the MethodParameters attribute of a method.
   141      */
   142     class MethodVisitor extends AttributeVisitor<Void, StringBuilder> {
   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 mIsLambda;
   151         public boolean mIsConstructor;
   152         public boolean mIsClinit;
   153         public boolean mIsBridge;
   154         public boolean isFinal;
   155         public String prefix;
   157         void visitMethod(Method method, StringBuilder sb) throws Exception {
   159             mName = method.getName(classFile.constant_pool);
   160             mDesc = method.descriptor;
   161             mParams =  mDesc.getParameterCount(classFile.constant_pool);
   162             mAttrs = method.attributes.attrs.length;
   163             mNumParams = -1; // no MethodParameters attribute found
   164             mSynthetic = method.access_flags.is(AccessFlags.ACC_SYNTHETIC);
   165             mIsConstructor = mName.equals("<init>");
   166             mIsClinit = mName.equals("<clinit>");
   167             prefix = cname + "." + mName + "() - ";
   168             mIsBridge = method.access_flags.is(AccessFlags.ACC_BRIDGE);
   169             mIsLambda = mSynthetic && mName.startsWith("lambda$");
   171             if (mIsClinit) {
   172                 sb = new StringBuilder(); // Discard output
   173             }
   174             sb.append(cname).append(".").append(mName).append("(");
   176             for (Attribute a : method.attributes) {
   177                 a.accept(this, sb);
   178             }
   179             if (mNumParams == -1) {
   180                 if (mSynthetic) {
   181                     // We don't generate MethodParameters attribute for synthetic
   182                     // methods, so we are creating a parameter pattern to match
   183                     // ReflectionVisitor API output.
   184                     for (int i = 0; i < mParams; i++) {
   185                         if (i == 0)
   186                             sb.append("arg").append(i);
   187                         else
   188                             sb.append(", arg").append(i);
   189                     }
   190                     sb.append(")/*synthetic*/");
   191                 } else {
   192                     sb.append(")");
   193                 }
   194             }
   195             sb.append("\n");
   197             // IMPL: methods with arguments must have a MethodParameters
   198             // attribute, except possibly some synthetic methods.
   199             if (mNumParams == -1 && mParams > 0 && ! mSynthetic) {
   200                 error(prefix + "missing MethodParameters attribute");
   201             }
   202         }
   204         public Void visitMethodParameters(MethodParameters_attribute mp,
   205                                           StringBuilder sb) {
   207             // SPEC: At most one MethodParameters attribute allowed
   208             if (mNumParams != -1) {
   209                 error(prefix + "Multiple MethodParameters attributes");
   210                 return null;
   211             }
   213             mNumParams = mp.method_parameter_table_length;
   215             // SPEC: An empty attribute is not allowed!
   216             if (mNumParams == 0) {
   217                 error(prefix + "0 length MethodParameters attribute");
   218                 return null;
   219             }
   221             // SPEC: one name per parameter.
   222             if (mNumParams != mParams) {
   223                 error(prefix + "found " + mNumParams +
   224                       " parameters, expected " + mParams);
   225                 return null;
   226             }
   228             // IMPL: Whether MethodParameters attributes will be generated
   229             // for some synthetics is unresolved. For now, assume no.
   230             if (mSynthetic && !mIsLambda) {
   231                 warn(prefix + "synthetic has MethodParameter attribute");
   232             }
   234             String sep = "";
   235             String userParam = null;
   236             for (int x = 0; x <  mNumParams; x++) {
   237                 isFinal = (mp.method_parameter_table[x].flags & AccessFlags.ACC_FINAL) != 0;
   238                 // IMPL: Assume all parameters are named, something.
   239                 int cpi = mp.method_parameter_table[x].name_index;
   240                 if (cpi == 0) {
   241                     error(prefix + "name expected, param[" + x + "]");
   242                     return null;
   243                 }
   245                 // SPEC: a non 0 index, must be valid!
   246                 String param = null;
   247                 try {
   248                     param = classFile.constant_pool.getUTF8Value(cpi);
   249                     if (isFinal)
   250                         param = "final " + param;
   251                     sb.append(sep).append(param);
   252                     sep = ", ";
   253                 } catch(ConstantPoolException e) {
   254                     error(prefix + "invalid index " + cpi + " for param["
   255                           + x + "]");
   256                     return null;
   257                 }
   260                 // Check availability, flags and special names
   261                 int check = checkParam(mp, param, x, sb, isFinal);
   262                 if (check < 0) {
   263                     return null;
   264                 }
   266                 // TEST: check test assumptions about parameter name.
   267                 // Expected names are calculated starting with the
   268                 // 2nd explicit (user given) parameter.
   269                 // param[n] == ++param[n-1].charAt(0) + param[n-1]
   270                 String expect = null;
   271                 if (userParam != null) {
   272                     char c = userParam.charAt(0);
   273                     expect =  (++c) + userParam;
   274                 }
   275                 if(isFinal && expect != null)
   276                     expect = "final " + expect;
   277                 if (check > 0) {
   278                     if(isFinal) {
   279                         userParam = param.substring(6);
   280                     } else {
   281                     userParam = param;
   282                 }
   283                 }
   284                 if (expect != null && !param.equals(expect)) {
   285                     error(prefix + "param[" + x + "]='"
   286                           + param + "' expected '" + expect + "'");
   287                     return null;
   288                 }
   289             }
   290             if (mSynthetic) {
   291                 sb.append(")/*synthetic*/");
   292             } else {
   293                 sb.append(")");
   294             }
   295             return null;
   296         }
   298         /*
   299          * Check a parameter for conformity to JLS and javac specific
   300          * assumptions.
   301          * Return -1, if an error is detected. Otherwise, return 0, if
   302          * the parameter is compiler generated, or 1 for an (presumably)
   303          * explicitly declared parameter.
   304          */
   305         int checkParam(MethodParameters_attribute mp, String param, int index,
   306                        StringBuilder sb, boolean isFinal) {
   308             boolean synthetic = (mp.method_parameter_table[index].flags
   309                                  & AccessFlags.ACC_SYNTHETIC) != 0;
   310             boolean mandated = (mp.method_parameter_table[index].flags
   311                                 & AccessFlags.ACC_MANDATED) != 0;
   313             // Setup expectations for flags and special names
   314             String expect = null;
   315             boolean allowMandated = false;
   316             boolean allowSynthetic = false;
   317             if (mSynthetic || synthetic) {
   318                 // not an implementation gurantee, but okay for now
   319                 expect = "arg" + index; // default
   320             }
   321             if (mIsConstructor) {
   322                 if (isEnum) {
   323                     if (index == 0) {
   324                         expect = "\\$enum\\$name";
   325                         allowSynthetic = true;
   326                     } else if(index == 1) {
   327                         expect = "\\$enum\\$ordinal";
   328                         allowSynthetic = true;
   329                     }
   330                 } else if (index == 0) {
   331                     if (isAnon) {
   332                         expect = "this\\$[0-9]+";
   333                         allowMandated = true;
   334                         if (isFinal) {
   335                             expect = "final this\\$[0-9]+";
   336                         }
   337                     } else if (isInner && !isStatic) {
   338                         expect = "this\\$[0-9]+";
   339                         allowMandated = true;
   340                         if (!isPublic) {
   341                             // some but not all non-public inner classes
   342                             // have synthetic argument. For now we give
   343                             // the test a bit of slack and allow either.
   344                             allowSynthetic = true;
   345                         }
   346                         if (isFinal) {
   347                             expect = "final this\\$[0-9]+";
   348                         }
   349                     }
   350                 }
   351             } else if (isEnum && mNumParams == 1 && index == 0 && mName.equals("valueOf")) {
   352                 expect = "name";
   353                 allowMandated = true;
   354             } else if (mIsBridge || mIsLambda) {
   355                 allowSynthetic = true;
   356                 /*  you can't expect an special name for bridges' parameters.
   357                  *  The name of the original parameters are now copied. Likewise
   358                  *  for a method encoding the lambda expression, names are derived
   359                  *  from source lambda's parameters and captured enclosing locals.
   360                  */
   361                 expect = null;
   362             }
   363             if (mandated) sb.append("/*implicit*/");
   364             if (synthetic) sb.append("/*synthetic*/");
   366             // IMPL: our rules a somewhat fuzzy, sometimes allowing both mandated
   367             // and synthetic. However, a parameters cannot be both.
   368             if (mandated && synthetic) {
   369                 error(prefix + "param[" + index + "] == \"" + param
   370                       + "\" ACC_SYNTHETIC and ACC_MANDATED");
   371                 return -1;
   372             }
   373             // ... but must be either, if both "allowed".
   374             if (!(mandated || synthetic) && allowMandated && allowSynthetic) {
   375                 error(prefix + "param[" + index + "] == \"" + param
   376                       + "\" expected ACC_MANDATED or ACC_SYNTHETIC");
   377                 return -1;
   378             }
   380             // ... if only one is "allowed", we meant "required".
   381             if (!mandated && allowMandated && !allowSynthetic) {
   382                 error(prefix + "param[" + index + "] == \"" + param
   383                       + "\" expected ACC_MANDATED");
   384                 return -1;
   385             }
   386             if (!synthetic && !allowMandated && allowSynthetic) {
   387                 error(prefix + "param[" + index + "] == \"" + param
   388                       + "\" expected ACC_SYNTHETIC");
   389                 return -1;
   390             }
   392             // ... and not "allowed", means prohibited.
   393             if (mandated && !allowMandated) {
   394                 error(prefix + "param[" + index + "] == \"" + param
   395                       + "\" unexpected, is ACC_MANDATED");
   396                 return -1;
   397             }
   398             if (synthetic && !allowSynthetic) {
   399                 error(prefix + "param[" + index + "] == \"" + param
   400                       + "\" unexpected, is ACC_SYNTHETIC");
   401                 return -1;
   402             }
   404             // Test special name expectations
   405             if (expect != null) {
   406                 if (param.matches(expect)) {
   407                     return 0;
   408                 }
   409                 error(prefix + "param[" + index + "]='" + param +
   410                       "' expected '" + expect + "'");
   411                 return -1;
   412             }
   414             // No further checking for synthetic methods.
   415             if (mSynthetic) {
   416                 return 0;
   417             }
   419             // Otherwise, do check test parameter naming convention.
   420             return 1;
   421         }
   422     }
   423 }

mercurial