# HG changeset patch # User bchristi # Date 1462473841 25200 # Node ID a3fdbd11148f1e120def48c4869c37770faf2691 # Parent 5ed176fa7d97cdfe95d34fd62b107bd97a946edd 8147039: Incorrect locals and operands in compiled frames Summary: Implement stack walking using javaVFrame instead of vframeStream Reviewed-by: mchung, vlivanov diff -r 5ed176fa7d97 -r a3fdbd11148f jdk/test/java/lang/StackWalker/CountLocalSlots.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/lang/StackWalker/CountLocalSlots.java Thu May 05 11:44:01 2016 -0700 @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016 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. + */ + +/* + * @test + * @bug 8147039 + * @summary Confirm locals[] always has expected length, even for "dead" locals + * @compile LocalsAndOperands.java + * @run testng/othervm -Xcomp CountLocalSlots + */ + +import org.testng.annotations.Test; +import java.lang.StackWalker.StackFrame; + +public class CountLocalSlots { + final static boolean debug = true; + + @Test(dataProvider = "provider", dataProviderClass = LocalsAndOperands.class) + public void countLocalSlots(StackFrame... frames) { + for (StackFrame frame : frames) { + if (debug) { + System.out.println("Running countLocalSlots"); + LocalsAndOperands.dumpStackWithLocals(frames); + } + // Confirm expected number of locals + String methodName = frame.getMethodName(); + Integer expectedObj = (Integer) LocalsAndOperands.Tester.NUM_LOCALS.get(methodName); + if (expectedObj == null) { + if (!debug) { LocalsAndOperands.dumpStackWithLocals(frames); } + throw new RuntimeException("No NUM_LOCALS entry for " + + methodName + "(). Update test?"); + } + Object[] locals = (Object[]) LocalsAndOperands.invokeGetLocals(frame); + if (locals.length != expectedObj) { + if (!debug) { LocalsAndOperands.dumpStackWithLocals(frames); } + throw new RuntimeException(methodName + "(): number of locals (" + + locals.length + ") did not match expected (" + expectedObj + ")"); + } + } + } +} diff -r 5ed176fa7d97 -r a3fdbd11148f jdk/test/java/lang/StackWalker/LocalsAndOperands.java --- a/jdk/test/java/lang/StackWalker/LocalsAndOperands.java Thu May 05 01:52:03 2016 -0700 +++ b/jdk/test/java/lang/StackWalker/LocalsAndOperands.java Thu May 05 11:44:01 2016 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2016 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 @@ -23,17 +23,20 @@ /* * @test - * @bug 8020968 - * @summary Sanity test for locals and operands - * @run main LocalsAndOperands + * @bug 8020968 8147039 + * @summary Tests for locals and operands + * @run testng LocalsAndOperands */ +import org.testng.annotations.*; import java.lang.StackWalker.StackFrame; import java.lang.reflect.*; -import java.util.List; -import java.util.stream.Collectors; +import java.util.*; +import java.util.stream.*; public class LocalsAndOperands { + static final boolean debug = true; + static Class liveStackFrameClass; static Class primitiveValueClass; static StackWalker extendedWalker; @@ -41,92 +44,319 @@ static Method getOperands; static Method getMonitors; static Method primitiveType; - public static void main(String... args) throws Exception { - liveStackFrameClass = Class.forName("java.lang.LiveStackFrame"); - primitiveValueClass = Class.forName("java.lang.LiveStackFrame$PrimitiveValue"); + + static { + try { + liveStackFrameClass = Class.forName("java.lang.LiveStackFrame"); + primitiveValueClass = Class.forName("java.lang.LiveStackFrame$PrimitiveValue"); + + getLocals = liveStackFrameClass.getDeclaredMethod("getLocals"); + getLocals.setAccessible(true); + + getOperands = liveStackFrameClass.getDeclaredMethod("getStack"); + getOperands.setAccessible(true); + + getMonitors = liveStackFrameClass.getDeclaredMethod("getMonitors"); + getMonitors.setAccessible(true); - getLocals = liveStackFrameClass.getDeclaredMethod("getLocals"); - getLocals.setAccessible(true); + primitiveType = primitiveValueClass.getDeclaredMethod("type"); + primitiveType.setAccessible(true); - getOperands = liveStackFrameClass.getDeclaredMethod("getStack"); - getOperands.setAccessible(true); + Method method = liveStackFrameClass.getMethod("getStackWalker"); + method.setAccessible(true); + extendedWalker = (StackWalker) method.invoke(null); + } catch (Throwable t) { throw new RuntimeException(t); } + } + + /** Helper method to return a StackFrame's locals */ + static Object[] invokeGetLocals(StackFrame arg) { + try { + return (Object[]) getLocals.invoke(arg); + } catch (Exception e) { throw new RuntimeException(e); } + } - getMonitors = liveStackFrameClass.getDeclaredMethod("getMonitors"); - getMonitors.setAccessible(true); + /***************** + * DataProviders * + *****************/ - primitiveType = primitiveValueClass.getDeclaredMethod("type"); - primitiveType.setAccessible(true); + /** Calls testLocals() and provides LiveStackFrames for testLocals* methods */ + @DataProvider + public static StackFrame[][] provider() { + return new StackFrame[][] { + new Tester().testLocals() + }; + } - Method method = liveStackFrameClass.getMethod("getStackWalker"); - method.setAccessible(true); - extendedWalker = (StackWalker) method.invoke(null); - new LocalsAndOperands(extendedWalker, true).test(); + /** + * Calls testLocalsKeepAlive() and provides LiveStackFrames for testLocals* methods. + * Local variables in testLocalsKeepAlive() are ensured to not become dead. + */ + @DataProvider + public static StackFrame[][] keepAliveProvider() { + return new StackFrame[][] { + new Tester().testLocalsKeepAlive() + }; + } - // no access to local and operands. - new LocalsAndOperands(StackWalker.getInstance(), false).test(); + /** + * Provides StackFrames from a StackWalker without the LOCALS_AND_OPERANDS + * option. + */ + @DataProvider + public static StackFrame[][] noLocalsProvider() { + // Use default StackWalker + return new StackFrame[][] { + new Tester(StackWalker.getInstance(), true).testLocals() + }; } - private final StackWalker walker; - private final boolean extended; - LocalsAndOperands(StackWalker walker, boolean extended) { - this.walker = walker; - this.extended = extended; + /** + * Calls testLocals() and provides LiveStackFrames for *all* called methods, + * including test infrastructure (jtreg, testng, etc) + * + */ + @DataProvider + public static StackFrame[][] unfilteredProvider() { + return new StackFrame[][] { + new Tester(extendedWalker, false).testLocals() + }; + } + + /**************** + * Test methods * + ****************/ + + /** + * Check for expected local values and types in the LiveStackFrame + */ + @Test(dataProvider = "keepAliveProvider") + public static void checkLocalValues(StackFrame... frames) { + if (debug) { + System.out.println("Running checkLocalValues"); + dumpStackWithLocals(frames); + } + Arrays.stream(frames).filter(f -> f.getMethodName() + .equals("testLocalsKeepAlive")) + .forEach( + f -> { + Object[] locals = invokeGetLocals(f); + for (int i = 0; i < locals.length; i++) { + // Value + String expected = Tester.LOCAL_VALUES[i]; + Object observed = locals[i]; + if (expected != null /* skip nulls in golden values */ && + !expected.equals(observed.toString())) { + System.err.println("Local value mismatch:"); + if (!debug) { dumpStackWithLocals(frames); } + throw new RuntimeException("local " + i + " value is " + + observed + ", expected " + expected); + } + + // Type + expected = Tester.LOCAL_TYPES[i]; + observed = type(locals[i]); + if (expected != null /* skip nulls in golden values */ && + !expected.equals(observed)) { + System.err.println("Local type mismatch:"); + if (!debug) { dumpStackWithLocals(frames); } + throw new RuntimeException("local " + i + " type is " + + observed + ", expected " + expected); + } + } + } + ); + } + + /** + * Basic sanity check for locals and operands + */ + @Test(dataProvider = "provider") + public static void sanityCheck(StackFrame... frames) { + if (debug) { + System.out.println("Running sanityCheck"); + } + try { + Stream stream = Arrays.stream(frames); + if (debug) { + stream.forEach(LocalsAndOperands::printLocals); + } else { + System.out.println(stream.count() + " frames"); + } + } catch (Throwable t) { + dumpStackWithLocals(frames); + throw t; + } } - synchronized void test() throws Exception { - int x = 10; - char c = 'z'; - String hi = "himom"; - long l = 1000000L; - double d = 3.1415926; - - List frames = walker.walk(s -> s.collect(Collectors.toList())); - if (extended) { - for (StackWalker.StackFrame f : frames) { - System.out.println("frame: " + f); - Object[] locals = (Object[]) getLocals.invoke(f); - for (int i = 0; i < locals.length; i++) { - System.out.format(" local %d: %s type %s\n", i, locals[i], type(locals[i])); + /** + * Sanity check for locals and operands, including testng/jtreg frames + */ + @Test(dataProvider = "unfilteredProvider") + public static void unfilteredSanityCheck(StackFrame... frames) { + if (debug) { + System.out.println("Running unfilteredSanityCheck"); + } + try { + Stream stream = Arrays.stream(frames); + if (debug) { + stream.forEach(f -> { System.out.println(f + ": " + + invokeGetLocals(f).length + " locals"); } ); + } else { + System.out.println(stream.count() + " frames"); + } + } catch (Throwable t) { + dumpStackWithLocals(frames); + throw t; + } + } - // check for non-null locals in LocalsAndOperands.test() - if (f.getClassName().equals("LocalsAndOperands") && - f.getMethodName().equals("test")) { - if (locals[i] == null) { - throw new RuntimeException("kept-alive locals should not be null"); - } - } - } + /** + * Test that LiveStackFrames are not provided with the default StackWalker + * options. + */ + @Test(dataProvider = "noLocalsProvider") + public static void withoutLocalsAndOperands(StackFrame... frames) { + for (StackFrame frame : frames) { + if (liveStackFrameClass.isInstance(frame)) { + throw new RuntimeException("should not be LiveStackFrame"); + } + } + } + + static class Tester { + private StackWalker walker; + private boolean filter = true; // Filter out testng/jtreg/etc frames? + + Tester() { + this.walker = extendedWalker; + } - Object[] operands = (Object[]) getOperands.invoke(f); - for (int i = 0; i < operands.length; i++) { - System.out.format(" operand %d: %s type %s%n", i, operands[i], - type(operands[i])); - } + Tester(StackWalker walker, boolean filter) { + this.walker = walker; + this.filter = filter; + } - Object[] monitors = (Object[]) getMonitors.invoke(f); - for (int i = 0; i < monitors.length; i++) { - System.out.format(" monitor %d: %s%n", i, monitors[i]); - } - } - } else { - for (StackFrame f : frames) { - if (liveStackFrameClass.isInstance(f)) { - throw new RuntimeException("should not be LiveStackFrame"); - } + /** + * Perform stackwalk without keeping local variables alive and return an + * array of the collected StackFrames + */ + private synchronized StackFrame[] testLocals() { + // Unused local variables will become dead + int x = 10; + char c = 'z'; + String hi = "himom"; + long l = 1000000L; + double d = 3.1415926; + + if (filter) { + return walker.walk(s -> s.filter(f -> TEST_METHODS.contains(f + .getMethodName())).collect(Collectors.toList())) + .toArray(new StackFrame[0]); + } else { + return walker.walk(s -> s.collect(Collectors.toList())) + .toArray(new StackFrame[0]); } } - // Use local variables so they stay alive - System.out.println("Stayin' alive: "+x+" "+c+" "+hi+" "+l+" "+d); + + /** + * Perform stackwalk, keeping local variables alive, and return a list of + * the collected StackFrames + */ + private synchronized StackFrame[] testLocalsKeepAlive() { + int x = 10; + char c = 'z'; + String hi = "himom"; + long l = 1000000L; + double d = 3.1415926; + + List frames; + if (filter) { + frames = walker.walk(s -> s.filter(f -> TEST_METHODS.contains(f + .getMethodName())).collect(Collectors.toList())); + } else { + frames = walker.walk(s -> s.collect(Collectors.toList())); + } + + // Use local variables so they stay alive + System.out.println("Stayin' alive: "+x+" "+c+" "+hi+" "+l+" "+d); + return frames.toArray(new StackFrame[0]); // FIXME: convert to Array here + } + + // Expected values for locals in testLocals() & testLocalsKeepAlive() + // TODO: use real values instead of Strings, rebuild doubles & floats, etc + private final static String[] LOCAL_VALUES = new String[] { + null, // skip, LocalsAndOperands$Tester@XXX identity is different each run + "10", + "122", + "himom", + "0", + null, // skip, fix in 8156073 + null, // skip, fix in 8156073 + null, // skip, fix in 8156073 + "0" + }; + + // Expected types for locals in testLocals() & testLocalsKeepAlive() + // TODO: use real types + private final static String[] LOCAL_TYPES = new String[] { + null, // skip + "I", + "I", + "java.lang.String", + "I", + "I", + "I", + "I", + "I" + }; + + final static Map NUM_LOCALS = Map.of("testLocals", 8, + "testLocalsKeepAlive", + LOCAL_VALUES.length); + private final static Collection TEST_METHODS = NUM_LOCALS.keySet(); } - String type(Object o) throws Exception { - if (o == null) { - return "null"; - } else if (primitiveValueClass.isInstance(o)) { - char c = (char)primitiveType.invoke(o); - return String.valueOf(c); - } else { - return o.getClass().getName(); - } + /** + * Print stack trace with locals + */ + public static void dumpStackWithLocals(StackFrame...frames) { + Arrays.stream(frames).forEach(LocalsAndOperands::printLocals); + } + + /** + * Print the StackFrame and an indexed list of its locals + */ + public static void printLocals(StackWalker.StackFrame frame) { + try { + System.out.println(frame); + Object[] locals = (Object[]) getLocals.invoke(frame); + for (int i = 0; i < locals.length; i++) { + System.out.format(" local %d: %s type %s\n", i, locals[i], type(locals[i])); + } + + Object[] operands = (Object[]) getOperands.invoke(frame); + for (int i = 0; i < operands.length; i++) { + System.out.format(" operand %d: %s type %s%n", i, operands[i], + type(operands[i])); + } + + Object[] monitors = (Object[]) getMonitors.invoke(frame); + for (int i = 0; i < monitors.length; i++) { + System.out.format(" monitor %d: %s%n", i, monitors[i]); + } + } catch (Exception e) { throw new RuntimeException(e); } + } + + private static String type(Object o) { + try { + if (o == null) { + return "null"; + } else if (primitiveValueClass.isInstance(o)) { + char c = (char)primitiveType.invoke(o); + return String.valueOf(c); + } else { + return o.getClass().getName(); + } + } catch(Exception e) { throw new RuntimeException(e); } } } diff -r 5ed176fa7d97 -r a3fdbd11148f jdk/test/java/lang/StackWalker/LocalsCrash.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/lang/StackWalker/LocalsCrash.java Thu May 05 11:44:01 2016 -0700 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016 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. + */ + +/* + * @test + * @bug 8147039 + * @summary Test for -Xcomp crash that happened before 8147039 fix + * @run testng/othervm -Xcomp LocalsCrash + */ + +import org.testng.annotations.*; +import java.lang.reflect.*; +import java.util.List; +import java.util.stream.Collectors; + +public class LocalsCrash { + static Class liveStackFrameClass; + static Method getStackWalker; + + static { + try { + liveStackFrameClass = Class.forName("java.lang.LiveStackFrame"); + getStackWalker = liveStackFrameClass.getMethod("getStackWalker"); + getStackWalker.setAccessible(true); + } catch (Throwable t) { throw new RuntimeException(t); } + } + + private StackWalker walker; + + LocalsCrash() { + try { + walker = (StackWalker) getStackWalker.invoke(null); + } catch (Exception e) { throw new RuntimeException(e); } + } + + @Test + public void test00() { doStackWalk(); } + + @Test + public void test01() { doStackWalk(); } + + private synchronized List doStackWalk() { + try { + // Unused local variables will become dead + int x = 10; + char c = 'z'; + String hi = "himom"; + long l = 1000000L; + double d = 3.1415926; + + return walker.walk(s -> s.collect(Collectors.toList())); + } catch (Exception e) { throw new RuntimeException(e); } + } +}