File: RedefineMethodInBacktraceApp.java

package info (click to toggle)
openjdk-24 24.0.2%2B12-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 831,900 kB
  • sloc: java: 5,677,020; cpp: 1,323,154; xml: 1,320,524; ansic: 486,889; asm: 405,131; objc: 21,025; sh: 15,221; javascript: 11,049; python: 8,222; makefile: 2,504; perl: 357; awk: 351; sed: 172; pascal: 103; exp: 54; jsp: 24; csh: 3
file content (200 lines) | stat: -rw-r--r-- 7,876 bytes parent folder | download | duplicates (16)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/*
 * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import com.sun.management.DiagnosticCommandMBean;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.instrument.ClassDefinition;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.CountDownLatch;
import javax.management.JMX;
import javax.management.ObjectName;

/**
 * When an exception is thrown, the JVM collects just enough information
 * about the stack trace to be able to create a full fledged stack trace
 * (StackTraceElement[]). The backtrace contains this information and the
 * JVM  must make sure that the data in the backtrace is still usable after
 * a class redefinition.
 *
 * After the PermGen removal there was a bug when the last reference to a Method
 * was in the backtrace. The class of the method was kept alive, because of the
 * mirror stored in the backtrace, but the old versions of the redefined method
 * could be freed, since class redefinition didn't know about the backtraces.
 */
public class RedefineMethodInBacktraceApp {
    static boolean failed = false;

    public static void main(String args[]) throws Exception {
        System.out.println("Hello from RedefineMethodInBacktraceApp!");
        new RedefineMethodInBacktraceApp().doTest();

        if (failed) {
            throw new Exception("ERROR: RedefineMethodInBacktraceApp failed.");
        }
    }

    public static CountDownLatch stop = new CountDownLatch(1);
    public static CountDownLatch called = new CountDownLatch(1);

    private void doTest() throws Exception {
        doMethodInBacktraceTest();
        doMethodInBacktraceTestB();
    }

    private void doMethodInBacktraceTest() throws Exception {
        Throwable t1 = getThrowableFromMethodToRedefine();
        Throwable t2 = getThrowableFromMethodToDelete();

        doRedefine(RedefineMethodInBacktraceTarget.class);

        doClassUnloading();

        System.out.println("checking backtrace for throwable from methodToRedefine");
        touchRedefinedMethodInBacktrace(t1);

        System.out.println("checking backtrace for throwable from methodToDelete");
        touchRedefinedMethodInBacktrace(t2);
    }

    private void doMethodInBacktraceTestB() throws Exception {
        // Start a thread which blocks in method
        Thread t = new Thread(RedefineMethodInBacktraceTargetB::methodToRedefine);
        t.setDaemon(true);
        t.start();

        // Wait here until the new thread is in the method we want to redefine
        called.await();

        // Now redefine the class while the method is still on the stack of the new thread
        doRedefine(RedefineMethodInBacktraceTargetB.class);

        // Do thread dumps in two different ways (to exercise different code paths)
        // while the old class is still on the stack

        ThreadInfo[] tis = ManagementFactory.getThreadMXBean().dumpAllThreads(false, false);
        for(ThreadInfo ti : tis) {
            System.out.println(ti);
        }

        String[] threadPrintArgs = {};
        Object[] dcmdArgs = {threadPrintArgs};
        String[] signature = {String[].class.getName()};
        System.out.println(ManagementFactory.getPlatformMBeanServer().invoke(
                ObjectName.getInstance("com.sun.management:type=DiagnosticCommand"),
                "threadPrint",
                dcmdArgs,
                signature));

        // release the thread
        stop.countDown();
    }

    private static Throwable getThrowableFromMethodToRedefine() throws Exception {
        Class<RedefineMethodInBacktraceTarget> c =
                RedefineMethodInBacktraceTarget.class;
        Method method = c.getMethod("methodToRedefine");

        Throwable thrownFromMethodToRedefine = null;
        try {
            method.invoke(null);
        } catch (InvocationTargetException e) {
            thrownFromMethodToRedefine = e.getCause();
            if (!(thrownFromMethodToRedefine instanceof RuntimeException)) {
                throw e;
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("\nTest failed: unexpected exception: " + e.toString());
            failed = true;
        }
        method = null;
        c = null;

        return thrownFromMethodToRedefine;
    }

    private static Throwable getThrowableFromMethodToDelete() throws Exception {
        Class<RedefineMethodInBacktraceTarget> c =
                RedefineMethodInBacktraceTarget.class;
        Method method = c.getMethod("callMethodToDelete");

        Throwable thrownFromMethodToDelete = null;
        try {
            method.invoke(null);
        } catch (InvocationTargetException e) {
            thrownFromMethodToDelete = e.getCause();
            if (!(thrownFromMethodToDelete instanceof RuntimeException)) {
                throw e;
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("\nTest failed: unexpected exception: " + e.toString());
            failed = true;
        }
        return thrownFromMethodToDelete;
    }


    private static void doClassUnloading() {
        // This will clean out old, unused redefined methods.
        System.gc();
    }

    private static void touchRedefinedMethodInBacktrace(Throwable throwable) {
        throwable.printStackTrace();
        // Make sure that we can convert the backtrace, which is referring to
        // the redefined method, to a  StrackTraceElement[] without crashing.
        StackTraceElement[] stackTrace = throwable.getStackTrace();
        for (int i = 0; i < stackTrace.length; i++) {
          StackTraceElement frame = stackTrace[i];
          if (frame.getClassName() == null) {
              System.out.println("\nTest failed: trace[" + i + "].getClassName() returned null");
              failed = true;
          }
          if (frame.getMethodName() == null) {
              System.out.println("\nTest failed: trace[" + i + "].getMethodName() returned null");
              failed = true;
          }
        }
    }

    private static void doRedefine(Class<?> clazz) throws Exception {
        // Load the second version of this class.
        File f = new File(clazz.getName() + ".class");
        System.out.println("Reading test class from " + f.getAbsolutePath());
        InputStream redefineStream = new FileInputStream(f);

        byte[] redefineBuffer = NamedBuffer.loadBufferFromStream(redefineStream);

        ClassDefinition redefineParamBlock = new ClassDefinition(
                clazz, redefineBuffer);

        RedefineMethodInBacktraceAgent.getInstrumentation().redefineClasses(
                new ClassDefinition[] {redefineParamBlock});
    }
}