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 }