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 26package jdk.nashorn.internal.tools.nasgen; 27 28import static jdk.nashorn.internal.tools.nasgen.ScriptClassInfo.SCRIPT_CLASS_ANNO_DESC; 29import static jdk.nashorn.internal.tools.nasgen.ScriptClassInfo.WHERE_ENUM_DESC; 30import java.io.BufferedInputStream; 31import java.io.FileInputStream; 32import java.io.IOException; 33import java.io.PrintStream; 34import java.util.ArrayList; 35import java.util.Collections; 36import java.util.List; 37import jdk.internal.org.objectweb.asm.AnnotationVisitor; 38import jdk.internal.org.objectweb.asm.ClassReader; 39import jdk.internal.org.objectweb.asm.ClassVisitor; 40import jdk.internal.org.objectweb.asm.FieldVisitor; 41import jdk.internal.org.objectweb.asm.MethodVisitor; 42import jdk.internal.org.objectweb.asm.Opcodes; 43import jdk.internal.org.objectweb.asm.Type; 44import 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 */ 51public 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 boolean convertsNumericArgs; 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 case "convertsNumericArgs": 243 assert annoKind == Kind.SPECIALIZED_FUNCTION; 244 this.convertsNumericArgs = (Boolean)annotationValue; 245 break; 246 default: 247 break; 248 } 249 250 super.visit(annotationName, annotationValue); 251 } 252 253 @Override 254 public void visitEnum(final String enumName, final String desc, final String enumValue) { 255 switch (enumName) { 256 case "where": 257 if (WHERE_ENUM_DESC.equals(desc)) { 258 this.where = Where.valueOf(enumValue); 259 } 260 break; 261 default: 262 break; 263 } 264 super.visitEnum(enumName, desc, enumValue); 265 } 266 267 @SuppressWarnings("fallthrough") 268 @Override 269 public void visitEnd() { 270 super.visitEnd(); 271 272 if (memInfo.getKind() == Kind.CONSTRUCTOR) { 273 memInfo.setName(name == null ? scriptClassName : name); 274 } else { 275 memInfo.setName(name == null ? methodName : name); 276 } 277 278 memInfo.setAttributes(attributes == null ? MemberInfo.DEFAULT_ATTRIBUTES : attributes); 279 280 memInfo.setArity((arity == null)? MemberInfo.DEFAULT_ARITY : arity); 281 if (where == null) { 282 // by default @Getter/@Setter belongs to INSTANCE 283 // @Function belong to PROTOTYPE. 284 switch (memInfo.getKind()) { 285 case GETTER: 286 case SETTER: 287 where = Where.INSTANCE; 288 break; 289 case CONSTRUCTOR: 290 where = Where.CONSTRUCTOR; 291 break; 292 case FUNCTION: 293 where = Where.PROTOTYPE; 294 break; 295 case SPECIALIZED_FUNCTION: 296 where = isSpecializedConstructor? Where.CONSTRUCTOR : Where.PROTOTYPE; 297 //fallthru 298 default: 299 break; 300 } 301 } 302 memInfo.setWhere(where); 303 memInfo.setLinkLogicClass(linkLogicClass); 304 memInfo.setIsSpecializedConstructor(isSpecializedConstructor); 305 memInfo.setIsOptimistic(isOptimistic); 306 memInfo.setConvertsNumericArgs(convertsNumericArgs); 307 } 308 }; 309 } 310 311 return delegateAV; 312 } 313 }; 314 } 315 316 ScriptClassInfo getScriptClassInfo() { 317 ScriptClassInfo sci = null; 318 if (scriptClassName != null) { 319 sci = new ScriptClassInfo(); 320 sci.setName(scriptClassName); 321 if (scriptMembers == null) { 322 scriptMembers = Collections.emptyList(); 323 } 324 sci.setMembers(scriptMembers); 325 sci.setJavaName(javaClassName); 326 } 327 return sci; 328 } 329 330 /** 331 * External entry point for ScriptClassInfoCollector if invoked from the command line 332 * @param args argument vector, args contains a class for which to collect info 333 * @throws IOException if there were problems parsing args or class 334 */ 335 public static void main(final String[] args) throws IOException { 336 if (args.length != 1) { 337 System.err.println("Usage: " + ScriptClassInfoCollector.class.getName() + " <class>"); 338 System.exit(1); 339 } 340 341 args[0] = args[0].replace('.', '/'); 342 final ScriptClassInfoCollector scic = new ScriptClassInfoCollector(); 343 try (final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(args[0] + ".class"))) { 344 final ClassReader reader = new ClassReader(bis); 345 reader.accept(scic, 0); 346 } 347 final ScriptClassInfo sci = scic.getScriptClassInfo(); 348 final PrintStream out = System.out; 349 if (sci != null) { 350 out.println("script class: " + sci.getName()); 351 out.println("==================================="); 352 for (final MemberInfo memInfo : sci.getMembers()) { 353 out.println("kind : " + memInfo.getKind()); 354 out.println("name : " + memInfo.getName()); 355 out.println("attributes: " + memInfo.getAttributes()); 356 out.println("javaName: " + memInfo.getJavaName()); 357 out.println("javaDesc: " + memInfo.getJavaDesc()); 358 out.println("where: " + memInfo.getWhere()); 359 out.println("====================================="); 360 } 361 } else { 362 out.println(args[0] + " is not a @ScriptClass"); 363 } 364 } 365} 366