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