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