/* * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.internal.tools.nasgen; import static jdk.nashorn.internal.tools.nasgen.ScriptClassInfo.SCRIPT_CLASS_ANNO_DESC; import static jdk.nashorn.internal.tools.nasgen.ScriptClassInfo.WHERE_ENUM_DESC; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import jdk.internal.org.objectweb.asm.AnnotationVisitor; import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.org.objectweb.asm.ClassVisitor; import jdk.internal.org.objectweb.asm.FieldVisitor; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; import jdk.internal.org.objectweb.asm.Type; import jdk.nashorn.internal.tools.nasgen.MemberInfo.Kind; /** * This class collects all @ScriptClass and other annotation information from a * compiled .class file. Enforces that @Function/@Getter/@Setter/@Constructor * methods are declared to be 'static'. */ public class ScriptClassInfoCollector extends ClassVisitor { private String scriptClassName; private List<MemberInfo> scriptMembers; private String javaClassName; ScriptClassInfoCollector(final ClassVisitor visitor) { super(Main.ASM_VERSION, visitor); } ScriptClassInfoCollector() { this(new NullVisitor()); } private void addScriptMember(final MemberInfo memInfo) { if (scriptMembers == null) { scriptMembers = new ArrayList<>(); } scriptMembers.add(memInfo); } @Override public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); javaClassName = name; } @Override public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { final AnnotationVisitor delegateAV = super.visitAnnotation(desc, visible); if (SCRIPT_CLASS_ANNO_DESC.equals(desc)) { return new AnnotationVisitor(Main.ASM_VERSION, delegateAV) { @Override public void visit(final String name, final Object value) { if ("value".equals(name)) { scriptClassName = (String) value; } super.visit(name, value); } }; } return delegateAV; } @Override public FieldVisitor visitField(final int fieldAccess, final String fieldName, final String fieldDesc, final String signature, final Object value) { final FieldVisitor delegateFV = super.visitField(fieldAccess, fieldName, fieldDesc, signature, value); return new FieldVisitor(Main.ASM_VERSION, delegateFV) { @Override public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { final AnnotationVisitor delegateAV = super.visitAnnotation(descriptor, visible); if (ScriptClassInfo.PROPERTY_ANNO_DESC.equals(descriptor)) { final MemberInfo memInfo = new MemberInfo(); memInfo.setKind(Kind.PROPERTY); memInfo.setJavaName(fieldName); memInfo.setJavaDesc(fieldDesc); memInfo.setJavaAccess(fieldAccess); if ((fieldAccess & Opcodes.ACC_STATIC) != 0) { memInfo.setValue(value); } addScriptMember(memInfo); return new AnnotationVisitor(Main.ASM_VERSION, delegateAV) { // These could be "null" if values are not supplied, // in which case we have to use the default values. private String name; private Integer attributes; private String clazz = ""; private Where where; @Override public void visit(final String annotationName, final Object annotationValue) { switch (annotationName) { case "name": this.name = (String) annotationValue; break; case "attributes": this.attributes = (Integer) annotationValue; break; case "clazz": this.clazz = (annotationValue == null) ? "" : annotationValue.toString(); break; default: break; } super.visit(annotationName, annotationValue); } @Override public void visitEnum(final String enumName, final String desc, final String enumValue) { if ("where".equals(enumName) && WHERE_ENUM_DESC.equals(desc)) { this.where = Where.valueOf(enumValue); } super.visitEnum(enumName, desc, enumValue); } @Override public void visitEnd() { super.visitEnd(); memInfo.setName(name == null ? fieldName : name); memInfo.setAttributes(attributes == null ? MemberInfo.DEFAULT_ATTRIBUTES : attributes); clazz = clazz.replace('.', '/'); memInfo.setInitClass(clazz); memInfo.setWhere(where == null? Where.INSTANCE : where); } }; } return delegateAV; } }; } private void error(final String javaName, final String javaDesc, final String msg) { throw new RuntimeException(scriptClassName + "." + javaName + javaDesc + " : " + msg); } @Override public MethodVisitor visitMethod(final int methodAccess, final String methodName, final String methodDesc, final String signature, final String[] exceptions) { final MethodVisitor delegateMV = super.visitMethod(methodAccess, methodName, methodDesc, signature, exceptions); return new MethodVisitor(Main.ASM_VERSION, delegateMV) { @Override public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { final AnnotationVisitor delegateAV = super.visitAnnotation(descriptor, visible); final Kind annoKind = ScriptClassInfo.annotations.get(descriptor); if (annoKind != null) { if ((methodAccess & Opcodes.ACC_STATIC) == 0) { error(methodName, methodDesc, "nasgen method annotations cannot be on instance methods"); } final MemberInfo memInfo = new MemberInfo(); // annoKind == GETTER or SPECIALIZED_FUNCTION memInfo.setKind(annoKind); memInfo.setJavaName(methodName); memInfo.setJavaDesc(methodDesc); memInfo.setJavaAccess(methodAccess); addScriptMember(memInfo); return new AnnotationVisitor(Main.ASM_VERSION, delegateAV) { // These could be "null" if values are not supplied, // in which case we have to use the default values. private String name; private Integer attributes; private Integer arity; private Where where; private boolean isSpecializedConstructor; private boolean isOptimistic; private boolean convertsNumericArgs; private Type linkLogicClass = MethodGenerator.EMPTY_LINK_LOGIC_TYPE; @Override public void visit(final String annotationName, final Object annotationValue) { switch (annotationName) { case "name": this.name = (String)annotationValue; if (name.isEmpty()) { name = null; } break; case "attributes": this.attributes = (Integer)annotationValue; break; case "arity": this.arity = (Integer)annotationValue; break; case "isConstructor": assert annoKind == Kind.SPECIALIZED_FUNCTION; this.isSpecializedConstructor = (Boolean)annotationValue; break; case "isOptimistic": assert annoKind == Kind.SPECIALIZED_FUNCTION; this.isOptimistic = (Boolean)annotationValue; break; case "linkLogic": this.linkLogicClass = (Type)annotationValue; break; case "convertsNumericArgs": assert annoKind == Kind.SPECIALIZED_FUNCTION; this.convertsNumericArgs = (Boolean)annotationValue; break; default: break; } super.visit(annotationName, annotationValue); } @Override public void visitEnum(final String enumName, final String desc, final String enumValue) { switch (enumName) { case "where": if (WHERE_ENUM_DESC.equals(desc)) { this.where = Where.valueOf(enumValue); } break; default: break; } super.visitEnum(enumName, desc, enumValue); } @SuppressWarnings("fallthrough") @Override public void visitEnd() { super.visitEnd(); if (memInfo.getKind() == Kind.CONSTRUCTOR) { memInfo.setName(name == null ? scriptClassName : name); } else { memInfo.setName(name == null ? methodName : name); } memInfo.setAttributes(attributes == null ? MemberInfo.DEFAULT_ATTRIBUTES : attributes); memInfo.setArity((arity == null)? MemberInfo.DEFAULT_ARITY : arity); if (where == null) { // by default @Getter/@Setter belongs to INSTANCE // @Function belong to PROTOTYPE. switch (memInfo.getKind()) { case GETTER: case SETTER: where = Where.INSTANCE; break; case CONSTRUCTOR: where = Where.CONSTRUCTOR; break; case FUNCTION: where = Where.PROTOTYPE; break; case SPECIALIZED_FUNCTION: where = isSpecializedConstructor? Where.CONSTRUCTOR : Where.PROTOTYPE; //fallthru default: break; } } memInfo.setWhere(where); memInfo.setLinkLogicClass(linkLogicClass); memInfo.setIsSpecializedConstructor(isSpecializedConstructor); memInfo.setIsOptimistic(isOptimistic); memInfo.setConvertsNumericArgs(convertsNumericArgs); } }; } return delegateAV; } }; } ScriptClassInfo getScriptClassInfo() { ScriptClassInfo sci = null; if (scriptClassName != null) { sci = new ScriptClassInfo(); sci.setName(scriptClassName); if (scriptMembers == null) { scriptMembers = Collections.emptyList(); } sci.setMembers(scriptMembers); sci.setJavaName(javaClassName); } return sci; } /** * External entry point for ScriptClassInfoCollector if invoked from the command line * @param args argument vector, args contains a class for which to collect info * @throws IOException if there were problems parsing args or class */ public static void main(final String[] args) throws IOException { if (args.length != 1) { System.err.println("Usage: " + ScriptClassInfoCollector.class.getName() + " <class>"); System.exit(1); } args[0] = args[0].replace('.', '/'); final ScriptClassInfoCollector scic = new ScriptClassInfoCollector(); try (final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(args[0] + ".class"))) { final ClassReader reader = new ClassReader(bis); reader.accept(scic, 0); } final ScriptClassInfo sci = scic.getScriptClassInfo(); final PrintStream out = System.out; if (sci != null) { out.println("script class: " + sci.getName()); out.println("==================================="); for (final MemberInfo memInfo : sci.getMembers()) { out.println("kind : " + memInfo.getKind()); out.println("name : " + memInfo.getName()); out.println("attributes: " + memInfo.getAttributes()); out.println("javaName: " + memInfo.getJavaName()); out.println("javaDesc: " + memInfo.getJavaDesc()); out.println("where: " + memInfo.getWhere()); out.println("====================================="); } } else { out.println(args[0] + " is not a @ScriptClass"); } } }