ScriptClassInstrumentor.java revision 1036:f0b5e3900a10
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.internal.org.objectweb.asm.Opcodes.ALOAD;
29import static jdk.internal.org.objectweb.asm.Opcodes.DUP;
30import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL;
31import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC;
32import static jdk.internal.org.objectweb.asm.Opcodes.NEW;
33import static jdk.internal.org.objectweb.asm.Opcodes.PUTFIELD;
34import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
35import static jdk.nashorn.internal.tools.nasgen.StringConstants.$CLINIT$;
36import static jdk.nashorn.internal.tools.nasgen.StringConstants.CLINIT;
37import static jdk.nashorn.internal.tools.nasgen.StringConstants.DEFAULT_INIT_DESC;
38import static jdk.nashorn.internal.tools.nasgen.StringConstants.INIT;
39import static jdk.nashorn.internal.tools.nasgen.StringConstants.OBJECT_DESC;
40import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTOBJECT_TYPE;
41import java.io.BufferedInputStream;
42import java.io.FileInputStream;
43import java.io.FileOutputStream;
44import java.io.IOException;
45import jdk.internal.org.objectweb.asm.AnnotationVisitor;
46import jdk.internal.org.objectweb.asm.Attribute;
47import jdk.internal.org.objectweb.asm.ClassReader;
48import jdk.internal.org.objectweb.asm.ClassVisitor;
49import jdk.internal.org.objectweb.asm.ClassWriter;
50import jdk.internal.org.objectweb.asm.FieldVisitor;
51import jdk.internal.org.objectweb.asm.MethodVisitor;
52import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
53import jdk.nashorn.internal.objects.annotations.Where;
54import jdk.nashorn.internal.tools.nasgen.MemberInfo.Kind;
55
56/**
57 * This class instruments the java class annotated with @ScriptClass.
58 *
59 * Changes done are:
60 *
61 * 1) remove all jdk.nashorn.internal.objects.annotations.* annotations.
62 * 2) static final @Property fields stay here. Other @Property fields moved to
63 *    respective classes depending on 'where' value of annotation.
64 * 2) add "Map" type static field named "$map".
65 * 3) add static initializer block to initialize map.
66 */
67
68public class ScriptClassInstrumentor extends ClassVisitor {
69    private final ScriptClassInfo scriptClassInfo;
70    private final int memberCount;
71    private boolean staticInitFound;
72
73    ScriptClassInstrumentor(final ClassVisitor visitor, final ScriptClassInfo sci) {
74        super(Main.ASM_VERSION, visitor);
75        if (sci == null) {
76            throw new IllegalArgumentException("Null ScriptClassInfo, is the class annotated?");
77        }
78        this.scriptClassInfo = sci;
79        this.memberCount = scriptClassInfo.getInstancePropertyCount();
80    }
81
82    @Override
83    public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
84        if (ScriptClassInfo.annotations.containsKey(desc)) {
85            // ignore @ScriptClass
86            return null;
87        }
88
89        return super.visitAnnotation(desc, visible);
90    }
91
92    @Override
93    public FieldVisitor visitField(final int fieldAccess, final String fieldName,
94            final String fieldDesc, final String signature, final Object value) {
95        final MemberInfo memInfo = scriptClassInfo.find(fieldName, fieldDesc, fieldAccess);
96        if (memInfo != null && memInfo.getKind() == Kind.PROPERTY &&
97                memInfo.getWhere() != Where.INSTANCE && !memInfo.isStaticFinal()) {
98            // non-instance @Property fields - these have to go elsewhere unless 'static final'
99            return null;
100        }
101
102        final FieldVisitor delegateFV = super.visitField(fieldAccess, fieldName, fieldDesc,
103                signature, value);
104        return new FieldVisitor(Main.ASM_VERSION, delegateFV) {
105            @Override
106            public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
107                if (ScriptClassInfo.annotations.containsKey(desc)) {
108                    // ignore script field annotations
109                    return null;
110                }
111
112                return fv.visitAnnotation(desc, visible);
113            }
114
115            @Override
116            public void visitAttribute(final Attribute attr) {
117                fv.visitAttribute(attr);
118            }
119
120            @Override
121            public void visitEnd() {
122                fv.visitEnd();
123            }
124        };
125    }
126
127    @Override
128    public MethodVisitor visitMethod(final int methodAccess, final String methodName,
129            final String methodDesc, final String signature, final String[] exceptions) {
130
131        final boolean isConstructor = INIT.equals(methodName);
132        final boolean isStaticInit  = CLINIT.equals(methodName);
133
134        if (isStaticInit) {
135            staticInitFound = true;
136        }
137
138        final MethodGenerator delegateMV = new MethodGenerator(super.visitMethod(methodAccess, methodName, methodDesc,
139                signature, exceptions), methodAccess, methodName, methodDesc);
140
141        return new MethodVisitor(Main.ASM_VERSION, delegateMV) {
142            @Override
143            public void visitInsn(final int opcode) {
144                // call $clinit$ just before return from <clinit>
145                if (isStaticInit && opcode == RETURN) {
146                    super.visitMethodInsn(INVOKESTATIC, scriptClassInfo.getJavaName(),
147                            $CLINIT$, DEFAULT_INIT_DESC, false);
148                }
149                super.visitInsn(opcode);
150            }
151
152            @Override
153            public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) {
154                if (isConstructor && opcode == INVOKESPECIAL &&
155                        INIT.equals(name) && SCRIPTOBJECT_TYPE.equals(owner)) {
156                    super.visitMethodInsn(opcode, owner, name, desc, false);
157
158                    if (memberCount > 0) {
159                        // initialize @Property fields if needed
160                        for (final MemberInfo memInfo : scriptClassInfo.getMembers()) {
161                            if (memInfo.isInstanceProperty() && !memInfo.getInitClass().isEmpty()) {
162                                final String clazz = memInfo.getInitClass();
163                                super.visitVarInsn(ALOAD, 0);
164                                super.visitTypeInsn(NEW, clazz);
165                                super.visitInsn(DUP);
166                                super.visitMethodInsn(INVOKESPECIAL, clazz,
167                                    INIT, DEFAULT_INIT_DESC, false);
168                                super.visitFieldInsn(PUTFIELD, scriptClassInfo.getJavaName(),
169                                    memInfo.getJavaName(), memInfo.getJavaDesc());
170                            }
171
172                            if (memInfo.isInstanceFunction()) {
173                                super.visitVarInsn(ALOAD, 0);
174                                ClassGenerator.newFunction(delegateMV, scriptClassInfo.getJavaName(), memInfo, scriptClassInfo.findSpecializations(memInfo.getJavaName()));
175                                super.visitFieldInsn(PUTFIELD, scriptClassInfo.getJavaName(),
176                                    memInfo.getJavaName(), OBJECT_DESC);
177                            }
178                        }
179                    }
180                } else {
181                    super.visitMethodInsn(opcode, owner, name, desc, itf);
182                }
183            }
184
185            @Override
186            public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
187                if (ScriptClassInfo.annotations.containsKey(desc)) {
188                    // ignore script method annotations
189                    return null;
190                }
191                return super.visitAnnotation(desc, visible);
192            }
193        };
194    }
195
196    @Override
197    public void visitEnd() {
198        emitFields();
199        emitStaticInitializer();
200        emitGettersSetters();
201        super.visitEnd();
202    }
203
204    private void emitFields() {
205        // introduce "Function" type instance fields for each
206        // instance @Function in script class info
207        final String className = scriptClassInfo.getJavaName();
208        for (MemberInfo memInfo : scriptClassInfo.getMembers()) {
209            if (memInfo.isInstanceFunction()) {
210                ClassGenerator.addFunctionField(cv, memInfo.getJavaName());
211                memInfo = (MemberInfo)memInfo.clone();
212                memInfo.setJavaDesc(OBJECT_DESC);
213                ClassGenerator.addGetter(cv, className, memInfo);
214                ClassGenerator.addSetter(cv, className, memInfo);
215            }
216        }
217        // omit addMapField() since instance classes already define a static PropertyMap field
218    }
219
220    void emitGettersSetters() {
221        if (memberCount > 0) {
222            for (final MemberInfo memInfo : scriptClassInfo.getMembers()) {
223                final String className = scriptClassInfo.getJavaName();
224                if (memInfo.isInstanceProperty()) {
225                    ClassGenerator.addGetter(cv, className, memInfo);
226                    if (! memInfo.isFinal()) {
227                        ClassGenerator.addSetter(cv, className, memInfo);
228                    }
229                }
230            }
231        }
232    }
233
234    private void emitStaticInitializer() {
235        final String className = scriptClassInfo.getJavaName();
236        if (! staticInitFound) {
237            // no user written <clinit> and so create one
238            final MethodVisitor mv = ClassGenerator.makeStaticInitializer(this);
239            mv.visitCode();
240            mv.visitInsn(RETURN);
241            mv.visitMaxs(Short.MAX_VALUE, 0);
242            mv.visitEnd();
243        }
244        // Now generate $clinit$
245        final MethodGenerator mi = ClassGenerator.makeStaticInitializer(this, $CLINIT$);
246        ClassGenerator.emitStaticInitPrefix(mi, className, memberCount);
247        if (memberCount > 0) {
248            for (final MemberInfo memInfo : scriptClassInfo.getMembers()) {
249                if (memInfo.isInstanceProperty() || memInfo.isInstanceFunction()) {
250                    ClassGenerator.linkerAddGetterSetter(mi, className, memInfo);
251                } else if (memInfo.isInstanceGetter()) {
252                    final MemberInfo setter = scriptClassInfo.findSetter(memInfo);
253                    ClassGenerator.linkerAddGetterSetter(mi, className, memInfo, setter);
254                }
255            }
256        }
257        ClassGenerator.emitStaticInitSuffix(mi, className);
258    }
259
260    /**
261     * External entry point for ScriptClassInfoCollector if run from the command line
262     *
263     * @param args arguments - one argument is needed, the name of the class to collect info from
264     *
265     * @throws IOException if there are problems reading class
266     */
267    public static void main(final String[] args) throws IOException {
268        if (args.length != 1) {
269            System.err.println("Usage: " + ScriptClassInfoCollector.class.getName() + " <class>");
270            System.exit(1);
271        }
272
273        final String fileName = args[0].replace('.', '/') + ".class";
274        final ScriptClassInfo sci = ClassGenerator.getScriptClassInfo(fileName);
275        if (sci == null) {
276            System.err.println("No @ScriptClass in " + fileName);
277            System.exit(2);
278            throw new AssertionError(); //guard against warning that sci is null below
279        }
280
281        try {
282            sci.verify();
283        } catch (final Exception e) {
284            System.err.println(e.getMessage());
285            System.exit(3);
286        }
287
288        final ClassWriter writer = ClassGenerator.makeClassWriter();
289        try (final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName))) {
290            final ClassReader reader = new ClassReader(bis);
291            final CheckClassAdapter checker = new CheckClassAdapter(writer);
292            final ScriptClassInstrumentor instr = new ScriptClassInstrumentor(checker, sci);
293            reader.accept(instr, 0);
294        }
295
296        try (FileOutputStream fos = new FileOutputStream(fileName)) {
297            fos.write(writer.toByteArray());
298        }
299    }
300}
301