1 /*
   2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8020968 8147039 8156073
  27  * @summary Tests for locals and operands
  28  * @modules java.base/java.lang:open
  29  * @run testng/othervm -Xint -DtestUnused=true LocalsAndOperands
  30  * @run testng/othervm -Xcomp LocalsAndOperands
  31  * @run testng/othervm -Xcomp -XX:-TieredCompilation LocalsAndOperands
  32  */
  33 
  34 import org.testng.annotations.*;
  35 import static org.testng.Assert.*;
  36 import java.lang.StackWalker.StackFrame;
  37 import static java.lang.StackWalker.Option.*;
  38 import java.lang.reflect.*;
  39 import java.util.*;
  40 import java.util.stream.*;
  41 
  42 public class LocalsAndOperands {
  43     static final boolean debug = false;
  44     static final boolean is32bit;
  45     static final boolean testUnused;
  46 
  47     static Class<?> liveStackFrameClass;
  48     static Class<?> primitiveSlotClass;
  49     static Class<?> primitiveSlot32Class;
  50     static Class<?> primitiveSlot64Class;
  51 
  52     static StackWalker extendedWalker;
  53     static Method getLocals;
  54     static Method getOperands;
  55     static Method getMonitors;
  56     static Method primitiveSize;
  57     static Method primitiveLongValue;
  58     static Method primitiveIntValue;
  59     static Method getExtendedWalker;
  60 
  61     private static final long LOWER_LONG_VAL = 4L; // Lower bits
  62     private static final long UPPER_LONG_VAL = 0x123400000000L; // Upper bits
  63     private static final long NEG_LONG_VAL = Long.MIN_VALUE;
  64 
  65     private static final double LOWER_DOUBLE_VAL = Double.longBitsToDouble(0xABCDL);
  66     private static final double UPPER_DOUBLE_VAL = Double.longBitsToDouble(0x432100000000L);
  67 
  68     static {
  69         try {
  70             liveStackFrameClass = Class.forName("java.lang.LiveStackFrame");
  71             primitiveSlotClass = Class.forName("java.lang.LiveStackFrame$PrimitiveSlot");
  72             primitiveSlot32Class = Class.forName("java.lang.LiveStackFrameInfo$PrimitiveSlot32");
  73             primitiveSlot64Class = Class.forName("java.lang.LiveStackFrameInfo$PrimitiveSlot64");
  74 
  75             getLocals = liveStackFrameClass.getDeclaredMethod("getLocals");
  76             getLocals.setAccessible(true);
  77 
  78             getOperands = liveStackFrameClass.getDeclaredMethod("getStack");
  79             getOperands.setAccessible(true);
  80 
  81             getMonitors = liveStackFrameClass.getDeclaredMethod("getMonitors");
  82             getMonitors.setAccessible(true);
  83 
  84             primitiveSize = primitiveSlotClass.getDeclaredMethod("size");
  85             primitiveSize.setAccessible(true);
  86 
  87             primitiveLongValue = primitiveSlotClass.getDeclaredMethod("longValue");
  88             primitiveLongValue.setAccessible(true);
  89 
  90             primitiveIntValue = primitiveSlotClass.getDeclaredMethod("intValue");
  91             primitiveIntValue.setAccessible(true);
  92 
  93             getExtendedWalker = liveStackFrameClass.getMethod("getStackWalker", Set.class);
  94             getExtendedWalker.setAccessible(true);
  95             extendedWalker = (StackWalker) getExtendedWalker.invoke(null,
  96                     EnumSet.noneOf(StackWalker.Option.class));
  97 
  98             String dataModel = System.getProperty("sun.arch.data.model");
  99             if ("32".equals(dataModel)) {
 100                 is32bit = true;
 101             } else if ("64".equals(dataModel)) {
 102                 is32bit= false;
 103             } else {
 104                 throw new RuntimeException("Weird data model:" + dataModel);
 105             }
 106             System.out.println("VM bits: " + dataModel);
 107 
 108             testUnused = System.getProperty("testUnused") != null;
 109         } catch (Throwable t) { throw new RuntimeException(t); }
 110     }
 111 
 112     /** Helper method to return a StackFrame's locals */
 113     static Object[] invokeGetLocals(StackFrame arg) {
 114         try {
 115             return (Object[]) getLocals.invoke(arg);
 116         } catch (Exception e) { throw new RuntimeException(e); }
 117     }
 118 
 119     /*****************
 120      * DataProviders *
 121      *****************/
 122 
 123     /** Calls KnownLocalsTester.testLocals* and provides LiveStackFrames */
 124     @DataProvider
 125     public static StackFrame[][] knownLocalsProvider() {
 126         List<StackFrame[]> list = new ArrayList<>(3);
 127         list.add(new KnownLocalsTester().testLocalsKeepAlive());
 128         list.add(new KnownLocalsTester().testLocalsKeepAliveArgs(0xA, 'z',
 129                 "himom", 0x3FF00000000L + 0xFFFF, Math.PI));
 130         if (testUnused) {
 131             list.add(new KnownLocalsTester().testLocalsUnused());
 132         }
 133         return list.toArray(new StackFrame[1][1]);
 134     }
 135 
 136     /****************
 137      * Test methods *
 138      ****************/
 139 
 140     /**
 141      * Check for expected local values in the LiveStackFrame
 142      */
 143     @Test(dataProvider = "knownLocalsProvider")
 144     public static void checkLocalValues(StackFrame... frames) {
 145         dumpFramesIfDebug(frames);
 146         try {
 147             Stream.of(frames)
 148                   .filter(f -> KnownLocalsTester.TEST_METHODS.contains(f.getMethodName()))
 149                   .forEach(LocalsAndOperands::checkFrameLocals);
 150         } catch (Exception e) { dumpFramesIfNotDebug(frames); throw e; }
 151     }
 152 
 153     /**
 154      * Check the locals in the given StackFrame against the expected values.
 155      */
 156     private static void checkFrameLocals(StackFrame f) {
 157         Object[] expectedArray = KnownLocalsTester.LOCAL_VALUES;
 158         Object[] locals = invokeGetLocals(f);
 159 
 160         for (int i = 0; i < locals.length; i++) {
 161             Object expected = expectedArray[i];
 162             Object observed = locals[i];
 163 
 164             if (expected == null) { /* skip nulls in golden values */
 165                 continue;
 166             } else if (expected instanceof KnownLocalsTester.TwoSlotValue) {
 167                 // confirm integrity of expected values
 168                 assertEquals(expectedArray[i+1], null,
 169                         "Malformed array of expected values - slot after TwoSlotValue should be null");
 170                 assertLongIsInSlots(locals[i], locals[i+1],
 171                         ((KnownLocalsTester.TwoSlotValue)expected).value);
 172                 i++; // skip following slot
 173             } else if (primitiveSlotClass.isInstance(observed)) { // single slot primitive
 174                 assertTrue(primitiveValueEquals(observed, expected),
 175                         "Local value mismatch: local " + i + " value is " +
 176                           observed + ", expected " + expected);
 177             } else if (expected instanceof Class) {
 178                 assertTrue(((Class)expected).isInstance(observed),
 179                         "Local value mismatch: local " + i + " expected instancof " +
 180                           expected + " but got " + observed);
 181             } else if (expected instanceof String) {
 182                 assertEquals(expected, observed, "Local value mismatch: local " +
 183                         i + " value is " + observed + ", expected " + expected);
 184             } else {
 185                 throw new RuntimeException("Unrecognized expected local value " +
 186                         i + ": " + expected);
 187             }
 188         }
 189     }
 190 
 191     /**
 192      * Sanity check for locals and operands, including testng/jtreg frames
 193      * using all StackWalker options.
 194      */
 195     @Test
 196     public synchronized void fullStackSanityCheck() throws Throwable {
 197         if (debug) {
 198             System.out.println("Running fullStackSanityCheck");
 199         }
 200         StackWalker sw = (StackWalker) getExtendedWalker.invoke(null,
 201                 EnumSet.of(SHOW_HIDDEN_FRAMES, SHOW_REFLECT_FRAMES,
 202                            RETAIN_CLASS_REFERENCE));
 203         sw.forEach(f -> {
 204             if (debug) {
 205                 printLocals(f);
 206             } else {
 207                 try {
 208                     System.out.println("    " + f + ": " +
 209                       ((Object[]) getLocals.invoke(f)).length + " locals, " +
 210                       ((Object[]) getOperands.invoke(f)).length + " operands, " +
 211                       ((Object[]) getMonitors.invoke(f)).length + " monitors");
 212                 } catch (IllegalAccessException|InvocationTargetException t) {
 213                     throw new RuntimeException(t);
 214                 }
 215             }
 216         });
 217     }
 218 
 219     /**
 220      * Test that LiveStackFrames are not provided with the default StackWalker
 221      * options.
 222      */
 223     @Test
 224     public static void noLocalsSanityCheck() {
 225         StackWalker sw = StackWalker.getInstance();
 226         sw.forEach(f -> {
 227             assertFalse(liveStackFrameClass.isInstance(f),
 228                         "should not be LiveStackFrame");
 229         });
 230     }
 231 
 232     /**
 233      * Class stack-walking methods with a known set of methods and local variables.
 234      */
 235     static class KnownLocalsTester {
 236         private StackWalker walker;
 237 
 238         KnownLocalsTester() {
 239             this.walker = extendedWalker;
 240         }
 241 
 242         /**
 243          * Perform stackwalk without keeping local variables alive and return an
 244          * array of the collected StackFrames
 245          */
 246         private synchronized StackFrame[] testLocalsUnused() {
 247             // Unused local variables will become dead
 248             int x = 0xA;
 249             char c = 'z'; // 0x7A
 250             String hi = "himom";
 251             long l = 0x3FF00000000L + 0xFFFFL;
 252             double d =  Math.PI;
 253 
 254             return walker.walk(s ->
 255                 s.filter(f -> TEST_METHODS.contains(f.getMethodName()))
 256                  .toArray(StackFrame[]::new)
 257             );
 258         }
 259 
 260         /**
 261          * Perform stackwalk, keeping local variables alive, and return a list of
 262          * the collected StackFrames
 263          */
 264         private synchronized StackFrame[] testLocalsKeepAlive() {
 265             int x = 0xA;
 266             char c = 'z'; // 0x7A
 267             String hi = "himom";
 268             long l = 0x3FF00000000L + 0xFFFFL;
 269             double d =  Math.PI;
 270 
 271             StackFrame[] frames = walker.walk(s ->
 272                 s.filter(f -> TEST_METHODS.contains(f.getMethodName()))
 273                  .toArray(StackFrame[]::new)
 274             );
 275 
 276             // Use local variables so they stay alive
 277             System.out.println("Stayin' alive: "+this+" "+x+" "+c+" "+hi+" "+l+" "+d);
 278             return frames;
 279         }
 280 
 281         /**
 282          * Perform stackwalk, keeping method arguments alive, and return a list of
 283          * the collected StackFrames
 284          */
 285         private synchronized StackFrame[] testLocalsKeepAliveArgs(int x, char c,
 286                                                                   String hi, long l,
 287                                                                   double d) {
 288             StackFrame[] frames = walker.walk(s ->
 289                 s.filter(f -> TEST_METHODS.contains(f.getMethodName()))
 290                  .toArray(StackFrame[]::new)
 291             );
 292 
 293             // Use local variables so they stay alive
 294             System.out.println("Stayin' alive: "+this+" "+x+" "+c+" "+hi+" "+l+" "+d);
 295             return frames;
 296         }
 297 
 298         // An expected two-slot local (i.e. long or double)
 299         static class TwoSlotValue {
 300             public long value;
 301             public TwoSlotValue(long value) { this.value = value; }
 302         }
 303 
 304         // Expected values for locals in KnownLocalsTester.testLocals* methods
 305         private final static Object[] LOCAL_VALUES = new Object[] {
 306             LocalsAndOperands.KnownLocalsTester.class,
 307             Integer.valueOf(0xA),
 308             Integer.valueOf(0x7A),
 309             "himom",
 310             new TwoSlotValue(0x3FF00000000L + 0xFFFFL),
 311             null, // 2nd slot
 312             new TwoSlotValue(Double.doubleToRawLongBits(Math.PI)),
 313             null, // 2nd slot
 314             Integer.valueOf(0)
 315         };
 316 
 317         private final static List<String> TEST_METHODS =
 318                 List.of("testLocalsUnused",
 319                         "testLocalsKeepAlive",
 320                         "testLocalsKeepAliveArgs");
 321     }
 322 
 323     /* Simpler tests of long & double arguments */
 324 
 325     @Test
 326     public static void testUsedLongArg() throws Exception {
 327         usedLong(LOWER_LONG_VAL);
 328         usedLong(UPPER_LONG_VAL);
 329         usedLong(NEG_LONG_VAL);
 330     }
 331 
 332     private static void usedLong(long longArg) throws Exception {
 333         StackFrame[] frames = extendedWalker.walk(s ->
 334             s.filter(f -> "usedLong".equals(f.getMethodName()))
 335              .toArray(StackFrame[]::new)
 336         );
 337         try {
 338             dumpFramesIfDebug(frames);
 339 
 340             Object[] locals = (Object[]) getLocals.invoke(frames[0]);
 341             assertLongIsInSlots(locals[0], locals[1], longArg);
 342             System.out.println("Stayin' alive: " + longArg);
 343         } catch (Exception t) {
 344             dumpFramesIfNotDebug(frames);
 345             throw t;
 346         }
 347     }
 348 
 349     @Test
 350     public static void testUnusedLongArg() throws Exception {
 351         if (testUnused) {
 352             unusedLong(NEG_LONG_VAL);
 353         }
 354     }
 355 
 356     private static void unusedLong(long longArg) throws Exception {
 357         StackFrame[] frames = extendedWalker.walk(s ->
 358             s.filter(f -> "unusedLong".equals(f.getMethodName()))
 359              .toArray(StackFrame[]::new)
 360         );
 361         try {
 362             dumpFramesIfDebug(frames);
 363 
 364             final Object[] locals = (Object[]) getLocals.invoke(frames[0]);
 365             assertLongIsInSlots(locals[0], locals[1], NEG_LONG_VAL);
 366         } catch (Exception t) {
 367             dumpFramesIfNotDebug(frames);
 368             throw t;
 369         }
 370     }
 371 
 372     @Test
 373     public static void testUsedDoubleArg() throws Exception {
 374         usedDouble(LOWER_DOUBLE_VAL);
 375         usedDouble(UPPER_DOUBLE_VAL);
 376     }
 377 
 378     private static void usedDouble(double doubleArg) throws Exception {
 379         StackFrame[] frames = extendedWalker.walk(s ->
 380             s.filter(f -> "usedDouble".equals(f.getMethodName()))
 381              .toArray(StackFrame[]::new)
 382         );
 383         try {
 384             dumpFramesIfDebug(frames);
 385 
 386             Object[] locals = (Object[]) getLocals.invoke(frames[0]);
 387             assertDoubleIsInSlots(locals[0], locals[1], doubleArg);
 388             System.out.println("Stayin' alive: " + doubleArg);
 389         } catch (Exception t) {
 390             dumpFramesIfNotDebug(frames);
 391             throw t;
 392         }
 393     }
 394 
 395     /*******************
 396      * Utility Methods *
 397      *******************/
 398 
 399     /**
 400      * Print stack trace with locals
 401      */
 402     public static void dumpStackWithLocals(StackFrame...frames) {
 403         Stream.of(frames).forEach(LocalsAndOperands::printLocals);
 404     }
 405 
 406     public static void dumpFramesIfDebug(StackFrame...frames) {
 407         if (debug) { dumpStackWithLocals(frames); }
 408     }
 409 
 410     public static void dumpFramesIfNotDebug(StackFrame...frames) {
 411         if (!debug) { dumpStackWithLocals(frames); }
 412     }
 413 
 414     /**
 415      * Print the StackFrame and an indexed list of its locals
 416      */
 417     public static void printLocals(StackWalker.StackFrame frame) {
 418         try {
 419             System.out.println("Locals for: " + frame);
 420             Object[] locals = (Object[]) getLocals.invoke(frame);
 421             for (int i = 0; i < locals.length; i++) {
 422                 String localStr = null;
 423 
 424                 if (primitiveSlot64Class.isInstance(locals[i])) {
 425                     localStr = String.format("0x%X",
 426                             (Long)primitiveLongValue.invoke(locals[i]));
 427                 } else if (primitiveSlot32Class.isInstance(locals[i])) {
 428                     localStr = String.format("0x%X",
 429                             (Integer)primitiveIntValue.invoke(locals[i]));
 430                 } else if (locals[i] != null) {
 431                     localStr = locals[i].toString();
 432                 }
 433                 System.out.format("  local %d: %s type %s\n", i, localStr, type(locals[i]));
 434             }
 435 
 436             Object[] operands = (Object[]) getOperands.invoke(frame);
 437             for (int i = 0; i < operands.length; i++) {
 438                 System.out.format("  operand %d: %s type %s%n", i, operands[i],
 439                                   type(operands[i]));
 440             }
 441 
 442             Object[] monitors = (Object[]) getMonitors.invoke(frame);
 443             for (int i = 0; i < monitors.length; i++) {
 444                 System.out.format("  monitor %d: %s%n", i, monitors[i]);
 445             }
 446         } catch (Exception e) { throw new RuntimeException(e); }
 447     }
 448 
 449     private static String type(Object o) {
 450         try {
 451             if (o == null) {
 452                 return "null";
 453             } else if (primitiveSlotClass.isInstance(o)) {
 454                 int s = (int)primitiveSize.invoke(o);
 455                 return s + "-byte primitive";
 456             } else {
 457                 return o.getClass().getName();
 458             }
 459         } catch(Exception e) { throw new RuntimeException(e); }
 460     }
 461 
 462     /*
 463      * Check if the PrimitiveValue "primVal" contains the specified value,
 464      * either a Long or an Integer.
 465      */
 466     static boolean primitiveValueEquals(Object primVal, Object expectedVal) {
 467         try {
 468             if (expectedVal instanceof Long) {
 469                 assertFalse(is32bit);
 470                 assertTrue(primitiveSlot64Class.isInstance(primVal));
 471                 assertTrue(8 == (int)primitiveSize.invoke(primVal));
 472                 return Objects.equals(primitiveLongValue.invoke(primVal), expectedVal);
 473             } else if (expectedVal instanceof Integer) {
 474                 int expectedInt = (Integer)expectedVal;
 475                 if (is32bit) {
 476                     assertTrue(primitiveSlot32Class.isInstance(primVal),
 477                             "expected a PrimitiveSlot32 on 32-bit VM");
 478                     assertTrue(4 == (int)primitiveSize.invoke(primVal));
 479                     return expectedInt == (int)primitiveIntValue.invoke(primVal);
 480                 } else {
 481                     assertTrue(primitiveSlot64Class.isInstance(primVal),
 482                             "expected a PrimitiveSlot64 on 64-bit VM");
 483                     assertTrue(8 == (int)primitiveSize.invoke(primVal));
 484                     // Look for int expectedVal in high- or low-order 32 bits
 485                     long primValLong = (long)primitiveLongValue.invoke(primVal);
 486                     return (int)(primValLong & 0x00000000FFFFFFFFL) == expectedInt ||
 487                            (int)(primValLong >>> 32) == expectedInt;
 488                 }
 489             } else {
 490                 throw new RuntimeException("Called with non-Integer/Long: " + expectedVal);
 491             }
 492         } catch (IllegalAccessException|InvocationTargetException e) {
 493             throw new RuntimeException(e);
 494         }
 495 
 496     }
 497 
 498     /*
 499      * Assert that the expected 2-slot long value is stored somewhere in the
 500      * pair of slots.
 501      * Throw exception if long value isn't in the two slots given.
 502      * Accounts for 32 vs 64 bit, but is lax on endianness (accepts either)
 503      */
 504     static void assertLongIsInSlots(Object primVal0, Object primVal1, long expected) {
 505         try {
 506             if (is32bit) {
 507                 int upper = (int)(expected & 0xFFFFFFFFL);
 508                 int lower = (int)(expected >> 32);
 509 
 510                 if (!((primitiveValueEquals(primVal0, upper) &&
 511                        primitiveValueEquals(primVal1, lower)) ||
 512                       (primitiveValueEquals(primVal0, lower) &&
 513                        primitiveValueEquals(primVal1, upper)))) {
 514                     throw new RuntimeException(String.format("0x%X and 0x%X of 0x%016X not found in 0x%X and 0x%X",
 515                             upper, lower, expected,
 516                             (int)primitiveIntValue.invoke(primVal0),
 517                             (int)primitiveIntValue.invoke(primVal1)));
 518                 }
 519             } else {
 520                 if (!(primitiveValueEquals(primVal0, expected) ||
 521                       primitiveValueEquals(primVal1, expected))) {
 522                     throw new RuntimeException(String.format("0x%016X not found in 0x%016X or 0x%016X",
 523                             expected,
 524                             (long)primitiveLongValue.invoke(primVal0),
 525                             (long)primitiveLongValue.invoke(primVal1)));
 526                 }
 527             }
 528         } catch (IllegalAccessException|InvocationTargetException e) {
 529             throw new RuntimeException(e);
 530         }
 531     }
 532 
 533     static void assertDoubleIsInSlots(Object primVal0, Object primVal1, double expected) {
 534         assertLongIsInSlots(primVal0, primVal1, Double.doubleToRawLongBits(expected));
 535     }
 536 }