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