8156073: 2-slot LiveStackFrame locals (long and double) are incorrect
authorbchristi
Tue, 31 Jan 2017 11:51:02 -0800
changeset 43715 6d72cc84759f
parent 43537 fe3858b549c8
child 43717 303ca4a590e7
8156073: 2-slot LiveStackFrame locals (long and double) are incorrect Reviewed-by: coleenp, mchung
jdk/src/java.base/share/classes/java/lang/LiveStackFrame.java
jdk/src/java.base/share/classes/java/lang/LiveStackFrameInfo.java
jdk/test/java/lang/StackWalker/CountLocalSlots.java
jdk/test/java/lang/StackWalker/LocalsAndOperands.java
jdk/test/java/lang/StackWalker/LocalsCrash.java
--- a/jdk/src/java.base/share/classes/java/lang/LiveStackFrame.java	Tue Jan 24 00:30:25 2017 +0100
+++ b/jdk/src/java.base/share/classes/java/lang/LiveStackFrame.java	Tue Jan 31 11:51:02 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -55,14 +55,36 @@
      *
      * <p>A single local variable can hold a value of type boolean, byte, char,
      * short, int, float, reference or returnAddress.  A pair of local variables
-     * can hold a value of type long or double.  In other words,
-     * a value of type long or type double occupies two consecutive local
-     * variables.  For a value of primitive type, the element in the
-     * local variable array is an {@link PrimitiveValue} object;
-     * otherwise, the element is an {@code Object}.
+     * can hold a value of type long or double (JVMS section 2.6.1).  Primitive
+     * locals are represented in the returned array as {@code PrimitiveSlot}s,
+     * with longs and doubles occupying a pair of consecutive
+     * {@code PrimitiveSlot}s.
+     *
+     * <p>The current VM implementation does not provide specific type
+     * information for primitive locals.  This method simply returns the raw
+     * contents of the VM's primitive locals on a best-effort basis, without
+     * indicating a specific type.
+     *
+     * <p>The returned array may contain null entries for local variables that
+     * are not live.
      *
-     * <p>The returned array may contain null entries if a local variable is not
-     * live.
+     * @implNote
+     * <p> The specific subclass of {@code PrimitiveSlot} will reflect the
+     * underlying architecture, and will be either {@code PrimitiveSlot32} or
+     * {@code PrimitiveSlot64}.
+     *
+     * <p>How a long or double value is stored in the pair of
+     * {@code PrimitiveSlot}s can vary based on the underlying architecture and
+     * VM implementation.  On 32-bit architectures, long/double values are split
+     * between the two {@code PrimitiveSlot32}s.
+     * On 64-bit architectures, the entire value may be stored in one of the
+     * {@code PrimitiveSlot64}s, with the other {@code PrimitiveSlot64} being
+     * unused.
+     *
+     * <p>The contents of the unused, high-order portion of a
+     * {@code PrimitiveSlot64} (when storing a primitive other than a long or
+     * double) is unspecified.  In particular, the unused bits are not
+     * necessarily zeroed out.
      *
      * @return  the local variable array of this stack frame.
      */
@@ -78,7 +100,7 @@
      * <p>Each entry on the operand stack can hold a value of any Java Virtual
      * Machine Type.
      * For a value of primitive type, the element in the returned array is
-     * an {@link PrimitiveValue} object; otherwise, the element is the {@code Object}
+     * a {@link PrimitiveSlot} object; otherwise, the element is the {@code Object}
      * on the operand stack.
      *
      * @return the operand stack of this stack frame.
@@ -87,107 +109,37 @@
 
     /**
      * <em>UNSUPPORTED</em> This interface is intended to be package-private
-     * or move to an internal package.<p>
+     * or moved to an internal package.<p>
      *
-     * Represents a local variable or an entry on the operand whose value is
+     * Represents a local variable or an entry on the operand stack whose value is
      * of primitive type.
      */
-    public abstract class PrimitiveValue {
+    public abstract class PrimitiveSlot {
         /**
-         * Returns the base type of this primitive value, one of
-         * {@code B, D, C, F, I, J, S, Z}.
-         *
-         * @return Name of a base type
-         * @jvms table 4.3-A
+         * Returns the size, in bytes, of the slot.
          */
-        abstract char type();
+        public abstract int size();
 
         /**
-         * Returns the boolean value if this primitive value is of type boolean.
-         * @return the boolean value if this primitive value is of type boolean.
+         * Returns the int value if this primitive value is of size 4
+         * @return the int value if this primitive value is of size 4
          *
          * @throws UnsupportedOperationException if this primitive value is not
-         * of type boolean.
-         */
-        public boolean booleanValue() {
-            throw new UnsupportedOperationException("this primitive of type " + type());
-        }
-
-        /**
-         * Returns the int value if this primitive value is of type int.
-         * @return the int value if this primitive value is of type int.
-         *
-         * @throws UnsupportedOperationException if this primitive value is not
-         * of type int.
+         * of size 4.
          */
         public int intValue() {
-            throw new UnsupportedOperationException("this primitive of type " + type());
-        }
-
-        /**
-         * Returns the long value if this primitive value is of type long.
-         * @return the long value if this primitive value is of type long.
-         *
-         * @throws UnsupportedOperationException if this primitive value is not
-         * of type long.
-         */
-        public long longValue() {
-            throw new UnsupportedOperationException("this primitive of type " + type());
+            throw new UnsupportedOperationException("this " + size() + "-byte primitive");
         }
 
         /**
-         * Returns the char value if this primitive value is of type char.
-         * @return the char value if this primitive value is of type char.
-         *
-         * @throws UnsupportedOperationException if this primitive value is not
-         * of type char.
-         */
-        public char charValue() {
-            throw new UnsupportedOperationException("this primitive of type " + type());
-        }
-
-        /**
-         * Returns the byte value if this primitive value is of type byte.
-         * @return the byte value if this primitive value is of type byte.
-         *
-         * @throws UnsupportedOperationException if this primitive value is not
-         * of type byte.
-         */
-        public byte byteValue() {
-            throw new UnsupportedOperationException("this primitive of type " + type());
-        }
-
-        /**
-         * Returns the short value if this primitive value is of type short.
-         * @return the short value if this primitive value is of type short.
+         * Returns the long value if this primitive value is of size 8
+         * @return the long value if this primitive value is of size 8
          *
          * @throws UnsupportedOperationException if this primitive value is not
-         * of type short.
-         */
-        public short shortValue() {
-            throw new UnsupportedOperationException("this primitive of type " + type());
-        }
-
-        /**
-         * Returns the float value if this primitive value is of type float.
-         * @return the float value if this primitive value is of type float.
-         *
-         * @throws UnsupportedOperationException if this primitive value is not
-         * of type float.
+         * of size 8.
          */
-        public float floatValue() {
-            throw new UnsupportedOperationException("this primitive of type " + type());
-        }
-
-        /**
-         * Returns the double value if this primitive value is of type double.
-         * @return the double value if this primitive value is of type double.
-         *
-         * @throws UnsupportedOperationException if this primitive value is not
-         * of type double.
-         */
-        public double doubleValue() {
-            throw new UnsupportedOperationException("this primitive of type " + type());
+        public long longValue() {
+            throw new UnsupportedOperationException("this " + size() + "-byte primitive");
         }
     }
 
--- a/jdk/src/java.base/share/classes/java/lang/LiveStackFrameInfo.java	Tue Jan 24 00:30:25 2017 +0100
+++ b/jdk/src/java.base/share/classes/java/lang/LiveStackFrameInfo.java	Tue Jan 31 11:51:02 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -24,15 +24,13 @@
  */
 package java.lang;
 
-import java.lang.StackWalker.Option;
-import java.util.EnumSet;
-import java.util.Set;
-
-import static java.lang.StackWalker.ExtendedOption.*;
-
 final class LiveStackFrameInfo extends StackFrameInfo implements LiveStackFrame {
     private static Object[] EMPTY_ARRAY = new Object[0];
 
+    // These flags must match the values maintained in the VM
+    private static final int MODE_INTERPRETED = 0x01;
+    private static final int MODE_COMPILED    = 0x02;
+
     LiveStackFrameInfo(StackWalker walker) {
         super(walker);
     }
@@ -41,6 +39,7 @@
     private Object[] monitors = EMPTY_ARRAY;
     private Object[] locals = EMPTY_ARRAY;
     private Object[] operands = EMPTY_ARRAY;
+    private int mode = 0;
 
     @Override
     public Object[] getMonitors() {
@@ -57,51 +56,44 @@
         return operands;
     }
 
-    /*
-     * Convert primitive value to {@code Primitive} object to represent
-     * a local variable or an element on the operand stack of primitive type.
-     */
-    static PrimitiveValue asPrimitive(boolean value) {
-        return new BooleanPrimitive(value);
-    }
-
-    static PrimitiveValue asPrimitive(int value) {
-        return new IntPrimitive(value);
-    }
-
-    static PrimitiveValue asPrimitive(short value) {
-        return new ShortPrimitive(value);
-    }
-
-    static PrimitiveValue asPrimitive(char value) {
-        return new CharPrimitive(value);
+    @Override
+    public String toString() {
+        StringBuilder retVal = new StringBuilder(super.toString());
+        if (mode != 0) {
+            retVal.append("(");
+            if ((mode & MODE_INTERPRETED) == MODE_INTERPRETED) {
+                retVal.append(" interpreted ");
+            }
+            if ((mode & MODE_COMPILED) == MODE_COMPILED) {
+                retVal.append(" compiled ");
+            }
+            retVal.append(")");
+        }
+        return retVal.toString();
     }
 
-    static PrimitiveValue asPrimitive(byte value) {
-        return new BytePrimitive(value);
-    }
+    /*
+     * Convert primitive value to {@code PrimitiveSlot} object to represent
+     * a local variable or an element on the operand stack of primitive type.
+     */
 
-    static PrimitiveValue asPrimitive(long value) {
-        return new LongPrimitive(value);
+    static PrimitiveSlot asPrimitive(int value) {
+        return new PrimitiveSlot32(value);
     }
 
-    static PrimitiveValue asPrimitive(float value) {
-        return new FloatPrimitive(value);
+    static PrimitiveSlot asPrimitive(long value) {
+        return new PrimitiveSlot64(value);
     }
 
-    static PrimitiveValue asPrimitive(double value) {
-        return new DoublePrimitive(value);
-    }
-
-    private static class IntPrimitive extends PrimitiveValue {
+    private static class PrimitiveSlot32 extends PrimitiveSlot {
         final int value;
-        IntPrimitive(int value) {
+        PrimitiveSlot32(int value) {
             this.value = value;
         }
 
         @Override
-        public char type() {
-            return 'I';
+        public int size() {
+            return 4;
         }
 
         @Override
@@ -115,103 +107,15 @@
         }
     }
 
-    private static class ShortPrimitive extends PrimitiveValue {
-        final short value;
-        ShortPrimitive(short value) {
-            this.value = value;
-        }
-
-        @Override
-        public char type() {
-            return 'S';
-        }
-
-        @Override
-        public short shortValue() {
-            return value;
-        }
-
-        @Override
-        public String toString() {
-            return String.valueOf(value);
-        }
-    }
-
-    private static class BooleanPrimitive extends PrimitiveValue {
-        final boolean value;
-        BooleanPrimitive(boolean value) {
+    private static class PrimitiveSlot64 extends PrimitiveSlot {
+        final long value;
+        PrimitiveSlot64(long value) {
             this.value = value;
         }
 
         @Override
-        public char type() {
-            return 'Z';
-        }
-
-        @Override
-        public boolean booleanValue()  {
-            return value;
-        }
-
-        @Override
-        public String toString() {
-            return String.valueOf(value);
-        }
-    }
-
-    private static class CharPrimitive extends PrimitiveValue {
-        final char value;
-        CharPrimitive(char value) {
-            this.value = value;
-        }
-
-        @Override
-        public char type() {
-            return 'C';
-        }
-
-        @Override
-        public char charValue() {
-            return value;
-        }
-
-        @Override
-        public String toString() {
-            return String.valueOf(value);
-        }
-    }
-
-    private static class BytePrimitive extends PrimitiveValue {
-        final byte value;
-        BytePrimitive(byte value) {
-            this.value = value;
-        }
-
-        @Override
-        public char type() {
-            return 'B';
-        }
-
-        @Override
-        public byte byteValue() {
-            return value;
-        }
-
-        @Override
-        public String toString() {
-            return String.valueOf(value);
-        }
-    }
-
-    private static class LongPrimitive extends PrimitiveValue {
-        final long value;
-        LongPrimitive(long value) {
-            this.value = value;
-        }
-
-        @Override
-        public char type() {
-            return 'J';
+        public int size() {
+            return 8;
         }
 
         @Override
@@ -224,48 +128,4 @@
             return String.valueOf(value);
         }
     }
-
-    private static class FloatPrimitive extends PrimitiveValue {
-        final float value;
-        FloatPrimitive(float value) {
-            this.value = value;
-        }
-
-        @Override
-        public char type() {
-            return 'F';
-        }
-
-        @Override
-        public float floatValue() {
-            return value;
-        }
-
-        @Override
-        public String toString() {
-            return String.valueOf(value);
-        }
-    }
-
-    private static class DoublePrimitive extends PrimitiveValue {
-        final double value;
-        DoublePrimitive(double value) {
-            this.value = value;
-        }
-
-        @Override
-        public char type() {
-            return 'D';
-        }
-
-        @Override
-        public double doubleValue() {
-            return value;
-        }
-
-        @Override
-        public String toString() {
-            return String.valueOf(value);
-        }
-    }
 }
--- a/jdk/test/java/lang/StackWalker/CountLocalSlots.java	Tue Jan 24 00:30:25 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/*
- * 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
- * @modules java.base/java.lang:open
- * @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 + ")");
-            }
-        }
-    }
-}
--- a/jdk/test/java/lang/StackWalker/LocalsAndOperands.java	Tue Jan 24 00:30:25 2017 +0100
+++ b/jdk/test/java/lang/StackWalker/LocalsAndOperands.java	Tue Jan 31 11:51:02 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -23,33 +23,54 @@
 
 /*
  * @test
- * @bug 8020968 8147039
+ * @bug 8020968 8147039 8156073
  * @summary Tests for locals and operands
  * @modules java.base/java.lang:open
- * @run testng LocalsAndOperands
+ * @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 = true;
+    static final boolean debug = false;
+    static final boolean is32bit;
+    static final boolean testUnused;
 
     static Class<?> liveStackFrameClass;
-    static Class<?> primitiveValueClass;
+    static Class<?> primitiveSlotClass;
+    static Class<?> primitiveSlot32Class;
+    static Class<?> primitiveSlot64Class;
+
     static StackWalker extendedWalker;
     static Method getLocals;
     static Method getOperands;
     static Method getMonitors;
-    static Method primitiveType;
+    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");
-            primitiveValueClass = Class.forName("java.lang.LiveStackFrame$PrimitiveValue");
+            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);
@@ -60,12 +81,31 @@
             getMonitors = liveStackFrameClass.getDeclaredMethod("getMonitors");
             getMonitors.setAccessible(true);
 
-            primitiveType = primitiveValueClass.getDeclaredMethod("type");
-            primitiveType.setAccessible(true);
+            primitiveSize = primitiveSlotClass.getDeclaredMethod("size");
+            primitiveSize.setAccessible(true);
+
+            primitiveLongValue = primitiveSlotClass.getDeclaredMethod("longValue");
+            primitiveLongValue.setAccessible(true);
+
+            primitiveIntValue = primitiveSlotClass.getDeclaredMethod("intValue");
+            primitiveIntValue.setAccessible(true);
 
-            Method method = liveStackFrameClass.getMethod("getStackWalker");
-            method.setAccessible(true);
-            extendedWalker = (StackWalker) method.invoke(null);
+            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); }
     }
 
@@ -80,47 +120,17 @@
      * DataProviders *
      *****************/
 
-    /** Calls testLocals() and provides LiveStackFrames for testLocals* methods */
-    @DataProvider
-    public static StackFrame[][] provider() {
-        return new StackFrame[][] {
-            new Tester().testLocals()
-        };
-    }
-
-    /**
-     * Calls testLocalsKeepAlive() and provides LiveStackFrames for testLocals* methods.
-     * Local variables in testLocalsKeepAlive() are ensured to not become dead.
-     */
+    /** Calls KnownLocalsTester.testLocals* and provides LiveStackFrames */
     @DataProvider
-    public static StackFrame[][] keepAliveProvider() {
-        return new StackFrame[][] {
-            new Tester().testLocalsKeepAlive()
-        };
-    }
-
-    /**
-     * 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()
-        };
-    }
-
-    /**
-     * 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()
-        };
+    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]);
     }
 
     /****************
@@ -128,135 +138,123 @@
      ****************/
 
     /**
-     * Check for expected local values and types in the LiveStackFrame
+     * Check for expected local values in the LiveStackFrame
      */
-    @Test(dataProvider = "keepAliveProvider")
+    @Test(dataProvider = "knownLocalsProvider")
     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);
-                    }
-                }
-            }
-        );
+        dumpFramesIfDebug(frames);
+        try {
+            Stream.of(frames)
+                  .filter(f -> KnownLocalsTester.TEST_METHODS.contains(f.getMethodName()))
+                  .forEach(LocalsAndOperands::checkFrameLocals);
+        } catch (Exception e) { dumpFramesIfNotDebug(frames); throw e; }
     }
 
     /**
-     * Basic sanity check for locals and operands
+     * Check the locals in the given StackFrame against the expected values.
      */
-    @Test(dataProvider = "provider")
-    public static void sanityCheck(StackFrame... frames) {
-        if (debug) {
-            System.out.println("Running sanityCheck");
-        }
-        try {
-            Stream<StackFrame> stream = Arrays.stream(frames);
-            if (debug) {
-                stream.forEach(LocalsAndOperands::printLocals);
+    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 {
-                System.out.println(stream.count() + " frames");
+                throw new RuntimeException("Unrecognized expected local value " +
+                        i + ": " + expected);
             }
-        } catch (Throwable t) {
-            dumpStackWithLocals(frames);
-            throw t;
         }
     }
 
     /**
      * Sanity check for locals and operands, including testng/jtreg frames
+     * using all StackWalker options.
      */
-    @Test(dataProvider = "unfilteredProvider")
-    public static void unfilteredSanityCheck(StackFrame... frames) {
+    @Test
+    public synchronized void fullStackSanityCheck() throws Throwable {
         if (debug) {
-            System.out.println("Running unfilteredSanityCheck");
+            System.out.println("Running fullStackSanityCheck");
         }
-        try {
-            Stream<StackFrame> stream = Arrays.stream(frames);
+        StackWalker sw = (StackWalker) getExtendedWalker.invoke(null,
+                EnumSet.of(SHOW_HIDDEN_FRAMES, SHOW_REFLECT_FRAMES,
+                           RETAIN_CLASS_REFERENCE));
+        sw.forEach(f -> {
             if (debug) {
-                stream.forEach(f -> { System.out.println(f + ": " +
-                        invokeGetLocals(f).length + " locals"); } );
+                printLocals(f);
             } else {
-                System.out.println(stream.count() + " frames");
+                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);
+                }
             }
-        } catch (Throwable t) {
-            dumpStackWithLocals(frames);
-            throw t;
-        }
+        });
     }
 
     /**
      * 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");
-            }
-        }
+    @Test
+    public static void noLocalsSanityCheck() {
+        StackWalker sw = StackWalker.getInstance();
+        sw.forEach(f -> {
+            assertFalse(liveStackFrameClass.isInstance(f),
+                        "should not be LiveStackFrame");
+        });
     }
 
-    static class Tester {
+    /**
+     * Class stack-walking methods with a known set of methods and local variables.
+     */
+    static class KnownLocalsTester {
         private StackWalker walker;
-        private boolean filter = true; // Filter out testng/jtreg/etc frames?
 
-        Tester() {
+        KnownLocalsTester() {
             this.walker = extendedWalker;
         }
 
-        Tester(StackWalker walker, boolean filter) {
-            this.walker = walker;
-            this.filter = filter;
-        }
-
         /**
          * Perform stackwalk without keeping local variables alive and return an
          * array of the collected StackFrames
          */
-        private synchronized StackFrame[] testLocals() {
+        private synchronized StackFrame[] testLocalsUnused() {
             // Unused local variables will become dead
-            int x = 10;
-            char c = 'z';
+            int x = 0xA;
+            char c = 'z'; // 0x7A
             String hi = "himom";
-            long l = 1000000L;
-            double d =  3.1415926;
+            long l = 0x3FF00000000L + 0xFFFFL;
+            double d =  Math.PI;
 
-            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]);
-            }
+            return walker.walk(s ->
+                s.filter(f -> TEST_METHODS.contains(f.getMethodName()))
+                 .toArray(StackFrame[]::new)
+            );
         }
 
         /**
@@ -264,64 +262,153 @@
          * the collected StackFrames
          */
         private synchronized StackFrame[] testLocalsKeepAlive() {
-            int x = 10;
-            char c = 'z';
+            int x = 0xA;
+            char c = 'z'; // 0x7A
             String hi = "himom";
-            long l = 1000000L;
-            double d =  3.1415926;
+            long l = 0x3FF00000000L + 0xFFFFL;
+            double d =  Math.PI;
 
-            List<StackWalker.StackFrame> 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()));
-            }
+            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: "+x+" "+c+" "+hi+" "+l+" "+d);
-            return frames.toArray(new StackFrame[0]); // FIXME: convert to Array here
+            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 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",
+        // 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",
-            "0",
-            null, // skip, fix in 8156073
-            null, // skip, fix in 8156073
-            null, // skip, fix in 8156073
-            "0"
+            new TwoSlotValue(0x3FF00000000L + 0xFFFFL),
+            null, // 2nd slot
+            new TwoSlotValue(Double.doubleToRawLongBits(Math.PI)),
+            null, // 2nd slot
+            Integer.valueOf(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"
-        };
+        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);
+        }
+    }
 
-        final static Map NUM_LOCALS = Map.of("testLocals", 8,
-                                             "testLocalsKeepAlive",
-                                             LOCAL_VALUES.length);
-        private final static Collection<String> TEST_METHODS = NUM_LOCALS.keySet();
+    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) {
-        Arrays.stream(frames).forEach(LocalsAndOperands::printLocals);
+        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); }
     }
 
     /**
@@ -329,10 +416,21 @@
      */
     public static void printLocals(StackWalker.StackFrame frame) {
         try {
-            System.out.println(frame);
+            System.out.println("Locals for: " + 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]));
+                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);
@@ -352,12 +450,87 @@
         try {
             if (o == null) {
                 return "null";
-            } else if (primitiveValueClass.isInstance(o)) {
-                char c = (char)primitiveType.invoke(o);
-                return String.valueOf(c);
+            } 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));
+    }
 }
--- a/jdk/test/java/lang/StackWalker/LocalsCrash.java	Tue Jan 24 00:30:25 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/*
- * 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
- * @modules java.base/java.lang:open
- * @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<StackWalker.StackFrame> 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); }
-    }
-}