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