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 }