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 }