| /* |
| * Copyright (c) 2015, 2017, 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 8020968 8147039 8156073 |
| * @summary Tests for locals and operands |
| * @modules java.base/java.lang:open |
| * @run testng/othervm -Xint -DtestUnused=true LocalsAndOperands |
| * @run testng/othervm -Xcomp LocalsAndOperands |
| * @run testng/othervm -Xcomp -XX:-TieredCompilation LocalsAndOperands |
| */ |
| |
| import org.testng.annotations.*; |
| import static org.testng.Assert.*; |
| import java.lang.StackWalker.StackFrame; |
| import static java.lang.StackWalker.Option.*; |
| import java.lang.reflect.*; |
| import java.util.*; |
| import java.util.stream.*; |
| |
| public class LocalsAndOperands { |
| static final boolean debug = false; |
| static final boolean is32bit; |
| static final boolean testUnused; |
| |
| static Class<?> liveStackFrameClass; |
| static Class<?> primitiveSlotClass; |
| static Class<?> primitiveSlot32Class; |
| static Class<?> primitiveSlot64Class; |
| |
| static StackWalker extendedWalker; |
| static Method getLocals; |
| static Method getOperands; |
| static Method getMonitors; |
| static Method primitiveSize; |
| static Method primitiveLongValue; |
| static Method primitiveIntValue; |
| static Method getExtendedWalker; |
| |
| private static final long LOWER_LONG_VAL = 4L; // Lower bits |
| private static final long UPPER_LONG_VAL = 0x123400000000L; // Upper bits |
| private static final long NEG_LONG_VAL = Long.MIN_VALUE; |
| |
| private static final double LOWER_DOUBLE_VAL = Double.longBitsToDouble(0xABCDL); |
| private static final double UPPER_DOUBLE_VAL = Double.longBitsToDouble(0x432100000000L); |
| |
| static { |
| try { |
| liveStackFrameClass = Class.forName("java.lang.LiveStackFrame"); |
| primitiveSlotClass = Class.forName("java.lang.LiveStackFrame$PrimitiveSlot"); |
| primitiveSlot32Class = Class.forName("java.lang.LiveStackFrameInfo$PrimitiveSlot32"); |
| primitiveSlot64Class = Class.forName("java.lang.LiveStackFrameInfo$PrimitiveSlot64"); |
| |
| getLocals = liveStackFrameClass.getDeclaredMethod("getLocals"); |
| getLocals.setAccessible(true); |
| |
| getOperands = liveStackFrameClass.getDeclaredMethod("getStack"); |
| getOperands.setAccessible(true); |
| |
| getMonitors = liveStackFrameClass.getDeclaredMethod("getMonitors"); |
| getMonitors.setAccessible(true); |
| |
| primitiveSize = primitiveSlotClass.getDeclaredMethod("size"); |
| primitiveSize.setAccessible(true); |
| |
| primitiveLongValue = primitiveSlotClass.getDeclaredMethod("longValue"); |
| primitiveLongValue.setAccessible(true); |
| |
| primitiveIntValue = primitiveSlotClass.getDeclaredMethod("intValue"); |
| primitiveIntValue.setAccessible(true); |
| |
| getExtendedWalker = liveStackFrameClass.getMethod("getStackWalker", Set.class); |
| getExtendedWalker.setAccessible(true); |
| extendedWalker = (StackWalker) getExtendedWalker.invoke(null, |
| EnumSet.noneOf(StackWalker.Option.class)); |
| |
| String dataModel = System.getProperty("sun.arch.data.model"); |
| if ("32".equals(dataModel)) { |
| is32bit = true; |
| } else if ("64".equals(dataModel)) { |
| is32bit= false; |
| } else { |
| throw new RuntimeException("Weird data model:" + dataModel); |
| } |
| System.out.println("VM bits: " + dataModel); |
| |
| testUnused = System.getProperty("testUnused") != 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); } |
| } |
| |
| /***************** |
| * DataProviders * |
| *****************/ |
| |
| /** Calls KnownLocalsTester.testLocals* and provides LiveStackFrames */ |
| @DataProvider |
| public static StackFrame[][] knownLocalsProvider() { |
| List<StackFrame[]> list = new ArrayList<>(3); |
| list.add(new KnownLocalsTester().testLocalsKeepAlive()); |
| list.add(new KnownLocalsTester().testLocalsKeepAliveArgs(0xA, 'z', |
| "himom", 0x3FF00000000L + 0xFFFF, Math.PI)); |
| if (testUnused) { |
| list.add(new KnownLocalsTester().testLocalsUnused()); |
| } |
| return list.toArray(new StackFrame[1][1]); |
| } |
| |
| /**************** |
| * Test methods * |
| ****************/ |
| |
| /** |
| * Check for expected local values in the LiveStackFrame |
| */ |
| @Test(dataProvider = "knownLocalsProvider") |
| public static void checkLocalValues(StackFrame... frames) { |
| dumpFramesIfDebug(frames); |
| try { |
| Stream.of(frames) |
| .filter(f -> KnownLocalsTester.TEST_METHODS.contains(f.getMethodName())) |
| .forEach(LocalsAndOperands::checkFrameLocals); |
| } catch (Exception e) { dumpFramesIfNotDebug(frames); throw e; } |
| } |
| |
| /** |
| * Check the locals in the given StackFrame against the expected values. |
| */ |
| private static void checkFrameLocals(StackFrame f) { |
| Object[] expectedArray = KnownLocalsTester.LOCAL_VALUES; |
| Object[] locals = invokeGetLocals(f); |
| |
| for (int i = 0; i < locals.length; i++) { |
| Object expected = expectedArray[i]; |
| Object observed = locals[i]; |
| |
| if (expected == null) { /* skip nulls in golden values */ |
| continue; |
| } else if (expected instanceof KnownLocalsTester.TwoSlotValue) { |
| // confirm integrity of expected values |
| assertEquals(expectedArray[i+1], null, |
| "Malformed array of expected values - slot after TwoSlotValue should be null"); |
| assertLongIsInSlots(locals[i], locals[i+1], |
| ((KnownLocalsTester.TwoSlotValue)expected).value); |
| i++; // skip following slot |
| } else if (primitiveSlotClass.isInstance(observed)) { // single slot primitive |
| assertTrue(primitiveValueEquals(observed, expected), |
| "Local value mismatch: local " + i + " value is " + |
| observed + ", expected " + expected); |
| } else if (expected instanceof Class) { |
| assertTrue(((Class)expected).isInstance(observed), |
| "Local value mismatch: local " + i + " expected instancof " + |
| expected + " but got " + observed); |
| } else if (expected instanceof String) { |
| assertEquals(expected, observed, "Local value mismatch: local " + |
| i + " value is " + observed + ", expected " + expected); |
| } else { |
| throw new RuntimeException("Unrecognized expected local value " + |
| i + ": " + expected); |
| } |
| } |
| } |
| |
| /** |
| * Sanity check for locals and operands, including testng/jtreg frames |
| * using all StackWalker options. |
| */ |
| @Test |
| public synchronized void fullStackSanityCheck() throws Throwable { |
| if (debug) { |
| System.out.println("Running fullStackSanityCheck"); |
| } |
| StackWalker sw = (StackWalker) getExtendedWalker.invoke(null, |
| EnumSet.of(SHOW_HIDDEN_FRAMES, SHOW_REFLECT_FRAMES, |
| RETAIN_CLASS_REFERENCE)); |
| sw.forEach(f -> { |
| if (debug) { |
| printLocals(f); |
| } else { |
| try { |
| System.out.println(" " + f + ": " + |
| ((Object[]) getLocals.invoke(f)).length + " locals, " + |
| ((Object[]) getOperands.invoke(f)).length + " operands, " + |
| ((Object[]) getMonitors.invoke(f)).length + " monitors"); |
| } catch (IllegalAccessException|InvocationTargetException t) { |
| throw new RuntimeException(t); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Test that LiveStackFrames are not provided with the default StackWalker |
| * options. |
| */ |
| @Test |
| public static void noLocalsSanityCheck() { |
| StackWalker sw = StackWalker.getInstance(); |
| sw.forEach(f -> { |
| assertFalse(liveStackFrameClass.isInstance(f), |
| "should not be LiveStackFrame"); |
| }); |
| } |
| |
| /** |
| * Class stack-walking methods with a known set of methods and local variables. |
| */ |
| static class KnownLocalsTester { |
| private StackWalker walker; |
| |
| KnownLocalsTester() { |
| this.walker = extendedWalker; |
| } |
| |
| /** |
| * Perform stackwalk without keeping local variables alive and return an |
| * array of the collected StackFrames |
| */ |
| private synchronized StackFrame[] testLocalsUnused() { |
| // Unused local variables will become dead |
| int x = 0xA; |
| char c = 'z'; // 0x7A |
| String hi = "himom"; |
| long l = 0x3FF00000000L + 0xFFFFL; |
| double d = Math.PI; |
| |
| return walker.walk(s -> |
| s.filter(f -> TEST_METHODS.contains(f.getMethodName())) |
| .toArray(StackFrame[]::new) |
| ); |
| } |
| |
| /** |
| * Perform stackwalk, keeping local variables alive, and return a list of |
| * the collected StackFrames |
| */ |
| private synchronized StackFrame[] testLocalsKeepAlive() { |
| int x = 0xA; |
| char c = 'z'; // 0x7A |
| String hi = "himom"; |
| long l = 0x3FF00000000L + 0xFFFFL; |
| double d = Math.PI; |
| |
| StackFrame[] frames = walker.walk(s -> |
| s.filter(f -> TEST_METHODS.contains(f.getMethodName())) |
| .toArray(StackFrame[]::new) |
| ); |
| |
| // Use local variables so they stay alive |
| System.out.println("Stayin' alive: "+this+" "+x+" "+c+" "+hi+" "+l+" "+d); |
| return frames; |
| } |
| |
| /** |
| * Perform stackwalk, keeping method arguments alive, and return a list of |
| * the collected StackFrames |
| */ |
| private synchronized StackFrame[] testLocalsKeepAliveArgs(int x, char c, |
| String hi, long l, |
| double d) { |
| StackFrame[] frames = walker.walk(s -> |
| s.filter(f -> TEST_METHODS.contains(f.getMethodName())) |
| .toArray(StackFrame[]::new) |
| ); |
| |
| // Use local variables so they stay alive |
| System.out.println("Stayin' alive: "+this+" "+x+" "+c+" "+hi+" "+l+" "+d); |
| return frames; |
| } |
| |
| // An expected two-slot local (i.e. long or double) |
| static class TwoSlotValue { |
| public long value; |
| public TwoSlotValue(long value) { this.value = value; } |
| } |
| |
| // Expected values for locals in KnownLocalsTester.testLocals* methods |
| private final static Object[] LOCAL_VALUES = new Object[] { |
| LocalsAndOperands.KnownLocalsTester.class, |
| Integer.valueOf(0xA), |
| Integer.valueOf(0x7A), |
| "himom", |
| new TwoSlotValue(0x3FF00000000L + 0xFFFFL), |
| null, // 2nd slot |
| new TwoSlotValue(Double.doubleToRawLongBits(Math.PI)), |
| null, // 2nd slot |
| Integer.valueOf(0) |
| }; |
| |
| private final static List<String> TEST_METHODS = |
| List.of("testLocalsUnused", |
| "testLocalsKeepAlive", |
| "testLocalsKeepAliveArgs"); |
| } |
| |
| /* Simpler tests of long & double arguments */ |
| |
| @Test |
| public static void testUsedLongArg() throws Exception { |
| usedLong(LOWER_LONG_VAL); |
| usedLong(UPPER_LONG_VAL); |
| usedLong(NEG_LONG_VAL); |
| } |
| |
| private static void usedLong(long longArg) throws Exception { |
| StackFrame[] frames = extendedWalker.walk(s -> |
| s.filter(f -> "usedLong".equals(f.getMethodName())) |
| .toArray(StackFrame[]::new) |
| ); |
| try { |
| dumpFramesIfDebug(frames); |
| |
| Object[] locals = (Object[]) getLocals.invoke(frames[0]); |
| assertLongIsInSlots(locals[0], locals[1], longArg); |
| System.out.println("Stayin' alive: " + longArg); |
| } catch (Exception t) { |
| dumpFramesIfNotDebug(frames); |
| throw t; |
| } |
| } |
| |
| @Test |
| public static void testUnusedLongArg() throws Exception { |
| if (testUnused) { |
| unusedLong(NEG_LONG_VAL); |
| } |
| } |
| |
| private static void unusedLong(long longArg) throws Exception { |
| StackFrame[] frames = extendedWalker.walk(s -> |
| s.filter(f -> "unusedLong".equals(f.getMethodName())) |
| .toArray(StackFrame[]::new) |
| ); |
| try { |
| dumpFramesIfDebug(frames); |
| |
| final Object[] locals = (Object[]) getLocals.invoke(frames[0]); |
| assertLongIsInSlots(locals[0], locals[1], NEG_LONG_VAL); |
| } catch (Exception t) { |
| dumpFramesIfNotDebug(frames); |
| throw t; |
| } |
| } |
| |
| @Test |
| public static void testUsedDoubleArg() throws Exception { |
| usedDouble(LOWER_DOUBLE_VAL); |
| usedDouble(UPPER_DOUBLE_VAL); |
| } |
| |
| private static void usedDouble(double doubleArg) throws Exception { |
| StackFrame[] frames = extendedWalker.walk(s -> |
| s.filter(f -> "usedDouble".equals(f.getMethodName())) |
| .toArray(StackFrame[]::new) |
| ); |
| try { |
| dumpFramesIfDebug(frames); |
| |
| Object[] locals = (Object[]) getLocals.invoke(frames[0]); |
| assertDoubleIsInSlots(locals[0], locals[1], doubleArg); |
| System.out.println("Stayin' alive: " + doubleArg); |
| } catch (Exception t) { |
| dumpFramesIfNotDebug(frames); |
| throw t; |
| } |
| } |
| |
| /******************* |
| * Utility Methods * |
| *******************/ |
| |
| /** |
| * Print stack trace with locals |
| */ |
| public static void dumpStackWithLocals(StackFrame...frames) { |
| Stream.of(frames).forEach(LocalsAndOperands::printLocals); |
| } |
| |
| public static void dumpFramesIfDebug(StackFrame...frames) { |
| if (debug) { dumpStackWithLocals(frames); } |
| } |
| |
| public static void dumpFramesIfNotDebug(StackFrame...frames) { |
| if (!debug) { dumpStackWithLocals(frames); } |
| } |
| |
| /** |
| * Print the StackFrame and an indexed list of its locals |
| */ |
| public static void printLocals(StackWalker.StackFrame frame) { |
| try { |
| System.out.println("Locals for: " + frame); |
| Object[] locals = (Object[]) getLocals.invoke(frame); |
| for (int i = 0; i < locals.length; i++) { |
| String localStr = null; |
| |
| if (primitiveSlot64Class.isInstance(locals[i])) { |
| localStr = String.format("0x%X", |
| (Long)primitiveLongValue.invoke(locals[i])); |
| } else if (primitiveSlot32Class.isInstance(locals[i])) { |
| localStr = String.format("0x%X", |
| (Integer)primitiveIntValue.invoke(locals[i])); |
| } else if (locals[i] != null) { |
| localStr = locals[i].toString(); |
| } |
| System.out.format(" local %d: %s type %s\n", i, localStr, 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 (primitiveSlotClass.isInstance(o)) { |
| int s = (int)primitiveSize.invoke(o); |
| return s + "-byte primitive"; |
| } else { |
| return o.getClass().getName(); |
| } |
| } catch(Exception e) { throw new RuntimeException(e); } |
| } |
| |
| /* |
| * Check if the PrimitiveValue "primVal" contains the specified value, |
| * either a Long or an Integer. |
| */ |
| static boolean primitiveValueEquals(Object primVal, Object expectedVal) { |
| try { |
| if (expectedVal instanceof Long) { |
| assertFalse(is32bit); |
| assertTrue(primitiveSlot64Class.isInstance(primVal)); |
| assertTrue(8 == (int)primitiveSize.invoke(primVal)); |
| return Objects.equals(primitiveLongValue.invoke(primVal), expectedVal); |
| } else if (expectedVal instanceof Integer) { |
| int expectedInt = (Integer)expectedVal; |
| if (is32bit) { |
| assertTrue(primitiveSlot32Class.isInstance(primVal), |
| "expected a PrimitiveSlot32 on 32-bit VM"); |
| assertTrue(4 == (int)primitiveSize.invoke(primVal)); |
| return expectedInt == (int)primitiveIntValue.invoke(primVal); |
| } else { |
| assertTrue(primitiveSlot64Class.isInstance(primVal), |
| "expected a PrimitiveSlot64 on 64-bit VM"); |
| assertTrue(8 == (int)primitiveSize.invoke(primVal)); |
| // Look for int expectedVal in high- or low-order 32 bits |
| long primValLong = (long)primitiveLongValue.invoke(primVal); |
| return (int)(primValLong & 0x00000000FFFFFFFFL) == expectedInt || |
| (int)(primValLong >>> 32) == expectedInt; |
| } |
| } else { |
| throw new RuntimeException("Called with non-Integer/Long: " + expectedVal); |
| } |
| } catch (IllegalAccessException|InvocationTargetException e) { |
| throw new RuntimeException(e); |
| } |
| |
| } |
| |
| /* |
| * Assert that the expected 2-slot long value is stored somewhere in the |
| * pair of slots. |
| * Throw exception if long value isn't in the two slots given. |
| * Accounts for 32 vs 64 bit, but is lax on endianness (accepts either) |
| */ |
| static void assertLongIsInSlots(Object primVal0, Object primVal1, long expected) { |
| try { |
| if (is32bit) { |
| int upper = (int)(expected & 0xFFFFFFFFL); |
| int lower = (int)(expected >> 32); |
| |
| if (!((primitiveValueEquals(primVal0, upper) && |
| primitiveValueEquals(primVal1, lower)) || |
| (primitiveValueEquals(primVal0, lower) && |
| primitiveValueEquals(primVal1, upper)))) { |
| throw new RuntimeException(String.format("0x%X and 0x%X of 0x%016X not found in 0x%X and 0x%X", |
| upper, lower, expected, |
| (int)primitiveIntValue.invoke(primVal0), |
| (int)primitiveIntValue.invoke(primVal1))); |
| } |
| } else { |
| if (!(primitiveValueEquals(primVal0, expected) || |
| primitiveValueEquals(primVal1, expected))) { |
| throw new RuntimeException(String.format("0x%016X not found in 0x%016X or 0x%016X", |
| expected, |
| (long)primitiveLongValue.invoke(primVal0), |
| (long)primitiveLongValue.invoke(primVal1))); |
| } |
| } |
| } catch (IllegalAccessException|InvocationTargetException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| static void assertDoubleIsInSlots(Object primVal0, Object primVal1, double expected) { |
| assertLongIsInSlots(primVal0, primVal1, Double.doubleToRawLongBits(expected)); |
| } |
| } |