1 /*
  2  * Copyright (c) 2025, 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  * @test
 26  * @summary Verifier should verify ClassFileLoadHook bytes even if on bootclasspath
 27  * @bug 8351654
 28  * @requires vm.jvmti
 29  * @library /test/lib
 30  * @modules java.base/jdk.internal.misc
 31  * @modules java.compiler
 32  *          java.instrument
 33  *          jdk.jartool/sun.tools.jar
 34  * @compile TestChecker.java
 35  * @run driver jdk.test.lib.helpers.ClassFileInstaller checker/TestChecker
 36  * @run main TestVerify buildAgent
 37  * @run main/othervm --patch-module=java.base=. -Dagent.retransform=false -javaagent:redefineagent.jar TestVerify
 38  * @run main/othervm --patch-module=java.base=. -Dagent.retransform=true -javaagent:redefineagent.jar TestVerify
 39  */
 40 
 41 import java.lang.invoke.MethodHandles;
 42 import java.lang.classfile.ClassFile;
 43 import java.lang.classfile.ClassTransform;
 44 import java.lang.classfile.MethodTransform;
 45 import java.lang.classfile.constantpool.InterfaceMethodRefEntry;
 46 import java.lang.classfile.instruction.ReturnInstruction;
 47 import java.lang.constant.ClassDesc;
 48 import java.lang.constant.ConstantDescs;
 49 import java.lang.constant.MethodTypeDesc;
 50 import java.lang.instrument.ClassFileTransformer;
 51 import java.lang.instrument.IllegalClassFormatException;
 52 import java.lang.instrument.Instrumentation;
 53 import java.nio.file.Files;
 54 import java.nio.file.Path;
 55 import java.security.ProtectionDomain;
 56 
 57 import java.io.PrintWriter;
 58 import jdk.test.lib.helpers.ClassFileInstaller;
 59 import java.io.FileNotFoundException;
 60 
 61 public class TestVerify {
 62 
 63     private static final String CLASS_TO_BREAK = "java.util.Date";
 64     private static final String INTERNAL_CLASS_TO_BREAK = CLASS_TO_BREAK.replace('.', '/');
 65     private static final boolean DEBUG = false;
 66 
 67     private static class BadTransformer implements ClassFileTransformer {
 68 
 69         @Override
 70         public byte[] transform(Module module, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
 71 
 72             if (className.equals(INTERNAL_CLASS_TO_BREAK)) {
 73                 System.out.println("Instrumenting modular class " + INTERNAL_CLASS_TO_BREAK);
 74 
 75                 var methodTransform = MethodTransform.transformingCode((builder, element) -> {
 76                     if (element instanceof ReturnInstruction) {
 77                         System.out.println("Injecting bug");
 78                         // THE BUG! insert broken function call
 79 
 80                         var checkerDesc = ClassDesc.of("checker", "TestChecker");
 81                         builder.invokestatic(checkerDesc, "instance", MethodTypeDesc.of(checkerDesc), true);
 82 
 83                         // dup the instance ref, this is just to get a bad argument to the next method call
 84                         builder.dup();
 85 
 86                         // then call a check method that doesn't take that type, but we have the wrong desc
 87                         builder.invokeinterface(checkerDesc, "check", MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Integer));
 88 
 89                         System.out.println("Done injecting bug");
 90                     }
 91                     builder.with(element);
 92                 });
 93                 var classTransform = ClassTransform.transformingMethods(mm -> mm.methodName().stringValue().equals("parse"), methodTransform);
 94 
 95                 byte[] bytes;
 96                 try {
 97                     var cf = ClassFile.of();
 98                     var existingClass = cf.parse(classfileBuffer);
 99                     bytes = cf.transformClass(existingClass, classTransform);
100 
101                     if (DEBUG) Files.write(Path.of("bad.class"), bytes);
102                 } catch (Throwable e) {
103                     throw new AssertionError(e);
104                 }
105                 return bytes;
106             }
107             return null;
108         }
109     }
110 
111     static Instrumentation inst = null;
112 
113     public static void premain(String args, Instrumentation instrumentation) throws Exception {
114         System.out.println("Premain");
115         inst = instrumentation;
116     }
117 
118     private static void buildAgent() {
119         try {
120             ClassFileInstaller.main("TestVerify");
121         } catch (Exception e) {
122             throw new RuntimeException("Could not write agent classfile", e);
123         }
124 
125         try {
126             PrintWriter pw = new PrintWriter("MANIFEST.MF");
127             pw.println("Premain-Class: TestVerify");
128             pw.println("Agent-Class: TestVerify");
129             pw.println("Can-Retransform-Classes: true");
130             pw.println("Can-Redefine-Classes: true");
131             pw.close();
132         } catch (FileNotFoundException e) {
133             throw new RuntimeException("Could not write manifest file for the agent", e);
134         }
135 
136         sun.tools.jar.Main jarTool = new sun.tools.jar.Main(System.out, System.err, "jar");
137         if (!jarTool.run(new String[] { "-cmf", "MANIFEST.MF", "redefineagent.jar", "TestVerify.class" })) {
138             throw new RuntimeException("Could not write the agent jar file");
139         }
140     }
141 
142     public static void main(String argv[]) throws Exception {
143         if (argv.length == 1 && argv[0].equals("buildAgent")) {
144             buildAgent();
145             return;
146         }
147 
148         // double check our class hasn't been loaded yet
149         for (Class clazz : inst.getAllLoadedClasses()) {
150             if (clazz.getName().equals(CLASS_TO_BREAK)) {
151                 throw new AssertionError("Oops! Class " + CLASS_TO_BREAK + " is already loaded, the test can't work");
152             }
153         }
154 
155         boolean retransform = Boolean.getBoolean("agent.retransform");
156 
157         try {
158             if (retransform) {
159                 // Retransform the class for the VerifyError.
160                 var clazz = Class.forName(CLASS_TO_BREAK);
161                 inst.addTransformer(new BadTransformer(), true);
162                 inst.retransformClasses(clazz);
163             } else {
164                 // Load the class instrumented with CFLH for the VerifyError.
165                 inst.addTransformer(new BadTransformer());
166                 Class<?> cls = Class.forName(CLASS_TO_BREAK);
167                 System.out.println("class loaded" + cls);
168             }
169             throw new RuntimeException("Failed: Did not throw VerifyError");
170         } catch (VerifyError e) {
171             System.out.println("Passed: VerifyError " + e.getMessage());
172         }
173     }
174 }