View Javadoc
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  
26  package jdk.nashorn.internal.codegen;
27  
28  import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL;
29  import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE;
30  import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
31  import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
32  import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
33  import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS;
34  import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKEINTERFACE;
35  import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESPECIAL;
36  import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC;
37  import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL;
38  import static jdk.internal.org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
39  import static jdk.internal.org.objectweb.asm.Opcodes.V1_7;
40  import static jdk.nashorn.internal.codegen.CompilerConstants.CLINIT;
41  import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS;
42  import static jdk.nashorn.internal.codegen.CompilerConstants.GET_ARRAY_PREFIX;
43  import static jdk.nashorn.internal.codegen.CompilerConstants.GET_ARRAY_SUFFIX;
44  import static jdk.nashorn.internal.codegen.CompilerConstants.GET_MAP;
45  import static jdk.nashorn.internal.codegen.CompilerConstants.GET_STRING;
46  import static jdk.nashorn.internal.codegen.CompilerConstants.INIT;
47  import static jdk.nashorn.internal.codegen.CompilerConstants.SET_MAP;
48  import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
49  import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE;
50  import static jdk.nashorn.internal.codegen.CompilerConstants.className;
51  import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor;
52  import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
53  import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor;
54  
55  import java.io.ByteArrayOutputStream;
56  import java.io.PrintWriter;
57  import java.util.Arrays;
58  import java.util.EnumSet;
59  import java.util.HashSet;
60  import java.util.Set;
61  
62  import jdk.internal.org.objectweb.asm.ClassReader;
63  import jdk.internal.org.objectweb.asm.ClassWriter;
64  import jdk.internal.org.objectweb.asm.MethodVisitor;
65  import jdk.internal.org.objectweb.asm.util.TraceClassVisitor;
66  import jdk.nashorn.internal.codegen.types.Type;
67  import jdk.nashorn.internal.ir.FunctionNode;
68  import jdk.nashorn.internal.ir.SplitNode;
69  import jdk.nashorn.internal.runtime.PropertyMap;
70  import jdk.nashorn.internal.runtime.ScriptEnvironment;
71  import jdk.nashorn.internal.runtime.ScriptObject;
72  import jdk.nashorn.internal.runtime.Source;
73  
74  /**
75   * The interface responsible for speaking to ASM, emitting classes,
76   * fields and methods.
77   * <p>
78   * This file contains the ClassEmitter, which is the master object
79   * responsible for writing byte codes. It utilizes a MethodEmitter
80   * for method generation, which also the NodeVisitors own, to keep
81   * track of the current code generator and what it is doing.
82   * <p>
83   * There is, however, nothing stopping you from using this in a
84   * completely self contained environment, for example in ObjectGenerator
85   * where there are no visitors or external hooks.
86   * <p>
87   * MethodEmitter makes it simple to generate code for methods without
88   * having to do arduous type checking. It maintains a type stack
89   * and will pick the appropriate operation for all operations sent to it
90   * We also allow chained called to a MethodEmitter for brevity, e.g.
91   * it is legal to write _new(className).dup() or
92   * load(slot).load(slot2).xor().store(slot3);
93   * <p>
94   * If running with assertions enabled, any type conflict, such as different
95   * bytecode stack sizes or operating on the wrong type will be detected
96   * and an error thrown.
97   * <p>
98   * There is also a very nice debug interface that can emit formatted
99   * bytecodes that have been written. This is enabled by setting the
100  * environment "nashorn.codegen.debug" to true, or --log=codegen:{@literal <level>}
101  * <p>
102  * A ClassEmitter implements an Emitter - i.e. it needs to have
103  * well defined start and end calls for whatever it is generating. Assertions
104  * detect if this is not true
105  *
106  * @see Compiler
107  */
108 public class ClassEmitter implements Emitter {
109 
110     /** Sanity check flag - have we started on a class? */
111     private boolean classStarted;
112 
113     /** Sanity check flag - have we ended this emission? */
114     private boolean classEnded;
115 
116     /**
117      * Sanity checks - which methods have we currently
118      * started for generation in this class?
119      */
120     private final HashSet<MethodEmitter> methodsStarted;
121 
122     /** The ASM classwriter that we use for all bytecode operations */
123     protected final ClassWriter cw;
124 
125     /** The script environment */
126     protected final ScriptEnvironment env;
127 
128     /** Default flags for class generation - oublic class */
129     private static final EnumSet<Flag> DEFAULT_METHOD_FLAGS = EnumSet.of(Flag.PUBLIC);
130 
131     /** Compile unit class name. */
132     private String unitClassName;
133 
134     /** Set of constants access methods required. */
135     private Set<Class<?>> constantMethodNeeded;
136 
137     /**
138      * Constructor - only used internally in this class as it breaks
139      * abstraction towards ASM or other code generator below
140      *
141      * @param env script environment
142      * @param cw  ASM classwriter
143      */
144     private ClassEmitter(final ScriptEnvironment env, final ClassWriter cw) {
145         assert env != null;
146 
147         this.env            = env;
148         this.cw             = cw;
149         this.methodsStarted = new HashSet<>();
150     }
151 
152     /**
153      * Constructor
154      *
155      * @param env             script environment
156      * @param className       name of class to weave
157      * @param superClassName  super class name for class
158      * @param interfaceNames  names of interfaces implemented by this class, or null if none
159      */
160     ClassEmitter(final ScriptEnvironment env, final String className, final String superClassName, final String... interfaceNames) {
161         this(env, new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS));
162         cw.visit(V1_7, ACC_PUBLIC | ACC_SUPER, className, null, superClassName, interfaceNames);
163     }
164 
165     /**
166      * Constructor from the compiler
167      *
168      * @param env           Script environment
169      * @param sourceName    Source name
170      * @param unitClassName Compile unit class name.
171      * @param strictMode    Should we generate this method in strict mode
172      */
173     ClassEmitter(final ScriptEnvironment env, final String sourceName, final String unitClassName, final boolean strictMode) {
174         this(env,
175              new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) {
176                 private static final String OBJECT_CLASS  = "java/lang/Object";
177 
178                 @Override
179                 protected String getCommonSuperClass(final String type1, final String type2) {
180                     try {
181                         return super.getCommonSuperClass(type1, type2);
182                     } catch (final RuntimeException e) {
183                         if (isScriptObject(Compiler.SCRIPTS_PACKAGE, type1) && isScriptObject(Compiler.SCRIPTS_PACKAGE, type2)) {
184                             return className(ScriptObject.class);
185                         }
186                         return OBJECT_CLASS;
187                     }
188                 }
189             });
190 
191         this.unitClassName        = unitClassName;
192         this.constantMethodNeeded = new HashSet<>();
193 
194         cw.visit(V1_7, ACC_PUBLIC | ACC_SUPER, unitClassName, null, pathName(jdk.nashorn.internal.scripts.JS.class.getName()), null);
195         cw.visitSource(sourceName, null);
196 
197         defineCommonStatics(strictMode);
198     }
199 
200     /**
201      * Returns the name of the compile unit class name.
202      * @return the name of the compile unit class name.
203      */
204     String getUnitClassName() {
205         return unitClassName;
206     }
207 
208     /**
209      * Convert a binary name to a package/class name.
210      *
211      * @param name Binary name.
212      * @return Package/class name.
213      */
214     private static String pathName(final String name) {
215         return name.replace('.', '/');
216     }
217 
218     /**
219      * Define the static fields common in all scripts.
220      * @param strictMode Should we generate this method in strict mode
221      */
222     private void defineCommonStatics(final boolean strictMode) {
223         // source - used to store the source data (text) for this script.  Shared across
224         // compile units.  Set externally by the compiler.
225         field(EnumSet.of(Flag.PRIVATE, Flag.STATIC), SOURCE.symbolName(), Source.class);
226 
227         // constants - used to the constants array for this script.  Shared across
228         // compile units.  Set externally by the compiler.
229         field(EnumSet.of(Flag.PRIVATE, Flag.STATIC), CONSTANTS.symbolName(), Object[].class);
230 
231         // strictMode - was this script compiled in strict mode.  Set externally by the compiler.
232         field(EnumSet.of(Flag.PUBLIC, Flag.STATIC, Flag.FINAL), STRICT_MODE.symbolName(), boolean.class, strictMode);
233     }
234 
235     /**
236      * Define static utilities common needed in scripts.  These are per compile unit
237      * and therefore have to be defined here and not in code gen.
238      */
239     private void defineCommonUtilities() {
240         assert unitClassName != null;
241 
242         if (constantMethodNeeded.contains(String.class)) {
243             // $getString - get the ith entry from the constants table and cast to String.
244             final MethodEmitter getStringMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), GET_STRING.symbolName(), String.class, int.class);
245             getStringMethod.begin();
246             getStringMethod.getStatic(unitClassName, CONSTANTS.symbolName(), CONSTANTS.descriptor())
247                         .load(Type.INT, 0)
248                         .arrayload()
249                         .checkcast(String.class)
250                         ._return();
251             getStringMethod.end();
252         }
253 
254         if (constantMethodNeeded.contains(PropertyMap.class)) {
255             // $getMap - get the ith entry from the constants table and cast to PropertyMap.
256             final MethodEmitter getMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), GET_MAP.symbolName(), PropertyMap.class, int.class);
257             getMapMethod.begin();
258             getMapMethod.loadConstants()
259                         .load(Type.INT, 0)
260                         .arrayload()
261                         .checkcast(PropertyMap.class)
262                         ._return();
263             getMapMethod.end();
264 
265             // $setMap - overwrite an existing map.
266             final MethodEmitter setMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), SET_MAP.symbolName(), void.class, int.class, PropertyMap.class);
267             setMapMethod.begin();
268             setMapMethod.loadConstants()
269                         .load(Type.INT, 0)
270                         .load(Type.OBJECT, 1)
271                         .arraystore();
272             setMapMethod.returnVoid();
273             setMapMethod.end();
274         }
275 
276         // $getXXXX$array - get the ith entry from the constants table and cast to XXXX[].
277         for (final Class<?> cls : constantMethodNeeded) {
278             if (cls.isArray()) {
279                 defineGetArrayMethod(cls);
280             }
281         }
282     }
283 
284     /**
285      * Constructs a primitive specific method for getting the ith entry from the constants table and cast.
286      * @param cls Array class.
287      */
288     private void defineGetArrayMethod(final Class<?> cls) {
289         assert unitClassName != null;
290 
291         final String        methodName     = getArrayMethodName(cls);
292         final MethodEmitter getArrayMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), methodName, cls, int.class);
293 
294         getArrayMethod.begin();
295         getArrayMethod.getStatic(unitClassName, CONSTANTS.symbolName(), CONSTANTS.descriptor())
296                       .load(Type.INT, 0)
297                       .arrayload()
298                       .checkcast(cls)
299                       .dup()
300                       .arraylength()
301                       .invoke(staticCallNoLookup(Arrays.class, "copyOf", cls, cls, int.class))
302                       ._return();
303         getArrayMethod.end();
304     }
305 
306     /**
307      * Generate the name of a get array from constant pool method.
308      * @param cls Name of array class.
309      * @return Method name.
310      */
311     static String getArrayMethodName(final Class<?> cls) {
312         assert cls.isArray();
313         return GET_ARRAY_PREFIX.symbolName() + cls.getComponentType().getSimpleName() + GET_ARRAY_SUFFIX.symbolName();
314     }
315 
316     /**
317      * Ensure a get constant method is issued for the class.
318      * @param cls Class of constant.
319      */
320     void needGetConstantMethod(final Class<?> cls) {
321         constantMethodNeeded.add(cls);
322     }
323 
324     /**
325      * Inspect class name and decide whether we are generating a ScriptObject class
326      *
327      * @param scriptPrefix the script class prefix for the current script
328      * @param type         the type to check
329      *
330      * @return true if type is ScriptObject
331      */
332     private static boolean isScriptObject(final String scriptPrefix, final String type) {
333         if (type.startsWith(scriptPrefix)) {
334             return true;
335         } else if (type.equals(CompilerConstants.className(ScriptObject.class))) {
336             return true;
337         } else if (type.startsWith(Compiler.OBJECTS_PACKAGE)) {
338             return true;
339         }
340 
341         return false;
342     }
343 
344     /**
345      * Call at beginning of class emission
346      * @see Emitter
347      */
348     @Override
349     public void begin() {
350         classStarted = true;
351     }
352 
353     /**
354      * Call at end of class emission
355      * @see Emitter
356      */
357     @Override
358     public void end() {
359         assert classStarted;
360 
361         if (unitClassName != null) {
362             defineCommonUtilities();
363         }
364 
365         cw.visitEnd();
366         classStarted = false;
367         classEnded   = true;
368         assert methodsStarted.isEmpty() : "methodsStarted not empty " + methodsStarted;
369     }
370 
371     /**
372      * Disassemble an array of byte code.
373      * @param bytecode  byte array representing bytecode
374      * @return disassembly as human readable string
375      */
376     static String disassemble(final byte[] bytecode) {
377         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
378         try (final PrintWriter pw = new PrintWriter(baos)) {
379             new ClassReader(bytecode).accept(new TraceClassVisitor(pw), 0);
380         }
381         return new String(baos.toByteArray());
382     }
383 
384     /**
385      * @return env used for class emission
386      */
387     ScriptEnvironment getEnv() {
388         return env;
389     }
390 
391     /**
392      * Call back from MethodEmitter for method start
393      *
394      * @see MethodEmitter
395      *
396      * @param method method emitter.
397      */
398     void beginMethod(final MethodEmitter method) {
399         assert !methodsStarted.contains(method);
400         methodsStarted.add(method);
401     }
402 
403     /**
404      * Call back from MethodEmitter for method end
405      *
406      * @see MethodEmitter
407      *
408      * @param method
409      */
410     void endMethod(final MethodEmitter method) {
411         assert methodsStarted.contains(method);
412         methodsStarted.remove(method);
413     }
414 
415     SplitMethodEmitter method(final SplitNode splitNode, final String methodName, final Class<?> rtype, final Class<?>... ptypes) {
416         return new SplitMethodEmitter(this, methodVisitor(EnumSet.of(Flag.PUBLIC, Flag.STATIC), methodName, rtype, ptypes), splitNode);
417     }
418 
419     /**
420      * Add a new method to the class - defaults to public method
421      *
422      * @param methodName name of method
423      * @param rtype      return type of the method
424      * @param ptypes     parameter types the method
425      *
426      * @return method emitter to use for weaving this method
427      */
428     MethodEmitter method(final String methodName, final Class<?> rtype, final Class<?>... ptypes) {
429         return method(DEFAULT_METHOD_FLAGS, methodName, rtype, ptypes); //TODO why public default ?
430     }
431 
432     /**
433      * Add a new method to the class - defaults to public method
434      *
435      * @param methodFlags access flags for the method
436      * @param methodName  name of method
437      * @param rtype       return type of the method
438      * @param ptypes      parameter types the method
439      *
440      * @return method emitter to use for weaving this method
441      */
442     MethodEmitter method(final EnumSet<Flag> methodFlags, final String methodName, final Class<?> rtype, final Class<?>... ptypes) {
443         return new MethodEmitter(this, methodVisitor(methodFlags, methodName, rtype, ptypes));
444     }
445 
446     /**
447      * Add a new method to the class - defaults to public method
448      *
449      * @param methodName name of method
450      * @param descriptor descriptor of method
451      *
452      * @return method emitter to use for weaving this method
453      */
454     MethodEmitter method(final String methodName, final String descriptor) {
455         return method(DEFAULT_METHOD_FLAGS, methodName, descriptor);
456     }
457 
458     /**
459      * Add a new method to the class - defaults to public method
460      *
461      * @param methodFlags access flags for the method
462      * @param methodName  name of method
463      * @param descriptor  descriptor of method
464      *
465      * @return method emitter to use for weaving this method
466      */
467     MethodEmitter method(final EnumSet<Flag> methodFlags, final String methodName, final String descriptor) {
468         return new MethodEmitter(this, cw.visitMethod(Flag.getValue(methodFlags), methodName, descriptor, null, null));
469     }
470 
471     /**
472      * Add a new method to the class, representing a function node
473      *
474      * @param functionNode the function node to generate a method for
475      * @return method emitter to use for weaving this method
476      */
477     MethodEmitter method(final FunctionNode functionNode) {
478         final MethodVisitor mv = cw.visitMethod(
479             ACC_PUBLIC | ACC_STATIC | (functionNode.isVarArg() ? ACC_VARARGS : 0),
480             functionNode.getName(),
481             new FunctionSignature(functionNode).toString(),
482             null,
483             null);
484 
485         return new MethodEmitter(this, mv, functionNode);
486     }
487 
488     /**
489      * Start generating the <clinit> method in the class
490      *
491      * @return method emitter to use for weaving <clinit>
492      */
493     MethodEmitter clinit() {
494         return method(EnumSet.of(Flag.STATIC), CLINIT.symbolName(), void.class);
495     }
496 
497     /**
498      * Start generating an <init>()V method in the class
499      *
500      * @return method emitter to use for weaving <init>()V
501      */
502     MethodEmitter init() {
503         return method(INIT.symbolName(), void.class);
504     }
505 
506     /**
507      * Start generating an <init>()V method in the class
508      *
509      * @param ptypes parameter types for constructor
510      * @return method emitter to use for weaving <init>()V
511      */
512     MethodEmitter init(final Class<?>... ptypes) {
513         return method(INIT.symbolName(), void.class, ptypes);
514     }
515 
516     /**
517      * Start generating an <init>(...)V method in the class
518      *
519      * @param flags  access flags for the constructor
520      * @param ptypes parameter types for the constructor
521      *
522      * @return method emitter to use for weaving <init>(...)V
523      */
524     MethodEmitter init(final EnumSet<Flag> flags, final Class<?>... ptypes) {
525         return method(flags, INIT.symbolName(), void.class, ptypes);
526     }
527 
528     /**
529      * Add a field to the class, initialized to a value
530      *
531      * @param fieldFlags flags, e.g. should it be static or public etc
532      * @param fieldName  name of field
533      * @param fieldType  the type of the field
534      * @param value      the value
535      *
536      * @see ClassEmitter.Flag
537      */
538     final void field(final EnumSet<Flag> fieldFlags, final String fieldName, final Class<?> fieldType, final Object value) {
539         cw.visitField(Flag.getValue(fieldFlags), fieldName, typeDescriptor(fieldType), null, value).visitEnd();
540     }
541 
542     /**
543      * Add a field to the class
544      *
545      * @param fieldFlags access flags for the field
546      * @param fieldName  name of field
547      * @param fieldType  type of the field
548      *
549      * @see ClassEmitter.Flag
550      */
551     final void field(final EnumSet<Flag> fieldFlags, final String fieldName, final Class<?> fieldType) {
552         field(fieldFlags, fieldName, fieldType, null);
553     }
554 
555     /**
556      * Add a field to the class - defaults to public
557      *
558      * @param fieldName  name of field
559      * @param fieldType  type of field
560      */
561     final void field(final String fieldName, final Class<?> fieldType) {
562         field(EnumSet.of(Flag.PUBLIC), fieldName, fieldType, null);
563     }
564 
565     /**
566      * Return a bytecode array from this ClassEmitter. The ClassEmitter must
567      * have been ended (having its end function called) for this to work.
568      *
569      * @return byte code array for generated class, null if class generation hasn't been ended with {@link ClassEmitter#end()}
570      */
571     byte[] toByteArray() {
572         assert classEnded;
573         if (!classEnded) {
574             return null;
575         }
576 
577         return cw.toByteArray();
578     }
579 
580     /**
581      * Abstraction for flags used in class emission
582      *
583      * We provide abstraction separating these from the underlying bytecode
584      * emitter.
585      *
586      * Flags are provided for method handles, protection levels, static/virtual
587      * fields/methods.
588      */
589     static enum Flag {
590         /** method handle with static access */
591         HANDLE_STATIC(H_INVOKESTATIC),
592         /** method handle with new invoke special access */
593         HANDLE_NEWSPECIAL(H_NEWINVOKESPECIAL),
594         /** method handle with invoke special access */
595         HANDLE_SPECIAL(H_INVOKESPECIAL),
596         /** method handle with invoke virtual access */
597         HANDLE_VIRTUAL(H_INVOKEVIRTUAL),
598         /** method handle with invoke interface access */
599         HANDLE_INTERFACE(H_INVOKEINTERFACE),
600 
601         /** final access */
602         FINAL(ACC_FINAL),
603         /** static access */
604         STATIC(ACC_STATIC),
605         /** public access */
606         PUBLIC(ACC_PUBLIC),
607         /** private access */
608         PRIVATE(ACC_PRIVATE);
609 
610         private int value;
611 
612         private Flag(final int value) {
613             this.value = value;
614         }
615 
616         /**
617          * Get the value of this flag
618          * @return the int value
619          */
620         int getValue() {
621             return value;
622         }
623 
624         /**
625          * Return the corresponding ASM flag value for an enum set of flags
626          *
627          * @param flags enum set of flags
628          * @return an integer value representing the flags intrinsic values or:ed together
629          */
630         static int getValue(final EnumSet<Flag> flags) {
631             int v = 0;
632             for (final Flag flag : flags) {
633                 v |= flag.getValue();
634             }
635             return v;
636         }
637     }
638 
639     private MethodVisitor methodVisitor(EnumSet<Flag> flags, final String methodName, final Class<?> rtype, final Class<?>... ptypes) {
640         return cw.visitMethod(Flag.getValue(flags), methodName, methodDescriptor(rtype, ptypes), null, null);
641     }
642 
643 }