test/jdk/java/lang/instrument/RedefineNestmateAttr/TestNestmateAttr.java
changeset 50735 2f2af62dfac7
equal deleted inserted replaced
50734:0828a0f6676b 50735:2f2af62dfac7
       
     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 8046171
       
    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 }