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