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