1 /* 2 * Copyright (c) 2019, 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 8192936 27 * @requires os.family != "solaris" 28 * @summary RI does not follow the JVMTI RedefineClasses spec; need to disallow adding and deleting methods 29 * @library /test/lib 30 * @modules java.base/jdk.internal.misc 31 * @modules java.compiler 32 * java.instrument 33 * jdk.jartool/sun.tools.jar 34 * @run main RedefineClassHelper 35 * @run main/othervm -javaagent:redefineagent.jar TestAddDeleteMethods AllowAddDelete=no 36 * @run main/othervm -javaagent:redefineagent.jar -XX:+AllowRedefinitionToAddDeleteMethods TestAddDeleteMethods AllowAddDelete=yes 37 */ 38 39 import static jdk.test.lib.Asserts.assertEquals; 40 import java.lang.Runnable; 41 42 // package access top-level class to avoid problem with RedefineClassHelper 43 // and nested types. 44 class A implements Runnable { 45 private void foo() { System.out.println(" OLD foo called"); } 46 public void publicFoo() { System.out.println(" OLD publicFoo called"); } 47 private final void finalFoo() { System.out.println(" OLD finalFoo called"); } 48 private static void staticFoo() { System.out.println(" OLD staticFoo called"); } 49 public void run() { foo(); publicFoo(); finalFoo(); staticFoo(); } 50 } 51 52 class B implements Runnable { 53 public void run() { } 54 } 55 56 public class TestAddDeleteMethods { 57 static private boolean allowAddDeleteMethods = false; 58 59 static private A a; 60 static private B b; 61 62 // This redefinition is expected to always succeed. 63 public static String newA = 64 "class A implements Runnable {" + 65 "private void foo() { System.out.println(\" NEW foo called\"); }" + 66 "public void publicFoo() { System.out.println(\" NEW publicFoo called\"); }" + 67 "private final void finalFoo() { System.out.println(\" NEW finalFoo called\"); }" + 68 "private static void staticFoo() { System.out.println(\" NEW staticFoo called\"); }" + 69 "public void run() { foo(); publicFoo(); finalFoo(); staticFoo(); }" + 70 "}"; 71 72 // This redefinition is expected to always fail. 73 public static String ADeleteFoo = 74 "class A implements Runnable {" + 75 "public void publicFoo() { System.out.println(\" NEW publicFoo called\"); }" + 76 "private final void finalFoo() { System.out.println(\" NEW finalFoo called\"); }" + 77 "private static void staticFoo() { System.out.println(\" NEW staticFoo called\"); }" + 78 "public void run() { publicFoo(); finalFoo(); staticFoo(); }" + 79 "}"; 80 81 // This redefinition is expected to always fail. 82 public static String ADeletePublicFoo = 83 "class A implements Runnable {" + 84 "private void foo() { System.out.println(\" NEW foo called\"); }" + 85 "private final void finalFoo() { System.out.println(\" NEW finalFoo called\"); }" + 86 "private static void staticFoo() { System.out.println(\" NEW staticFoo called\"); }" + 87 "public void run() { foo(); finalFoo(); staticFoo(); }" + 88 "}"; 89 90 // This redefinition is expected to succeed with option -XX:+AllowRedefinitionToAddDeleteMethods. 91 public static String ADeleteFinalFoo = 92 "class A implements Runnable {" + 93 "private void foo() { System.out.println(\" NEW foo called\"); }" + 94 "public void publicFoo() { System.out.println(\" NEW publicFoo called\"); }" + 95 "private static void staticFoo() { System.out.println(\" NEW staticFoo called\"); }" + 96 "public void run() { foo(); publicFoo(); staticFoo(); }" + 97 "}"; 98 99 // This redefinition is expected to succeed with option -XX:+AllowRedefinitionToAddDeleteMethods. 100 // With compatibility option redefinition ADeleteFinalFoo already deleted finalFoo method. 101 // So, this redefinition will add it back which is expected to work. 102 public static String ADeleteStaticFoo = 103 "class A implements Runnable {" + 104 "private void foo() { System.out.println(\" NEW foo called\"); }" + 105 "public void publicFoo() { System.out.println(\" NEW publicFoo called\"); }" + 106 "private final void finalFoo() { System.out.println(\" NEW finalFoo called\"); }" + 107 "public void run() { foo(); publicFoo(); finalFoo(); }" + 108 "}"; 109 110 // This redefinition is expected to always fail. 111 public static String BAddBar = 112 "class B implements Runnable {" + 113 "private void bar() { System.out.println(\" bar called\"); }" + 114 "public void run() { bar(); }" + 115 "}"; 116 117 // This redefinition is expected to always fail. 118 public static String BAddPublicBar = 119 "class B implements Runnable {" + 120 "public void publicBar() { System.out.println(\" publicBar called\"); }" + 121 "public void run() { publicBar(); }" + 122 "}"; 123 124 // This redefinition is expected to succeed with option -XX:+AllowRedefinitionToAddDeleteMethods. 125 public static String BAddFinalBar = 126 "class B implements Runnable {" + 127 "private final void finalBar() { System.out.println(\" finalBar called\"); }" + 128 "public void run() { finalBar(); }" + 129 "}"; 130 131 // This redefinition is expected to succeed with option -XX:+AllowRedefinitionToAddDeleteMethods. 132 // With compatibility option redefinition BAddFinalBar added finalBar method. 133 // So, this redefinition will deleate it back which is expected to work. 134 public static String BAddStaticBar = 135 "class B implements Runnable {" + 136 "private static void staticBar() { System.out.println(\" staticBar called\"); }" + 137 "public void run() { staticBar(); }" + 138 "}"; 139 140 static private final String ExpMsgPrefix = "attempted to "; 141 static private final String ExpMsgPostfix = " a method"; 142 143 static private void log(String msg) { System.out.println(msg); } 144 145 public static void test(Runnable obj, String newBytes, String expSuffix, String methodName, 146 boolean expectedRedefToPass) throws Exception { 147 String expectedMessage = ExpMsgPrefix + expSuffix + ExpMsgPostfix; 148 Class klass = obj.getClass(); 149 String className = klass.getName(); 150 String expResult = expectedRedefToPass ? "PASS" : "FAIL"; 151 152 log(""); 153 log("## Test " + expSuffix + " method \'" + methodName + "\' in class " + className + 154 "; redefinition expected to " + expResult); 155 156 try { 157 RedefineClassHelper.redefineClass(klass, newBytes); 158 159 if (expectedRedefToPass) { 160 log(" Did not get UOE at redefinition as expected"); 161 } else { 162 throw new RuntimeException("Failed, expected UOE"); 163 } 164 obj.run(); 165 log(""); 166 } catch (UnsupportedOperationException uoe) { 167 String message = uoe.getMessage(); 168 169 if (expectedRedefToPass) { 170 throw new RuntimeException("Failed, unexpected UOE: " + message); 171 } else { 172 log(" Got expected UOE: " + message); 173 if (!message.endsWith(expectedMessage)) { 174 throw new RuntimeException("Expected UOE error message to end with: " + expectedMessage); 175 } 176 } 177 } 178 } 179 180 static { 181 a = new A(); 182 b = new B(); 183 } 184 185 public static void main(String[] args) throws Exception { 186 if (args.length > 0 && args[0].equals("AllowAddDelete=yes")) { 187 allowAddDeleteMethods = true; 188 } 189 190 log("## Test original class A"); 191 a.run(); 192 log(""); 193 194 log("## Test with modified method bodies in class A; redefinition expected to pass: true"); 195 RedefineClassHelper.redefineClass(A.class, newA); 196 a.run(); 197 198 test(a, ADeleteFoo, "delete", "foo", false); 199 test(a, ADeletePublicFoo, "delete", "publicFoo", false); 200 test(a, ADeleteFinalFoo, "delete", "finalFoo", allowAddDeleteMethods); 201 test(a, ADeleteStaticFoo, "delete", "staticFoo", allowAddDeleteMethods); 202 203 test(b, BAddBar, "add", "bar", false); 204 test(b, BAddPublicBar, "add", "publicBar", false); 205 test(b, BAddFinalBar, "add", "finalBar", allowAddDeleteMethods); 206 test(b, BAddStaticBar, "add", "staticBar", allowAddDeleteMethods); 207 } 208 }