/* * Copyright (c) 2020 SAP SE. 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. */ /** * @test * @bug 8249293 * * @summary Test if stack walk to get local variable in the JVMTI implementation is safe if the * target thread is not suspended. * * @comment The main/target thread uses recursion to build a large stack, then * calls a native method to notify the JVMTI agent thread to get a * local variable deep in the stack. This prolongs the stack walk. The * target thread's stack is walkable while in native. After sending the * notification it waits a while to give the agent time to reach the * stack walk, then it returns from native. This is when its stack * becomes not walkable again. * * @library /test/lib * @compile GetLocalWithoutSuspendTest.java * @run main/othervm/native * -agentlib:GetLocalWithoutSuspendTest * -Xbatch * GetLocalWithoutSuspendTest */ public class GetLocalWithoutSuspendTest { public static final int M = 1 << 20; public static final int TEST_ITERATIONS = 200; /** * Native method to notify the agent thread to call GetLocalObject() on this thread. * * @param depth Depth of target frame for GetLocalObject() call. Should be * large value to prolong the unsafe stack walk. * @param waitTime Time to wait after notify with * walkable stack before returning an becoming unsafe again. * @return Dummy value. */ public static native void notifyAgentToGetLocal(int depth, int waitTime); /** * Notify agent thread that we are shutting down and wait for it to terminate. */ public static native void shutDown(); /** * Provide agent thread with reference to target thread. * @param target The target thread */ public static native void setTargetThread(Thread target); public static void main(String[] args) throws Exception { new GetLocalWithoutSuspendTest().runTest(); } /** * Wait cycles in native, i.e. with walkable stack, after notifying agent * thread to do GetLocalObject() call. */ public int waitCycles = 1; public void runTest() throws Exception { log("Set target thread for get local variable calls by agent."); setTargetThread(Thread.currentThread()); log("Test how many frames fit on the stack by performing recursive calls until"); log("StackOverflowError is thrown"); int targetDepth = recursiveMethod(0, M); log("Testing with target depth: " + targetDepth); log("Begin Test."); long start = System.currentTimeMillis(); for (int iterations = 0; iterations < TEST_ITERATIONS; iterations++) { long now = System.currentTimeMillis(); log((now - start) + " ms Iteration : " + iterations + " waitTime : " + waitCycles); int newTargetDepth = recursiveMethod(0, targetDepth); if (newTargetDepth < targetDepth) { // A StackOverflowError can occur due to (re-)compilation. We // don't reach the native method notifyAgentToGetLocal() then // which is a prerequisite to trigger the problematic race // condition. So we reduce the targetDepth to avoid stack // overflow. log("StackOverflowError during test."); log("Old target depth: " + targetDepth); log("Retry with new target depth: " + newTargetDepth); targetDepth = newTargetDepth; } iterations++; // Double wait time, but limit to roughly 10^6 cycles. waitCycles = (waitCycles << 1) & (M - 1); waitCycles = waitCycles == 0 ? 1 : waitCycles; } // Notify agent thread that we are shutting down and wait for it to terminate. shutDown(); log("Successfully finished test"); } /** * Perform recursive calls until the target stack depth is reached or the stack overflows. * Call {@link #notifyAgentToGetLocal(int, int)} if the target depth is reached. * * @param depth Current recursion depth * @param targetStackDepth Target recursion depth * @return Depth at which the recursion was ended */ public int recursiveMethod(int depth, int targetStackDepth) { int maxDepth = depth; try { if (depth == targetStackDepth) { notifyAgentToGetLocal(depth - 100, waitCycles); } else { maxDepth = recursiveMethod(depth + 1, targetStackDepth); } } catch (StackOverflowError e) { // Don't print message here, because this would likely trigger a new StackOverflowError } return maxDepth; } public static void log(String m) { System.out.println("### Java-Test: " + m); } }