1 /*
2  * Copyright (c) 2004, 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 package nsk.jvmti.RedefineClasses;
25 
26 import java.io.*;
27 import java.util.*;
28 import nsk.share.*;
29 import nsk.share.jvmti.*;
30 
31 /**
32  * The test exercises that the JVMTI function RedefineClasses()
33  * enable to redefine a static inner (nested) class properly. Redefiniton
34  * is performed in asynchronous manner from a separate
35  * thread when the VM is provoked to switch into compiled mode.<br>
36  * The test works as follows. Two threads are started: java thread
37  * executing a nested class to be redefined, and native one executing
38  * an agent code. Then the nested class method <code>redefclass029HotMethod()</code>
39  * is provoked to be compiled (optimized), and thus the JVMTI event
40  * <code>CompiledMethodLoad</code> should be sent. After that, the
41  * agent redefines the nested class. Different kinds of outer fields
42  * are accessed from executing methods in both versions of the
43  * nested class. Upon the redefinition, the main test thread verifies
44  * via the outer fields values that the nested method <code>run()</code>
45  * having an active stack frame stays obsolete but the other nested
46  * methods have been redefined. It also verifies that the outer class
47  * is still can access the nested class fields after the redefinition.
48  */
49 public class redefclass029 extends DebugeeClass {
50     final static String REDEF_CLS_SIGNATURE =
51         "Lnsk/jvmti/RedefineClasses/redefclass029$RedefClass;";
52 
53     static int status = Consts.TEST_PASSED;
54 
55     static Log log = null;
56 
57     // path to the directory with redefining class files
58     static String clfBasePath = null;
59 
60     // dummy outer fields to be changed by the nested class
61     private static int prStOuterFl[] = {0,0,0};
62     static int packStOuterFl[] = {0,0,0};
63     public static int pubStOuterFl[] = {0,0,0};
64 
65     /* a dummy outer private field to be accessed or not in
66        the nested class and thus provoking compiler to add or not
67        a synthetic access method into the outer class. */
68     private static char prOuterFieldToBeAccessed = 'a';
69 
storeClassBytes(byte[] classBytes)70     native static void storeClassBytes(byte[] classBytes);
notifyNativeAgent()71     native static void notifyNativeAgent(); /** notify native agent that "hot" method is entered */
isRedefinitionOccurred()72     native static boolean isRedefinitionOccurred(); /** check whether class redefinition was already occurred */
73 
main(String args[])74     public static void main(String args[]) {
75         args = nsk.share.jvmti.JVMTITest.commonInit(args);
76 
77         // produce JCK-like exit status.
78         System.exit(run(args, System.out) + Consts.JCK_STATUS_BASE);
79     }
80 
run(String args[], PrintStream out)81     public static int run(String args[], PrintStream out) {
82         return new redefclass029().runIt(args, out);
83     }
84 
runIt(String args[], PrintStream out)85     private int runIt(String args[], PrintStream out) {
86         int iter;
87 
88         ArgumentHandler argHandler = new ArgumentHandler(args);
89         log = new Log(out, argHandler);
90 
91         try {
92             // number of iterations for a method to become 'hotspot' one
93             iter = Integer.parseInt(args[0]);
94 
95             // directory to search a redefining class
96             clfBasePath = args[1];
97         } catch(Exception e) {
98             throw new Failure("TEST BUG: Wrong test parameters, caught: "
99                 + e);
100         }
101 
102         storeClassBytes(loadFromClassFile(REDEF_CLS_SIGNATURE));
103 
104         // testing sync
105         log.display("waiting for the agent start ...\n");
106         status = checkStatus(status);
107 
108         log.display("starting an auxiliary thread ...\n");
109         RedefClass redefCls = new RedefClass(iter);
110         redefCls.setDaemon(true);
111         synchronized(redefCls) {
112             redefCls.start();
113             log.display("waiting for auxiliary thread readiness...\n");
114             try {
115                 redefCls.wait(); // wait for the thread's readiness
116             } catch (InterruptedException e) {
117                 redefCls.interrupt();
118                 throw new Failure("TEST FAILURE: waiting for auxiliary thread start, caught: "
119                     + e);
120             }
121         }
122 
123         // testing sync
124         log.display("auxiliary thread started\n"
125             + "waiting for the agent finish ...\n");
126         status = checkStatus(status);
127 
128         boolean isRedefinitionStarted = waitForRedefinitionStarted();
129         boolean isRedefinitionCompleted = false;
130         if (isRedefinitionStarted) {
131             isRedefinitionCompleted = waitForRedefinitionCompleted(redefCls);
132         }
133 
134         log.display("waiting for auxiliary thread ...\n");
135         redefCls.stopMe = true;
136         try {
137             redefCls.join();
138         } catch (InterruptedException e) {
139             redefCls.interrupt();
140             throw new Failure("TEST FAILURE: waiting for auxiliary thread death, caught: "
141                 + e);
142         }
143 
144         // CR 6604375: check whether class redefinition occurred
145         if (isRedefinitionCompleted) {
146             // verify results
147             checkOuterFields(0, 1);
148             checkOuterFields(1, 2);
149             checkOuterFields(2, 2);
150             checkInnerFields(redefCls, 1);
151         }
152 
153         return status;
154     }
155 
waitForRedefinitionStarted()156     private boolean waitForRedefinitionStarted() {
157         final int SLEEP_MS = 20;
158         int iterationsLeft = 2000 / SLEEP_MS;
159         while (iterationsLeft >= 0) {
160             if (isRedefinitionOccurred()) {
161                 log.display("Redefinition started.");
162                 return true;
163             }
164             --iterationsLeft;
165             safeSleep(SLEEP_MS);
166         }
167         log.complain("Redefinition not started. May need more time for -Xcomp.");
168         status = Consts.TEST_FAILED;
169         return false;
170     }
171 
waitForRedefinitionCompleted(RedefClass redefCls)172     private boolean waitForRedefinitionCompleted(RedefClass redefCls) {
173         final int SLEEP_MS = 20;
174         int iterationsLeft = 10000 / SLEEP_MS;
175         while (iterationsLeft >= 0) {
176             // Check if new code has changed fields.
177             if (prStOuterFl[1] == 2 && prStOuterFl[2] == 2 && redefCls.prInnerFl == 1) {
178                 log.display("Redefinition completed.");
179                 return true;
180             }
181             --iterationsLeft;
182             safeSleep(SLEEP_MS);
183         }
184         log.complain("Redefinition not completed. May need more time for -Xcomp.");
185         status = Consts.TEST_FAILED;
186         return false;
187     }
188 
189 
checkOuterFields(int index, int expValue)190     private void checkOuterFields(int index, int expValue) {
191         if (prStOuterFl[index] != expValue
192                 || packStOuterFl[index] != expValue
193                 || pubStOuterFl[index] != expValue) {
194             status = Consts.TEST_FAILED;
195             log.complain("TEST FAILED: unexpected values of outer fields:"
196                 + "\n\tprStOuterFl["+ index +"]: got: " + prStOuterFl[index]
197                 + ", expected: " + expValue
198                 + "\n\tpackStOuterFl["+ index +"]: got: " + packStOuterFl[index]
199                 + ", expected: " + expValue
200                 + "\n\tpubStOuterFl["+ index +"]: got: " + pubStOuterFl[index]
201                 + ", expected: " + expValue);
202         }
203     }
204 
checkInnerFields(RedefClass redefCls, int expValue)205     private void checkInnerFields(RedefClass redefCls, int expValue) {
206         if (redefCls.prInnerFl != expValue
207                 || redefCls.packInnerFl != expValue
208                 || redefCls.pubInnerFl != expValue) {
209             status = Consts.TEST_FAILED;
210             log.complain("TEST FAILED: unexpected values of inner fields:"
211                 + "\n\tprInnerFl: got: " + redefCls.prInnerFl
212                 + ", expected: " + expValue
213                 + "\n\tpackInnerFl: got: " + redefCls.packInnerFl
214                 + ", expected: " + expValue
215                 + "\n\tpubInnerFl: got: " + redefCls.pubInnerFl
216                 + ", expected: " + expValue);
217         }
218     }
219 
220     /**
221      * Load bytes of a redefining class.
222      */
loadFromClassFile(String signature)223     private static byte[] loadFromClassFile(String signature) {
224         String testPath = clfBasePath + File.separator
225             + "newclass" + File.separator
226             + signature.substring(1, signature.length()-1).replace('/', File.separatorChar)
227             + ".class";
228         File classFile = null;
229 
230         log.display("looking for class file at\n\t"
231             + testPath + " ...\n");
232 
233         try {
234             classFile = new File(testPath);
235         } catch (NullPointerException e) {
236             throw new Failure("FAILURE: failed to open class file, caught: "
237                 + e);
238         }
239 
240         log.display("loading " + classFile.length()
241             + " bytes from class file "+ testPath + " ...\n");
242         byte[] buf = new byte[(int) classFile.length()];
243         try {
244             InputStream in = new FileInputStream(classFile);
245             in.read(buf);
246             in.close();
247         } catch (Exception e) {
248             throw new Failure("FAILURE: failed to load bytes from class file, caught: "
249                 + e);
250         }
251 
252         log.display(classFile.length()
253             + " bytes of a redefining class loaded\n");
254 
255         return buf;
256     }
257 
258     /**
259      * Static inner (nested) class to be redefined in the agent.
260      */
261     static class RedefClass extends Thread {
262         boolean stopMe = false;
263         int iter;
264 
265         // dummy inner fields to be accessed by the outer class
266         private int prInnerFl = 1;
267         int packInnerFl = 1;
268         public int pubInnerFl = 1;
269 
RedefClass(int iter)270         RedefClass(int iter) {
271             super("RedefClass");
272             this.iter = iter;
273         }
274 
275         /**
276          * This method will have an active stack frame during
277          * nested class redinition, so this version should continue
278          * execution and thus outer fields should have values equal 1.
279          */
run()280         public void run() {
281             log.display(this.getName() + ": started");
282             synchronized(this) {
283                 this.notify(); // notify the main thread
284 
285                 prStOuterFl[0] = 1;
286                 packStOuterFl[0] = 1;
287                 pubStOuterFl[0] = 1;
288 
289                 while(!stopMe) {
290                     warmUpMethod();
291 
292                     // get the main thread chance to obtain CPU
293                     try {
294                         this.wait(100);
295                     } catch(Exception e) {}
296                 }
297                 log.display(this.getName() + ": exiting");
298             }
299         }
300 
warmUpMethod()301         void warmUpMethod() {
302             prStOuterFl[1] = 1;
303             packStOuterFl[1] = 1;
304             pubStOuterFl[1] = 1;
305 
306             for (int i=0; i<iter; i++)
307                 redefclass029HotMethod(i);
308 
309             redefclass029HotMethod(10);
310         }
311 
312         /**
313          * Hotspot method to be compiled.
314          */
redefclass029HotMethod(int i)315         void redefclass029HotMethod(int i) {
316             prStOuterFl[2] = 1;
317             packStOuterFl[2] = 1;
318             pubStOuterFl[2] = 1;
319 
320             int j=0;
321 
322             j +=i;
323             j--;
324             if (j >10)
325                 j = 0;
326 
327             notifyNativeAgent();
328         }
329 
330         /**
331          * A dummy method accessing a private field of the outer class.
332          * It provokes compiler to add a synthetic access method
333          * into the outer class.
334          */
methAccessingOuterPrivateField()335          void methAccessingOuterPrivateField() {
336             prOuterFieldToBeAccessed = 'b';
337          }
338     }
339 
340 }
341