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 suppiled,
 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 == e.g. 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 suppiled,
 207                         // in which case we have to use the default values.
 208                         private String  name;
 209                         private Integer attributes;
 210                         private Integer arity;
 211                         private Where   where;
 212                         private boolean isSpecializedConstructor;
 213                         private boolean isOptimistic;
 214                         private Type    linkLogicClass = MethodGenerator.EMPTY_LINK_LOGIC_TYPE;
 215 
 216                         @Override
 217                         public void visit(final String annotationName, final Object annotationValue) {
 218                             switch (annotationName) {
 219                             case "name":
 220                                 this.name = (String)annotationValue;
 221                                 if (name.isEmpty()) {
 222                                     name = null;
 223                                 }
 224                                 break;
 225                             case "attributes":
 226                                 this.attributes = (Integer)annotationValue;
 227                                 break;
 228                             case "arity":
 229                                 this.arity = (Integer)annotationValue;
 230                                 break;
 231                             case "isConstructor":
 232                                 assert annoKind == Kind.SPECIALIZED_FUNCTION;
 233                                 this.isSpecializedConstructor = (Boolean)annotationValue;
 234                                 break;
 235                             case "isOptimistic":
 236                                 assert annoKind == Kind.SPECIALIZED_FUNCTION;
 237                                 this.isOptimistic = (Boolean)annotationValue;
 238                                 break;
 239                             case "linkLogic":
 240                                 this.linkLogicClass = (Type)annotationValue;
 241                                 break;
 242                             default:
 243                                 break;
 244                             }
 245 
 246                             super.visit(annotationName, annotationValue);
 247                         }
 248 
 249                         @Override
 250                         public void visitEnum(final String enumName, final String desc, final String enumValue) {
 251                             switch (enumName) {
 252                             case "where":
 253                                 if (WHERE_ENUM_DESC.equals(desc)) {
 254                                     this.where = Where.valueOf(enumValue);
 255                                 }
 256                                 break;
 257                             default:
 258                                 break;
 259                             }
 260                             super.visitEnum(enumName, desc, enumValue);
 261                         }
 262 
 263                         @SuppressWarnings("fallthrough")
 264                         @Override
 265                         public void visitEnd() {
 266                             super.visitEnd();
 267 
 268                             if (memInfo.getKind() == Kind.CONSTRUCTOR) {
 269                                 memInfo.setName(name == null ? scriptClassName : name);
 270                             } else {
 271                                 memInfo.setName(name == null ? methodName : name);
 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 }