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