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