1 /* 2 * Copyright (c) 2020, 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 * @bug 8225056 27 * @summary Class redefinition must preclude changes to PermittedSubclasses attributes 28 * @comment This is a copy of test/jdk/java/lang/instrument/RedefineNestmateAttr/ 29 * @comment modified for sealed classes and the PermittedSubclasses attribute. 30 * 31 * @library /test/lib 32 * @modules java.base/jdk.internal.misc 33 * @modules java.compiler 34 * java.instrument 35 * @compile ../NamedBuffer.java 36 * @run main RedefineClassHelper 37 * @compile --enable-preview --source ${jdk.version} Host/Host.java ClassOne.java ClassTwo.java ClassThree.java ClassFour.java 38 * @compile --enable-preview --source ${jdk.version} TestPermittedSubclassesAttr.java 39 * @run main/othervm -javaagent:redefineagent.jar --enable-preview -Xlog:redefine+class+sealed=trace TestPermittedSubclassesAttr Host 40 * @compile --enable-preview --source ${jdk.version} HostA/Host.java 41 * @run main/othervm -javaagent:redefineagent.jar --enable-preview -Xlog:redefine+class+sealed=trace TestPermittedSubclassesAttr HostA 42 * @compile --enable-preview --source ${jdk.version} HostAB/Host.java 43 * @run main/othervm -javaagent:redefineagent.jar --enable-preview -Xlog:redefine+class+sealed=trace TestPermittedSubclassesAttr HostAB 44 * @compile --enable-preview --source ${jdk.version} HostABC/Host.java 45 * @run main/othervm -javaagent:redefineagent.jar --enable-preview -Xlog:redefine+class+sealed=trace TestPermittedSubclassesAttr HostABC 46 */ 47 48 /* Test Description 49 50 The basic test class is called Host and we have variants that have zero or more 51 permitted subclasses ClassOne, ClassTwo, ClassThree, and ClassFour. Each variant 52 of Host is defined in source code in its own directory i.e. 53 54 Host/Host.java defines zero permitted classes 55 Sealed class HostA/Host.java permits ClassOne 56 Sealed HostAB/Host.java permits ClassOne and ClassTwo (in that order) 57 Sealed HostABC/Host.java permits ClassOne, ClassTwo, and ClassThree (in that order) 58 etc. 59 60 Each Host class has the form: 61 62 public sealed class Host <permits zero or more classes> { 63 public static String getID() { return "<directory name>/Host.java"; } 64 65 public int m() { 66 return 1; // original class 67 } 68 } 69 70 Under each directory is a directory "redef" with a modified version of the Host 71 class that changes the ID to e.g. Host/redef/Host.java, and the method m() 72 returns 2. This allows us to check we have the redefined class loaded. 73 74 Using Host' to represent the redefined version we test redefinition 75 combinations as follows: 76 77 Host: 78 Host -> Host' - succeeds m() returns 2 79 Host -> HostA' - fails - added a permitted subclass 80 81 HostA: 82 HostA -> HostA' - succeeds m() returns 2 83 HostA -> Host' - fails - removed a permitted subclass 84 HostA -> HostAB' - fails - added a permitted subclass 85 HostA -> HostB' - fails - replaced a permitted subclass 86 87 HostAB: 88 HostAB -> HostAB' - succeeds m() returns 2 89 HostAB -> HostBA' - succeeds m() returns 2 90 HostAB -> HostA' - fails - removed a permitted subclass 91 HostAB -> HostABC' - fails - added a permitted subclass 92 HostAB -> HostAC' - fails - replaced a permitted subclass 93 94 HostABC: 95 HostABC -> HostABC' - succeeds m() returns 2 96 HostABC -> HostACB' - succeeds m() returns 2 97 HostABC -> HostBAC' - succeeds m() returns 2 98 HostABC -> HostBCA' - succeeds m() returns 2 99 HostABC -> HostCAB' - succeeds m() returns 2 100 HostABC -> HostCBA' - succeeds m() returns 2 101 HostABC -> HostAB' - fails - removed a permitted subclass 102 HostABC -> HostABCD' - fails - added a permitted subclass 103 HostABC -> HostABD' - fails - replaced a permitted subclass 104 105 More than three permitted subclasses doesn't add to the code coverage so 106 we stop here. 107 108 Note that we always try to load the redefined version even when we expect it 109 to fail. 110 111 We can only directly load one class Host per classloader, so to run all the 112 groups we either need to use new classloaders, or we reinvoke the test 113 requesting a different primary directory. We chose the latter using 114 multiple @run tags. So we preceed as follows: 115 116 @compile Host/Host.java 117 @run TestPermittedSubclassesAttr Host 118 @compile HostA/Host.java - replaces previous Host.class 119 @run TestPermittedSubclassesAttr HostA 120 @compile HostAB/Host.java - replaces previous Host.class 121 @run TestPermittedSubclassesAttr HostAB 122 etc. 123 124 Within the test we directly compile redefined versions of the classes, 125 using CompilerUtil, and then read the .class file directly as a byte[] 126 to use with the RedefineClassHelper. 127 128 */ 129 130 import java.io.File; 131 import java.io.FileInputStream; 132 import jdk.test.lib.ByteCodeLoader; 133 import jdk.test.lib.compiler.CompilerUtils; 134 import jdk.test.lib.compiler.InMemoryJavaCompiler; 135 import static jdk.test.lib.Asserts.assertTrue; 136 137 public class TestPermittedSubclassesAttr { 138 139 static final String SRC = System.getProperty("test.src"); 140 static final String DEST = System.getProperty("test.classes"); 141 static final boolean VERBOSE = Boolean.getBoolean("verbose"); 142 private static final String VERSION = Integer.toString(Runtime.version().feature()); 143 144 public static void main(String[] args) throws Throwable { 145 String origin = args[0]; 146 System.out.println("Testing original Host class from " + origin); 147 148 // Make sure the Host class loaded directly is an original version 149 // and from the expected location 150 Host h = new Host(); 151 assertTrue(h.m() == 1); 152 assertTrue(Host.getID().startsWith(origin + "/")); 153 154 String[] badTransforms; // directories of bad classes 155 String[] goodTransforms; // directories of good classes 156 157 switch (origin) { 158 case "Host": 159 badTransforms = new String[] { 160 "HostA" // add permitted subclass 161 }; 162 goodTransforms = new String[] { 163 origin 164 }; 165 break; 166 167 case "HostA": 168 badTransforms = new String[] { 169 "Host", // remove permitted subclass 170 "HostAB", // add permitted subclass 171 "HostB" // change permitted subclass 172 }; 173 goodTransforms = new String[] { 174 origin 175 }; 176 break; 177 178 case "HostAB": 179 badTransforms = new String[] { 180 "HostA", // remove permitted subclass 181 "HostABC", // add permitted subclass 182 "HostAC" // change permitted subclass 183 }; 184 goodTransforms = new String[] { 185 origin, 186 "HostBA" // reorder permitted subclasses 187 }; 188 break; 189 190 case "HostABC": 191 badTransforms = new String[] { 192 "HostAB", // remove permitted subclass 193 "HostABCD", // add permitted subclass 194 "HostABD" // change permitted subclass 195 }; 196 goodTransforms = new String[] { 197 origin, 198 "HostACB", // reorder permitted subclasses 199 "HostBAC", // reorder permitted subclasses 200 "HostBCA", // reorder permitted subclasses 201 "HostCAB", // reorder permitted subclasses 202 "HostCBA" // reorder permitted subclasses 203 }; 204 break; 205 206 default: throw new Error("Unknown test directory: " + origin); 207 } 208 209 // Compile and check bad transformations 210 checkBadTransforms(Host.class, badTransforms); 211 212 // Compile and check good transformations 213 checkGoodTransforms(Host.class, goodTransforms); 214 } 215 216 static void checkGoodTransforms(Class<?> c, String[] dirs) throws Throwable { 217 for (String dir : dirs) { 218 dir += "/redef"; 219 System.out.println("Trying good retransform from " + dir); 220 byte[] buf = bytesForHostClass(dir); 221 RedefineClassHelper.redefineClass(c, buf); 222 223 // Test redefintion worked 224 Host h = new Host(); 225 assertTrue(h.m() == 2); 226 if (VERBOSE) System.out.println("Redefined ID: " + Host.getID()); 227 assertTrue(Host.getID().startsWith(dir)); 228 } 229 } 230 231 static void checkBadTransforms(Class<?> c, String[] dirs) throws Throwable { 232 for (String dir : dirs) { 233 dir += "/redef"; 234 System.out.println("Trying bad retransform from " + dir); 235 byte[] buf = bytesForHostClass(dir); 236 try { 237 RedefineClassHelper.redefineClass(c, buf); 238 throw new Error("Retransformation from directory " + dir + 239 " succeeded unexpectedly"); 240 } 241 catch (UnsupportedOperationException uoe) { 242 if (uoe.getMessage().contains("attempted to change the class") && 243 uoe.getMessage().contains(" PermittedSubclasses")) { 244 System.out.println("Got expected exception " + uoe); 245 } 246 else throw new Error("Wrong UnsupportedOperationException", uoe); 247 } 248 } 249 } 250 251 static byte[] bytesForHostClass(String dir) throws Throwable { 252 compile("/" + dir); 253 File clsfile = new File(DEST + "/" + dir + "/Host.class"); 254 if (VERBOSE) System.out.println("Reading bytes from " + clsfile); 255 byte[] buf = null; 256 try (FileInputStream str = new FileInputStream(clsfile)) { 257 return buf = NamedBuffer.loadBufferFromStream(str); 258 } 259 } 260 261 static void compile(String dir) throws Throwable { 262 File src = new File(SRC + dir); 263 File dst = new File(DEST + dir); 264 if (VERBOSE) System.out.println("Compiling from: " + src + "\n" + 265 " to: " + dst); 266 CompilerUtils.compile(src.toPath(), 267 dst.toPath(), 268 false /* don't recurse */, 269 "-classpath", DEST, 270 "--enable-preview", 271 "--source", VERSION); 272 } 273 }