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 }