1 /* 2 * Copyright (c) 2018, 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 8010319 27 * @summary Class redefinition must preclude changes to nest attributes 28 * @library /test/lib 29 * @modules java.base/jdk.internal.misc 30 * @modules java.compiler 31 * java.instrument 32 * jdk.jartool/sun.tools.jar 33 * @compile ../NamedBuffer.java 34 * @run main RedefineClassHelper 35 * @compile Host/Host.java 36 * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class+nestmates=trace TestNestmateAttr Host 37 * @compile HostA/Host.java 38 * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class+nestmates=trace TestNestmateAttr HostA 39 * @compile HostAB/Host.java 40 * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class+nestmates=trace TestNestmateAttr HostAB 41 * @compile HostABC/Host.java 42 * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class+nestmates=trace TestNestmateAttr HostABC 43 */ 44 45 /* Test Description 46 47 The basic test class is call Host and we have variants that have zero or more 48 nested classes named A, B, C etc. Each variant of Host is defined in source 49 code in its own directory i.e. 50 51 Host/Host.java defines zero nested classes 52 HostA/Host.java defines one nested class A 53 HostAB/Host.java defines two nested classes A and B (in that order) 54 etc. 55 56 Each Host class has the form: 57 58 public class Host { 59 public static String getID() { return "<directory name>/Host.java"; } 60 61 < zero or more empty nested classes> 62 63 public int m() { 64 return 1; // original class 65 } 66 } 67 68 Under each directory is a directory "redef" with a modified version of the Host 69 class that changes the ID to e.g. Host/redef/Host.java, and the method m() 70 returns 2. This allows us to check we have the redefined class loaded. 71 72 Using Host' to represent the redefined version we test redefinition 73 combinations as follows: 74 75 Host: 76 Host -> Host' - succeeds m() returns 2 77 Host -> HostA' - fails - added a nest member 78 79 HostA: 80 HostA -> HostA' - succeeds m() returns 2 81 HostA -> Host' - fails - removed a nest member 82 HostA -> HostAB' - fails - added a nest member 83 HostA -> HostB' - fails - replaced a nest member 84 85 HostAB: 86 HostAB -> HostAB' - succeeds m() returns 2 87 HostAB -> HostBA' - succeeds m() returns 2 88 HostAB -> HostA' - fails - removed a nest member 89 HostAB -> HostABC' - fails - added a nest member 90 HostAB -> HostAC' - fails - replaced a nest member 91 92 HostABC: 93 HostABC -> HostABC' - succeeds m() returns 2 94 HostABC -> HostACB' - succeeds m() returns 2 95 HostABC -> HostBAC' - succeeds m() returns 2 96 HostABC -> HostBCA' - succeeds m() returns 2 97 HostABC -> HostCAB' - succeeds m() returns 2 98 HostABC -> HostCBA' - succeeds m() returns 2 99 HostABC -> HostAB' - fails - removed a nest member 100 HostABC -> HostABCD' - fails - added a nest member 101 HostABC -> HostABD' - fails - replaced a nest member 102 103 More than three nested classes doesn't add to the code coverage so 104 we stop here. 105 106 Note that we always try to load the redefined version even when we expect it 107 to fail. 108 109 We can only directly load one class Host per classloader, so to run all the 110 groups we either need to use new classloaders, or we reinvoke the test 111 requesting a different primary directory. We chose the latter using 112 multiple @run tags. So we preceed as follows: 113 114 @compile Host/Host.java 115 @run TestNestmateAttr Host 116 @compile HostA/Host.java - replaces previous Host.class 117 @run TestNestmateAttr HostA 118 @compile HostAB/Host.java - replaces previous Host.class 119 @run TestNestmateAttr HostAB 120 etc. 121 122 Within the test we directly compile redefined versions of the classes, 123 using CompilerUtil, and then read the .class file directly as a byte[] 124 to use with the RedefineClassHelper. 125 126 Finally we test redefinition of the NestHost attribute - which is 127 conceptually simple, but in fact very tricky to do. We do that 128 when testing HostA so we can reuse the Host$A class. 129 130 */ 131 132 import java.io.File; 133 import java.io.FileInputStream; 134 import jdk.test.lib.ByteCodeLoader; 135 import jdk.test.lib.compiler.CompilerUtils; 136 import jdk.test.lib.compiler.InMemoryJavaCompiler; 137 import static jdk.test.lib.Asserts.assertTrue; 138 139 public class TestNestmateAttr { 140 141 static final String SRC = System.getProperty("test.src"); 142 static final String DEST = System.getProperty("test.classes"); 143 static final boolean VERBOSE = Boolean.getBoolean("verbose"); 144 145 public static void main(String[] args) throws Throwable { 146 String origin = args[0]; 147 System.out.println("Testing original Host class from " + origin); 148 149 // Make sure the Host class loaded directly is an original version 150 // and from the expected location 151 Host h = new Host(); 152 assertTrue(h.m() == 1); 153 assertTrue(Host.getID().startsWith(origin + "/")); 154 155 String[] badTransforms; // directories of bad classes 156 String[] goodTransforms; // directories of good classes 157 158 boolean testNestHostChanges = false; 159 160 switch (origin) { 161 case "Host": 162 badTransforms = new String[] { 163 "HostA" // add member 164 }; 165 goodTransforms = new String[] { 166 origin 167 }; 168 break; 169 170 case "HostA": 171 badTransforms = new String[] { 172 "Host", // remove member 173 "HostAB", // add member 174 "HostB" // change member 175 }; 176 goodTransforms = new String[] { 177 origin 178 }; 179 testNestHostChanges = true; 180 break; 181 182 case "HostAB": 183 badTransforms = new String[] { 184 "HostA", // remove member 185 "HostABC", // add member 186 "HostAC" // change member 187 }; 188 goodTransforms = new String[] { 189 origin, 190 "HostBA" // reorder members 191 }; 192 break; 193 194 case "HostABC": 195 badTransforms = new String[] { 196 "HostAB", // remove member 197 "HostABCD", // add member 198 "HostABD" // change member 199 }; 200 goodTransforms = new String[] { 201 origin, 202 "HostACB", // reorder members 203 "HostBAC", // reorder members 204 "HostBCA", // reorder members 205 "HostCAB", // reorder members 206 "HostCBA" // reorder members 207 }; 208 break; 209 210 default: throw new Error("Unknown test directory: " + origin); 211 } 212 213 // Compile and check bad transformations 214 checkBadTransforms(Host.class, badTransforms); 215 216 // Compile and check good transformations 217 checkGoodTransforms(Host.class, goodTransforms); 218 219 if (testNestHostChanges) 220 checkNestHostChanges(); 221 } 222 223 static void checkNestHostChanges() throws Throwable { 224 // case 1: remove NestHost attribute 225 // - try to redefine Host$A with a top-level 226 // class called Host$A 227 System.out.println("Trying bad retransform that removes the NestHost attribute"); 228 229 String name = "Host$A"; 230 // This is compiled as a top-level class: the $ in the name is not 231 // significant to the compiler. 232 String hostA = "public class " + name + " {}"; 233 234 // Have to do this reflectively as there is no Host$A 235 // when compiling the "Host/" case. 236 Class<?> nestedA = Class.forName(name); 237 238 try { 239 RedefineClassHelper.redefineClass(nestedA, hostA); 240 throw new Error("Retransformation to top-level class " + name + 241 " succeeded unexpectedly"); 242 } 243 catch (UnsupportedOperationException uoe) { 244 if (uoe.getMessage().contains("attempted to change the class Nest")) { 245 System.out.println("Got expected exception " + uoe); 246 } 247 else throw new Error("Wrong UnsupportedOperationException", uoe); 248 } 249 250 // case 2: add NestHost attribute 251 // - This is tricky because the class with no NestHost attribute 252 // has to have the name of a nested class! Plus we need the 253 // redefining class in bytecode form. 254 // - Use the InMemoryJavaCompiler plus ByteCodeLoader to load 255 // the top-level Host$A class 256 // - Try to redefine that class with a real nested Host$A 257 258 System.out.println("Trying bad retransform that adds the NestHost attribute"); 259 byte[] bytes = InMemoryJavaCompiler.compile(name, hostA); 260 Class<?> topLevelHostA = ByteCodeLoader.load(name, bytes); 261 262 byte[] nestedBytes; 263 File clsfile = new File(DEST + "/" + name + ".class"); 264 if (VERBOSE) System.out.println("Reading bytes from " + clsfile); 265 try (FileInputStream str = new FileInputStream(clsfile)) { 266 nestedBytes = NamedBuffer.loadBufferFromStream(str); 267 } 268 try { 269 RedefineClassHelper.redefineClass(topLevelHostA, nestedBytes); 270 throw new Error("Retransformation to nested class " + name + 271 " succeeded unexpectedly"); 272 } 273 catch (UnsupportedOperationException uoe) { 274 if (uoe.getMessage().contains("attempted to change the class Nest")) { 275 System.out.println("Got expected exception " + uoe); 276 } 277 else throw new Error("Wrong UnsupportedOperationException", uoe); 278 } 279 280 // case 3: replace the NestHost attribute 281 // - the easiest way (perhaps only reasonable way) to do this 282 // is to search for the Utf8 entry used by the Constant_ClassRef, 283 // set in the NestHost attribute, and edit it to refer to a different 284 // name. 285 System.out.println("Trying bad retransform that changes the NestHost attribute"); 286 int utf8Entry_length = 7; 287 boolean found = false; 288 for (int i = 0; i < nestedBytes.length - utf8Entry_length; i++) { 289 if (nestedBytes[i] == 1 && // utf8 tag 290 nestedBytes[i+1] == 0 && // msb of length 291 nestedBytes[i+2] == 4 && // lsb of length 292 nestedBytes[i+3] == (byte) 'H' && 293 nestedBytes[i+4] == (byte) 'o' && 294 nestedBytes[i+5] == (byte) 's' && 295 nestedBytes[i+6] == (byte) 't') { 296 297 if (VERBOSE) System.out.println("Appear to have found Host utf8 entry starting at " + i); 298 299 nestedBytes[i+3] = (byte) 'G'; 300 found = true; 301 break; 302 } 303 } 304 305 if (!found) 306 throw new Error("Could not locate 'Host' name in byte array"); 307 308 try { 309 RedefineClassHelper.redefineClass(nestedA, nestedBytes); 310 throw new Error("Retransformation to modified nested class" + 311 " succeeded unexpectedly"); 312 } 313 catch (UnsupportedOperationException uoe) { 314 if (uoe.getMessage().contains("attempted to change the class Nest")) { 315 System.out.println("Got expected exception " + uoe); 316 } 317 else throw new Error("Wrong UnsupportedOperationException", uoe); 318 } 319 320 } 321 322 static void checkGoodTransforms(Class<?> c, String[] dirs) throws Throwable { 323 for (String dir : dirs) { 324 dir += "/redef"; 325 System.out.println("Trying good retransform from " + dir); 326 byte[] buf = bytesForHostClass(dir); 327 RedefineClassHelper.redefineClass(c, buf); 328 329 // Test redefintion worked 330 Host h = new Host(); 331 assertTrue(h.m() == 2); 332 if (VERBOSE) System.out.println("Redefined ID: " + Host.getID()); 333 assertTrue(Host.getID().startsWith(dir)); 334 } 335 } 336 337 static void checkBadTransforms(Class<?> c, String[] dirs) throws Throwable { 338 for (String dir : dirs) { 339 dir += "/redef"; 340 System.out.println("Trying bad retransform from " + dir); 341 byte[] buf = bytesForHostClass(dir); 342 try { 343 RedefineClassHelper.redefineClass(c, buf); 344 throw new Error("Retransformation from directory " + dir + 345 " succeeded unexpectedly"); 346 } 347 catch (UnsupportedOperationException uoe) { 348 if (uoe.getMessage().contains("attempted to change the class Nest")) { 349 System.out.println("Got expected exception " + uoe); 350 } 351 else throw new Error("Wrong UnsupportedOperationException", uoe); 352 } 353 } 354 } 355 356 static byte[] bytesForHostClass(String dir) throws Throwable { 357 compile("/" + dir); 358 File clsfile = new File(DEST + "/" + dir + "/Host.class"); 359 if (VERBOSE) System.out.println("Reading bytes from " + clsfile); 360 byte[] buf = null; 361 try (FileInputStream str = new FileInputStream(clsfile)) { 362 return buf = NamedBuffer.loadBufferFromStream(str); 363 } 364 } 365 366 static void compile(String dir) throws Throwable { 367 File src = new File(SRC + dir); 368 File dst = new File(DEST + dir); 369 if (VERBOSE) System.out.println("Compiling from: " + src + "\n" + 370 " to: " + dst); 371 CompilerUtils.compile(src.toPath(), 372 dst.toPath(), 373 false /* don't recurse */, 374 new String[0]); 375 } 376 }