1 /*
   2  * Copyright (c) 2018, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 /**
  26  * @test
  27  * @summary test auxiliary classes 
  28  *
  29  * @modules java.base/jdk.internal.org.objectweb.asm
  30  *          java.instrument
  31  * @compile ../asmlib/Instrumentor.java DefineAuxClassTest.java DefineAuxClassApp.java
  32  * @run shell/timeout=240 MakeAgent.sh 'Can-Retransform-Classes: true'
  33  * @run main/othervm -javaagent:DefineAuxClassTest.jar p.DefineAuxClassApp
  34  */
  35 
  36 package p;
  37 
  38 import java.lang.instrument.ClassFileTransformer;
  39 import java.lang.instrument.ClassFileTransformer.ClassDefiner;
  40 import java.lang.instrument.Instrumentation;
  41 import java.lang.instrument.IllegalClassFormatException;
  42 
  43 import java.security.ProtectionDomain;
  44 import java.util.stream.Stream;
  45 import java.util.concurrent.atomic.AtomicBoolean;
  46 import java.nio.file.*;
  47 import java.io.IOException;
  48 import java.io.OutputStream;
  49 import jdk.internal.org.objectweb.asm.ClassReader;
  50 import jdk.internal.org.objectweb.asm.ClassVisitor;
  51 import jdk.internal.org.objectweb.asm.ClassWriter;
  52 import jdk.internal.org.objectweb.asm.MethodVisitor;
  53 import jdk.internal.org.objectweb.asm.Opcodes;
  54 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  55 
  56 import asmlib.*;
  57 
  58 class DefineAuxClassTest {
  59 
  60     static ClassFileTransformer t1, t2, t3, t4;
  61     static Instrumentation inst;
  62     static boolean succeeded = true;
  63     static int markCount = 0;
  64     static int[] markGolden = {30, 40, 20, 30, 40, 20, 30, 40, 20, 30, 40, 20,
  65                                11, 40, 20, 11, 40, 20, 11, 40, 20, 11, 40, 20};
  66 
  67     static class Tr implements ClassFileTransformer {
  68         final String trname;
  69         final boolean onLoad;
  70         final int loadIndex;
  71         final boolean onRedef;
  72         final int redefIndex;
  73         final String cname;
  74         final String nname;
  75         final String auxClassHelperName;
  76         final AtomicBoolean auxClassDefined;
  77 
  78         Tr(String trname, boolean onLoad, int loadIndex, boolean onRedef, int redefIndex,
  79            String cname, String nname) {
  80             this.trname = trname;
  81             this.onLoad = onLoad;
  82             this.loadIndex = loadIndex;
  83             this.onRedef = onRedef;
  84             this.redefIndex = redefIndex;
  85             this.cname = cname;
  86             this.nname = nname;
  87             
  88             int index = cname.lastIndexOf(".");
  89             if (index == -1) {
  90                 this.auxClassHelperName = trname + "AuxClassHelper";
  91             } else {
  92                 String pn = cname.substring(0, index);
  93                 this.auxClassHelperName = pn + "." + trname + "AuxClassHelper";
  94 
  95             }
  96             this.auxClassDefined = new AtomicBoolean(false);
  97         }
  98 
  99         public byte[] transform(ClassDefiner classDefiner,
 100                                 Module module,
 101                                 ClassLoader loader,
 102                                 String className,
 103                                 Class<?> classBeingRedefined,
 104                                 ProtectionDomain    protectionDomain,
 105                                 byte[] classfileBuffer) throws IllegalClassFormatException {
 106             boolean redef = classBeingRedefined != null;
 107             String name = className.replace('/', '.');
 108             if ((redef? onRedef : onLoad) && className != null && name.equals(cname)) {
 109                 byte[] bytes = auxClassHelper();
 110                 if (!auxClassDefined.getAndSet(true)) {
 111                     // first time defining auxiliary class
 112                     Class<?> c = classDefiner.defineClass(bytes);
 113                     if (!c.getName().equals(auxClassHelperName)) {
 114                         throw new RuntimeException("unexpected class defined: " + c);
 115                     }
 116                 } else {
 117                     try {
 118                         Class<?> c = classDefiner.defineClass(bytes);
 119                         throw new RuntimeException(auxClassHelperName + " should fail to be defined");
 120                     } catch (Throwable e) {}
 121                 }
 122 
 123                 int fixedIndex = redef ? redefIndex : loadIndex;
 124                 try {
 125                     String internalName = auxClassHelperName.replace('.', '/');
 126                     byte[] newcf = Instrumentor.instrFor(classfileBuffer)
 127                                    .addMethodEntryInjection(
 128                                         nname,
 129                                         (h)->{
 130                                            h.push(fixedIndex);
 131                                            h.invokeStatic(internalName, "callTracker", "(I)V", false);
 132                                         })
 133                                    .apply();
 134                     System.err.println(trname + ": " + className + " index: " + fixedIndex +
 135                                        (redef? " REDEF" : " LOAD") +
 136                                        " len before: " + classfileBuffer.length +
 137                                        " after: " + newcf.length);
 138                     return newcf;
 139                 } catch (Throwable ex) {
 140                     System.err.println("Injection failure: " + ex);
 141                     ex.printStackTrace();
 142                 }
 143             }
 144             return null;
 145         }
 146 
 147         /*
 148          * Generates the auxiliary class
 149          */
 150         byte[] auxClassHelper() {
 151             ClassWriter cw = new ClassWriter(0);
 152             String internalName = auxClassHelperName.replace('.', '/');
 153             cw.visit(V1_8, ACC_PUBLIC, internalName, null, "java/lang/Object", null);
 154             MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "callTracker", "(I)V", null, null);
 155             mv.visitCode();
 156             mv.visitVarInsn(ILOAD, 0);
 157             mv.visitMethodInsn(INVOKESTATIC, "p/DefineAuxClassTest", "callTracker", "(I)V");
 158             mv.visitInsn(RETURN);
 159             mv.visitMaxs(2, 2);
 160             mv.visitEnd();
 161             cw.visitEnd();
 162 
 163             return cw.toByteArray();
 164         }
 165     }
 166 
 167     static void write_buffer(String fname, byte[] buffer) {
 168         try {
 169             Path path = Paths.get(fname);
 170             if (path.getParent() != null) {
 171                 Files.createDirectories(path.getParent());
 172             }
 173             try (OutputStream outStream = Files.newOutputStream(path)) {
 174                 outStream.write(buffer, 0, buffer.length);
 175             }
 176         } catch (IOException ex) {
 177             System.err.println("EXCEPTION in write_buffer: " + ex);
 178         }
 179     }
 180 
 181     public static void premain(String agentArgs, Instrumentation instArg) {
 182         inst = instArg;
 183         System.err.println("Premain");
 184 
 185         t1 = new Tr("TR1", false, 10, true, 11, "p.DefineAuxClassApp", "foo");
 186         inst.addTransformer(t1, true);
 187         t2 = new Tr("TN2", true,  20, true, 21, "p.DefineAuxClassApp", "foo");
 188         inst.addTransformer(t2, false);
 189         t3 = new Tr("TR3", true,  30, true, 31, "p.DefineAuxClassApp", "foo");
 190         inst.addTransformer(t3, true);
 191         t4 = new Tr("TN4", true,  40, true, 41, "p.DefineAuxClassApp", "foo");
 192         inst.addTransformer(t4, false);
 193     }
 194 
 195     public static void undo() {
 196         inst.removeTransformer(t3);
 197         try {
 198             System.err.println("RETRANSFORM");
 199             inst.retransformClasses(new DefineAuxClassApp().getClass());
 200         } catch (Exception ex) {
 201             System.err.println("EXCEPTION on undo: " + ex);
 202         }
 203     }
 204 
 205     public static boolean succeeded() {
 206         return succeeded && markCount == markGolden.length;
 207     }
 208 
 209     public static void callTracker(int mark) {
 210         System.err.println("got mark " + mark);
 211         if (markCount >= markGolden.length || mark != markGolden[markCount++]) {
 212             succeeded = false;
 213         }
 214     }
 215 
 216 }