ScriptClassInfoCollector.java revision 1326:a35490e96dc5
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.objects.annotations.Where;
45import 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 */
52public 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}
360