/* * Copyright (c) 2015, 2018, 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 */ /* * @test * @bug 8020968 8147039 8156073 * @modules java.base/java.lang:open * @requires !vm.graal.enabled * @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 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 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)); } }