1 /* 2 * Copyright (c) 2024, 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 Test for value class redefinition 27 * @comment The code is based on test/jdk/java/lang/instrument/RedefineNestmateAttr 28 * @comment modified for value classes. 29 * 30 * @library /test/lib 31 * @modules java.compiler 32 * java.instrument 33 * @enablePreview 34 * @run main RedefineClassHelper 35 * @compile Host/Host.java 36 * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class*=trace RedefineValueClass Host 37 * @compile HostA/Host.java 38 * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class*=trace RedefineValueClass HostA 39 * @compile HostAB/Host.java 40 * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class*=trace RedefineValueClass HostAB 41 * @compile HostI/Host.java 42 * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class*=trace RedefineValueClass HostI 43 */ 44 45 /* Test Description 46 47 The basic test class is called Host. 48 Each variant of the class is defined in source code in its own directory i.e. 49 50 Host/Host.java defines zero fields 51 Class HostA/Host.java has field "int A" 52 Class HostAB/Host.java has fields "int A" and "long B" (in that order) 53 Class HostI/Host.java is an instance class with zero fields 54 etc. 55 56 Each Host class has the form: 57 58 public value class Host { 59 // fields here 60 public static String getID() { return "<directory name>/Host.java"; } 61 62 public int m() { 63 return 1; // original class 64 } 65 66 public Host(int A, long B, char C) { 67 ... 68 } 69 } 70 71 The only exception is class in HostI dir which is instance class. 72 73 Under each directory is a directory "redef" with a modified version of the Host 74 class that changes the ID to e.g. Host/redef/Host.java, and the method m() 75 returns 2. This allows us to check we have the redefined class loaded. 76 77 Using Host' to represent the redefined version we test different redefinition combinations. 78 79 We can only directly load one class Host per classloader, so to run all the 80 groups we either need to use new classloaders, or we reinvoke the test 81 requesting a different primary directory. We chose the latter using 82 multiple @run tags. So we preceed as follows: 83 84 @compile Host/Host.java 85 @run RedefineValueClass Host 86 @compile HostA/Host.java - replaces previous Host.class 87 @run RedefineValueClass HostA 88 @compile HostAB/Host.java - replaces previous Host.class 89 @run RedefineValueClass HostAB 90 etc. 91 92 Within the test we directly compile redefined versions of the classes, 93 using CompilerUtil, and then read the .class file directly as a byte[] 94 to use with the RedefineClassHelper. 95 */ 96 97 import java.nio.file.Files; 98 import java.nio.file.Path; 99 import java.nio.file.Paths; 100 import jdk.test.lib.compiler.CompilerUtils; 101 import static jdk.test.lib.Asserts.assertTrue; 102 103 public class RedefineValueClass { 104 105 static final Path SRC = Paths.get(System.getProperty("test.src")); 106 static final Path DEST = Paths.get(System.getProperty("test.classes")); 107 108 public static void main(String[] args) throws Throwable { 109 String origin = args[0]; 110 System.out.println("Testing original Host class from " + origin); 111 112 // Make sure the Host class loaded directly is an original version 113 // and from the expected location. Use a ctor common to all Host 114 // classes. 115 Host h = new Host(3, 4, 'a'); 116 assertTrue(h.m() == 1); 117 assertTrue(Host.getID().startsWith(origin + "/")); 118 119 String[] badTransforms = null; // directories of bad classes 120 String[] goodTransforms = null; // directories of good classes 121 122 switch (origin) { 123 case "Host": 124 badTransforms = new String[] { 125 "HostI", // value class to instance class 126 "HostA" // add field 127 }; 128 goodTransforms = new String[] { 129 origin 130 }; 131 break; 132 133 case "HostA": 134 badTransforms = new String[] { 135 "Host", // remove field 136 "HostAB", // add field 137 "HostB" // change field 138 }; 139 goodTransforms = new String[] { 140 origin 141 }; 142 break; 143 144 case "HostAB": 145 badTransforms = new String[] { 146 "HostA", // remove field 147 "HostABC", // add field 148 "HostAC", // change fields 149 "HostBA" // reorder fields 150 }; 151 goodTransforms = new String[] { 152 origin, 153 }; 154 break; 155 156 case "HostI": // instance class 157 badTransforms = new String[] { 158 "Host", // instance class to value class 159 }; 160 break; 161 162 default: 163 throw new RuntimeException("Unknown test directory: " + origin); 164 } 165 166 // Compile and check bad transformations 167 checkBadTransforms(Host.class, badTransforms); 168 169 // Compile and check good transformations 170 if (goodTransforms != null) { 171 checkGoodTransforms(Host.class, goodTransforms); 172 } 173 } 174 175 static void checkGoodTransforms(Class<?> c, String[] dirs) throws Throwable { 176 for (String dir : dirs) { 177 dir += "/redef"; 178 System.out.println("Trying good retransform from " + dir); 179 byte[] buf = bytesForHostClass(dir); 180 RedefineClassHelper.redefineClass(c, buf); 181 182 // Test redefintion worked 183 Host h = new Host(3, 4, 'a'); 184 assertTrue(h.m() == 2); 185 System.out.println("Redefined ID: " + Host.getID()); 186 assertTrue(Host.getID().startsWith(dir)); 187 } 188 } 189 190 static void checkBadTransforms(Class<?> c, String[] dirs) throws Throwable { 191 for (String dir : dirs) { 192 dir += "/redef"; 193 System.out.println("Trying bad retransform from " + dir); 194 byte[] buf = bytesForHostClass(dir); 195 try { 196 RedefineClassHelper.redefineClass(c, buf); 197 throw new RuntimeException("Retransformation from directory " + dir + 198 " succeeded unexpectedly"); 199 } 200 catch (UnsupportedOperationException uoe) { 201 System.out.println("Got expected UnsupportedOperationException " + uoe); 202 } 203 } 204 } 205 206 static byte[] bytesForHostClass(String dir) throws Throwable { 207 compile(dir); 208 Path clsfile = DEST.resolve(dir).resolve("Host.class"); 209 System.out.println("Reading bytes from " + clsfile); 210 return Files.readAllBytes(clsfile); 211 } 212 213 static void compile(String dir) throws Throwable { 214 Path src = SRC.resolve(dir); 215 Path dst = DEST.resolve(dir); 216 System.out.println("Compiling from: " + src + "\n" + 217 " to: " + dst); 218 CompilerUtils.compile(src, dst, 219 false /* don't recurse */, 220 "--enable-preview", 221 "--source", String.valueOf(Runtime.version().feature())); 222 } 223 }