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