1 /*
   2  * Copyright (c) 2010, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.tools.nasgen;
  27 
  28 import static jdk.nashorn.internal.tools.nasgen.ScriptClassInfo.SCRIPT_CLASS_ANNO_DESC;
  29 import static jdk.nashorn.internal.tools.nasgen.ScriptClassInfo.WHERE_ENUM_DESC;
  30 import java.io.BufferedInputStream;
  31 import java.io.FileInputStream;
  32 import java.io.IOException;
  33 import java.io.PrintStream;
  34 import java.util.ArrayList;
  35 import java.util.Collections;
  36 import java.util.List;
  37 import jdk.internal.org.objectweb.asm.AnnotationVisitor;
  38 import jdk.internal.org.objectweb.asm.ClassReader;
  39 import jdk.internal.org.objectweb.asm.ClassVisitor;
  40 import jdk.internal.org.objectweb.asm.FieldVisitor;
  41 import jdk.internal.org.objectweb.asm.MethodVisitor;
  42 import jdk.internal.org.objectweb.asm.Opcodes;
  43 import jdk.internal.org.objectweb.asm.Type;
  44 import jdk.nashorn.internal.tools.nasgen.MemberInfo.Kind;
  45 
  46 /**
  47  * This class collects all @ScriptClass and other annotation information from a
  48  * compiled .class file. Enforces that @Function/@Getter/@Setter/@Constructor
  49  * methods are declared to be 'static'.
  50  */
  51 public class ScriptClassInfoCollector extends ClassVisitor {
  52     private String scriptClassName;
  53     private List<MemberInfo> scriptMembers;
  54     private String javaClassName;
  55 
  56     ScriptClassInfoCollector(final ClassVisitor visitor) {
  57         super(Main.ASM_VERSION, visitor);
  58     }
  59 
  60     ScriptClassInfoCollector() {
  61         this(new NullVisitor());
  62     }
  63 
  64     private void addScriptMember(final MemberInfo memInfo) {
  65         if (scriptMembers == null) {
  66             scriptMembers = new ArrayList<>();
  67         }
  68         scriptMembers.add(memInfo);
  69     }
  70 
  71     @Override
  72     public void visit(final int version, final int access, final String name, final String signature,
  73            final String superName, final String[] interfaces) {
  74         super.visit(version, access, name, signature, superName, interfaces);
  75         javaClassName = name;
  76     }
  77 
  78     @Override
  79     public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
  80         final AnnotationVisitor delegateAV = super.visitAnnotation(desc, visible);
  81         if (SCRIPT_CLASS_ANNO_DESC.equals(desc)) {
  82             return new AnnotationVisitor(Main.ASM_VERSION, delegateAV) {
  83                 @Override
  84                 public void visit(final String name, final Object value) {
  85                     if ("value".equals(name)) {
  86                         scriptClassName = (String) value;
  87                     }
  88                     super.visit(name, value);
  89                 }
  90             };
  91         }
  92 
  93         return delegateAV;
  94     }
  95 
  96     @Override
  97     public FieldVisitor visitField(final int fieldAccess, final String fieldName, final String fieldDesc, final String signature, final Object value) {
  98         final FieldVisitor delegateFV = super.visitField(fieldAccess, fieldName, fieldDesc, signature, value);
  99 
 100         return new FieldVisitor(Main.ASM_VERSION, delegateFV) {
 101             @Override
 102             public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
 103                 final AnnotationVisitor delegateAV = super.visitAnnotation(descriptor, visible);
 104 
 105                 if (ScriptClassInfo.PROPERTY_ANNO_DESC.equals(descriptor)) {
 106                     final MemberInfo memInfo = new MemberInfo();
 107 
 108                     memInfo.setKind(Kind.PROPERTY);
 109                     memInfo.setJavaName(fieldName);
 110                     memInfo.setJavaDesc(fieldDesc);
 111                     memInfo.setJavaAccess(fieldAccess);
 112 
 113                     if ((fieldAccess & Opcodes.ACC_STATIC) != 0) {
 114                         memInfo.setValue(value);
 115                     }
 116 
 117                     addScriptMember(memInfo);
 118 
 119                     return new AnnotationVisitor(Main.ASM_VERSION, delegateAV) {
 120                         // These could be "null" if values are not supplied,
 121                         // in which case we have to use the default values.
 122                         private String  name;
 123                         private Integer attributes;
 124                         private String  clazz = "";
 125                         private Where   where;
 126 
 127                         @Override
 128                         public void visit(final String annotationName, final Object annotationValue) {
 129                             switch (annotationName) {
 130                             case "name":
 131                                 this.name = (String) annotationValue;
 132                                 break;
 133                             case "attributes":
 134                                 this.attributes = (Integer) annotationValue;
 135                                 break;
 136                             case "clazz":
 137                                 this.clazz = (annotationValue == null) ? "" : annotationValue.toString();
 138                                 break;
 139                             default:
 140                                 break;
 141                             }
 142                             super.visit(annotationName, annotationValue);
 143                         }
 144 
 145                         @Override
 146                         public void visitEnum(final String enumName, final String desc, final String enumValue) {
 147                             if ("where".equals(enumName) && WHERE_ENUM_DESC.equals(desc)) {
 148                                 this.where = Where.valueOf(enumValue);
 149                             }
 150                             super.visitEnum(enumName, desc, enumValue);
 151                         }
 152 
 153                         @Override
 154                         public void visitEnd() {
 155                             super.visitEnd();
 156                             memInfo.setName(name == null ? fieldName : name);
 157                             memInfo.setAttributes(attributes == null
 158                                     ? MemberInfo.DEFAULT_ATTRIBUTES : attributes);
 159                             clazz = clazz.replace('.', '/');
 160                             memInfo.setInitClass(clazz);
 161                             memInfo.setWhere(where == null? Where.INSTANCE : where);
 162                         }
 163                     };
 164                 }
 165 
 166                 return delegateAV;
 167             }
 168         };
 169     }
 170 
 171     private void error(final String javaName, final String javaDesc, final String msg) {
 172         throw new RuntimeException(scriptClassName + "." + javaName + javaDesc + " : " + msg);
 173     }
 174 
 175     @Override
 176     public MethodVisitor visitMethod(final int methodAccess, final String methodName,
 177             final String methodDesc, final String signature, final String[] exceptions) {
 178 
 179         final MethodVisitor delegateMV = super.visitMethod(methodAccess, methodName, methodDesc,
 180                 signature, exceptions);
 181 
 182         return new MethodVisitor(Main.ASM_VERSION, delegateMV) {
 183 
 184             @Override
 185             public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
 186                 final AnnotationVisitor delegateAV = super.visitAnnotation(descriptor, visible);
 187                 final Kind annoKind = ScriptClassInfo.annotations.get(descriptor);
 188 
 189                 if (annoKind != null) {
 190                     if ((methodAccess & Opcodes.ACC_STATIC) == 0) {
 191                         error(methodName, methodDesc, "nasgen method annotations cannot be on instance methods");
 192                     }
 193 
 194                     final MemberInfo memInfo = new MemberInfo();
 195 
 196                     // annoKind == GETTER or SPECIALIZED_FUNCTION
 197                     memInfo.setKind(annoKind);
 198                     memInfo.setJavaName(methodName);
 199                     memInfo.setJavaDesc(methodDesc);
 200                     memInfo.setJavaAccess(methodAccess);
 201 
 202                     addScriptMember(memInfo);
 203 
 204                     return new AnnotationVisitor(Main.ASM_VERSION, delegateAV) {
 205                         // These could be "null" if values are not supplied,
 206                         // in which case we have to use the default values.
 207                         private String  name;
 208                         private Integer attributes;
 209                         private Integer arity;
 210                         private Where   where;
 211                         private boolean isSpecializedConstructor;
 212                         private boolean isOptimistic;
 213                         private Type    linkLogicClass = MethodGenerator.EMPTY_LINK_LOGIC_TYPE;
 214 
 215                         @Override
 216                         public void visit(final String annotationName, final Object annotationValue) {
 217                             switch (annotationName) {
 218                             case "name":
 219                                 this.name = (String)annotationValue;
 220                                 if (name.isEmpty()) {
 221                                     name = null;
 222                                 }
 223                                 break;
 224                             case "attributes":
 225                                 this.attributes = (Integer)annotationValue;
 226                                 break;
 227                             case "arity":
 228                                 this.arity = (Integer)annotationValue;
 229                                 break;
 230                             case "isConstructor":
 231                                 assert annoKind == Kind.SPECIALIZED_FUNCTION;
 232                                 this.isSpecializedConstructor = (Boolean)annotationValue;
 233                                 break;
 234                             case "isOptimistic":
 235                                 assert annoKind == Kind.SPECIALIZED_FUNCTION;
 236                                 this.isOptimistic = (Boolean)annotationValue;
 237                                 break;
 238                             case "linkLogic":
 239                                 this.linkLogicClass = (Type)annotationValue;
 240                                 break;
 241                             default:
 242                                 break;
 243                             }
 244 
 245                             super.visit(annotationName, annotationValue);
 246                         }
 247 
 248                         @Override
 249                         public void visitEnum(final String enumName, final String desc, final String enumValue) {
 250                             switch (enumName) {
 251                             case "where":
 252                                 if (WHERE_ENUM_DESC.equals(desc)) {
 253                                     this.where = Where.valueOf(enumValue);
 254                                 }
 255                                 break;
 256                             default:
 257                                 break;
 258                             }
 259                             super.visitEnum(enumName, desc, enumValue);
 260                         }
 261 
 262                         @SuppressWarnings("fallthrough")
 263                         @Override
 264                         public void visitEnd() {
 265                             super.visitEnd();
 266 
 267                             if (memInfo.getKind() == Kind.CONSTRUCTOR) {
 268                                 memInfo.setName(name == null ? scriptClassName : name);
 269                             } else {
 270                                 memInfo.setName(name == null ? methodName : name);
 271                             }
 272 
 273                             memInfo.setAttributes(attributes == null ? MemberInfo.DEFAULT_ATTRIBUTES : attributes);
 274 
 275                             memInfo.setArity((arity == null)? MemberInfo.DEFAULT_ARITY : arity);
 276                             if (where == null) {
 277                                 // by default @Getter/@Setter belongs to INSTANCE
 278                                 // @Function belong to PROTOTYPE.
 279                                 switch (memInfo.getKind()) {
 280                                     case GETTER:
 281                                     case SETTER:
 282                                         where = Where.INSTANCE;
 283                                         break;
 284                                     case CONSTRUCTOR:
 285                                         where = Where.CONSTRUCTOR;
 286                                         break;
 287                                     case FUNCTION:
 288                                         where = Where.PROTOTYPE;
 289                                         break;
 290                                     case SPECIALIZED_FUNCTION:
 291                                         where = isSpecializedConstructor? Where.CONSTRUCTOR : Where.PROTOTYPE;
 292                                         //fallthru
 293                                     default:
 294                                         break;
 295                                 }
 296                             }
 297                             memInfo.setWhere(where);
 298                             memInfo.setLinkLogicClass(linkLogicClass);
 299                             memInfo.setIsSpecializedConstructor(isSpecializedConstructor);
 300                             memInfo.setIsOptimistic(isOptimistic);
 301                         }
 302                     };
 303                 }
 304 
 305                 return delegateAV;
 306             }
 307         };
 308     }
 309 
 310     ScriptClassInfo getScriptClassInfo() {
 311         ScriptClassInfo sci = null;
 312         if (scriptClassName != null) {
 313             sci = new ScriptClassInfo();
 314             sci.setName(scriptClassName);
 315             if (scriptMembers == null) {
 316                 scriptMembers = Collections.emptyList();
 317             }
 318             sci.setMembers(scriptMembers);
 319             sci.setJavaName(javaClassName);
 320         }
 321         return sci;
 322     }
 323 
 324     /**
 325      * External entry point for ScriptClassInfoCollector if invoked from the command line
 326      * @param args argument vector, args contains a class for which to collect info
 327      * @throws IOException if there were problems parsing args or class
 328      */
 329     public static void main(final String[] args) throws IOException {
 330         if (args.length != 1) {
 331             System.err.println("Usage: " + ScriptClassInfoCollector.class.getName() + " <class>");
 332             System.exit(1);
 333         }
 334 
 335         args[0] = args[0].replace('.', '/');
 336         final ScriptClassInfoCollector scic = new ScriptClassInfoCollector();
 337         try (final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(args[0] + ".class"))) {
 338             final ClassReader reader = new ClassReader(bis);
 339             reader.accept(scic, 0);
 340         }
 341         final ScriptClassInfo sci = scic.getScriptClassInfo();
 342         final PrintStream out = System.out;
 343         if (sci != null) {
 344             out.println("script class: " + sci.getName());
 345             out.println("===================================");
 346             for (final MemberInfo memInfo : sci.getMembers()) {
 347                 out.println("kind : " + memInfo.getKind());
 348                 out.println("name : " + memInfo.getName());
 349                 out.println("attributes: " + memInfo.getAttributes());
 350                 out.println("javaName: " + memInfo.getJavaName());
 351                 out.println("javaDesc: " + memInfo.getJavaDesc());
 352                 out.println("where: " + memInfo.getWhere());
 353                 out.println("=====================================");
 354             }
 355         } else {
 356             out.println(args[0] + " is not a @ScriptClass");
 357         }
 358     }
 359 }