1 /*
   2  * Copyright (c) 2015, 2016 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
  27  * @summary Tests for locals and operands
  28  * @run testng LocalsAndOperands
  29  */
  30 
  31 import org.testng.annotations.*;
  32 import java.lang.StackWalker.StackFrame;
  33 import java.lang.reflect.*;
  34 import java.util.*;
  35 import java.util.stream.*;
  36 
  37 public class LocalsAndOperands {
  38     static final boolean debug = true;
  39 
  40     static Class<?> liveStackFrameClass;
  41     static Class<?> primitiveValueClass;
  42     static StackWalker extendedWalker;
  43     static Method getLocals;
  44     static Method getOperands;
  45     static Method getMonitors;
  46     static Method primitiveType;
  47 
  48     static {
  49         try {
  50             liveStackFrameClass = Class.forName("java.lang.LiveStackFrame");
  51             primitiveValueClass = Class.forName("java.lang.LiveStackFrame$PrimitiveValue");
  52 
  53             getLocals = liveStackFrameClass.getDeclaredMethod("getLocals");
  54             getLocals.setAccessible(true);
  55 
  56             getOperands = liveStackFrameClass.getDeclaredMethod("getStack");
  57             getOperands.setAccessible(true);
  58 
  59             getMonitors = liveStackFrameClass.getDeclaredMethod("getMonitors");
  60             getMonitors.setAccessible(true);
  61 
  62             primitiveType = primitiveValueClass.getDeclaredMethod("type");
  63             primitiveType.setAccessible(true);
  64 
  65             Method method = liveStackFrameClass.getMethod("getStackWalker");
  66             method.setAccessible(true);
  67             extendedWalker = (StackWalker) method.invoke(null);
  68         } catch (Throwable t) { throw new RuntimeException(t); }
  69     }
  70 
  71     /** Helper method to return a StackFrame's locals */
  72     static Object[] invokeGetLocals(StackFrame arg) {
  73         try {
  74             return (Object[]) getLocals.invoke(arg);
  75         } catch (Exception e) { throw new RuntimeException(e); }
  76     }
  77 
  78     /*****************
  79      * DataProviders *
  80      *****************/
  81 
  82     /** Calls testLocals() and provides LiveStackFrames for testLocals* methods */
  83     @DataProvider
  84     public static StackFrame[][] provider() {
  85         return new StackFrame[][] {
  86             new Tester().testLocals()
  87         };
  88     }
  89 
  90     /**
  91      * Calls testLocalsKeepAlive() and provides LiveStackFrames for testLocals* methods.
  92      * Local variables in testLocalsKeepAlive() are ensured to not become dead.
  93      */
  94     @DataProvider
  95     public static StackFrame[][] keepAliveProvider() {
  96         return new StackFrame[][] {
  97             new Tester().testLocalsKeepAlive()
  98         };
  99     }
 100 
 101     /**
 102      * Provides StackFrames from a StackWalker without the LOCALS_AND_OPERANDS
 103      * option.
 104      */
 105     @DataProvider
 106     public static StackFrame[][] noLocalsProvider() {
 107         // Use default StackWalker
 108         return new StackFrame[][] {
 109             new Tester(StackWalker.getInstance(), true).testLocals()
 110         };
 111     }
 112 
 113     /**
 114      * Calls testLocals() and provides LiveStackFrames for *all* called methods,
 115      * including test infrastructure (jtreg, testng, etc)
 116      *
 117      */
 118     @DataProvider
 119     public static StackFrame[][] unfilteredProvider() {
 120         return new StackFrame[][] {
 121             new Tester(extendedWalker, false).testLocals()
 122         };
 123     }
 124 
 125     /****************
 126      * Test methods *
 127      ****************/
 128 
 129     /**
 130      * Check for expected local values and types in the LiveStackFrame
 131      */
 132     @Test(dataProvider = "keepAliveProvider")
 133     public static void checkLocalValues(StackFrame... frames) {
 134         if (debug) {
 135             System.out.println("Running checkLocalValues");
 136             dumpStackWithLocals(frames);
 137         }
 138         Arrays.stream(frames).filter(f -> f.getMethodName()
 139                                            .equals("testLocalsKeepAlive"))
 140                                            .forEach(
 141             f -> {
 142                 Object[] locals = invokeGetLocals(f);
 143                 for (int i = 0; i < locals.length; i++) {
 144                     // Value
 145                     String expected = Tester.LOCAL_VALUES[i];
 146                     Object observed = locals[i];
 147                     if (expected != null /* skip nulls in golden values */ &&
 148                             !expected.equals(observed.toString())) {
 149                         System.err.println("Local value mismatch:");
 150                         if (!debug) { dumpStackWithLocals(frames); }
 151                         throw new RuntimeException("local " + i + " value is " +
 152                                 observed + ", expected " + expected);
 153                     }
 154 
 155                     // Type
 156                     expected = Tester.LOCAL_TYPES[i];
 157                     observed = type(locals[i]);
 158                     if (expected != null /* skip nulls in golden values */ &&
 159                             !expected.equals(observed)) {
 160                         System.err.println("Local type mismatch:");
 161                         if (!debug) { dumpStackWithLocals(frames); }
 162                         throw new RuntimeException("local " + i + " type is " +
 163                                 observed + ", expected " + expected);
 164                     }
 165                 }
 166             }
 167         );
 168     }
 169 
 170     /**
 171      * Basic sanity check for locals and operands
 172      */
 173     @Test(dataProvider = "provider")
 174     public static void sanityCheck(StackFrame... frames) {
 175         if (debug) {
 176             System.out.println("Running sanityCheck");
 177         }
 178         try {
 179             Stream<StackFrame> stream = Arrays.stream(frames);
 180             if (debug) {
 181                 stream.forEach(LocalsAndOperands::printLocals);
 182             } else {
 183                 System.out.println(stream.count() + " frames");
 184             }
 185         } catch (Throwable t) {
 186             dumpStackWithLocals(frames);
 187             throw t;
 188         }
 189     }
 190 
 191     /**
 192      * Sanity check for locals and operands, including testng/jtreg frames
 193      */
 194     @Test(dataProvider = "unfilteredProvider")
 195     public static void unfilteredSanityCheck(StackFrame... frames) {
 196         if (debug) {
 197             System.out.println("Running unfilteredSanityCheck");
 198         }
 199         try {
 200             Stream<StackFrame> stream = Arrays.stream(frames);
 201             if (debug) {
 202                 stream.forEach(f -> { System.out.println(f + ": " +
 203                         invokeGetLocals(f).length + " locals"); } );
 204             } else {
 205                 System.out.println(stream.count() + " frames");
 206             }
 207         } catch (Throwable t) {
 208             dumpStackWithLocals(frames);
 209             throw t;
 210         }
 211     }
 212 
 213     /**
 214      * Test that LiveStackFrames are not provided with the default StackWalker
 215      * options.
 216      */
 217     @Test(dataProvider = "noLocalsProvider")
 218     public static void withoutLocalsAndOperands(StackFrame... frames) {
 219         for (StackFrame frame : frames) {
 220             if (liveStackFrameClass.isInstance(frame)) {
 221                 throw new RuntimeException("should not be LiveStackFrame");
 222             }
 223         }
 224     }
 225 
 226     static class Tester {
 227         private StackWalker walker;
 228         private boolean filter = true; // Filter out testng/jtreg/etc frames?
 229 
 230         Tester() {
 231             this.walker = extendedWalker;
 232         }
 233 
 234         Tester(StackWalker walker, boolean filter) {
 235             this.walker = walker;
 236             this.filter = filter;
 237         }
 238 
 239         /**
 240          * Perform stackwalk without keeping local variables alive and return an
 241          * array of the collected StackFrames
 242          */
 243         private synchronized StackFrame[] testLocals() {
 244             // Unused local variables will become dead
 245             int x = 10;
 246             char c = 'z';
 247             String hi = "himom";
 248             long l = 1000000L;
 249             double d =  3.1415926;
 250 
 251             if (filter) {
 252                 return walker.walk(s -> s.filter(f -> TEST_METHODS.contains(f
 253                         .getMethodName())).collect(Collectors.toList()))
 254                         .toArray(new StackFrame[0]);
 255             } else {
 256                 return walker.walk(s -> s.collect(Collectors.toList()))
 257                         .toArray(new StackFrame[0]);
 258             }
 259         }
 260 
 261         /**
 262          * Perform stackwalk, keeping local variables alive, and return a list of
 263          * the collected StackFrames
 264          */
 265         private synchronized StackFrame[] testLocalsKeepAlive() {
 266             int x = 10;
 267             char c = 'z';
 268             String hi = "himom";
 269             long l = 1000000L;
 270             double d =  3.1415926;
 271 
 272             List<StackWalker.StackFrame> frames;
 273             if (filter) {
 274                 frames = walker.walk(s -> s.filter(f -> TEST_METHODS.contains(f
 275                         .getMethodName())).collect(Collectors.toList()));
 276             } else {
 277                 frames = walker.walk(s -> s.collect(Collectors.toList()));
 278             }
 279 
 280             // Use local variables so they stay alive
 281             System.out.println("Stayin' alive: "+x+" "+c+" "+hi+" "+l+" "+d);
 282             return frames.toArray(new StackFrame[0]); // FIXME: convert to Array here
 283         }
 284 
 285         // Expected values for locals in testLocals() & testLocalsKeepAlive()
 286         // TODO: use real values instead of Strings, rebuild doubles & floats, etc
 287         private final static String[] LOCAL_VALUES = new String[] {
 288             null, // skip, LocalsAndOperands$Tester@XXX identity is different each run
 289             "10",
 290             "122",
 291             "himom",
 292             "0",
 293             null, // skip, fix in 8156073
 294             null, // skip, fix in 8156073
 295             null, // skip, fix in 8156073
 296             "0"
 297         };
 298 
 299         // Expected types for locals in testLocals() & testLocalsKeepAlive()
 300         // TODO: use real types
 301         private final static String[] LOCAL_TYPES = new String[] {
 302             null, // skip
 303             "I",
 304             "I",
 305             "java.lang.String",
 306             "I",
 307             "I",
 308             "I",
 309             "I",
 310             "I"
 311         };
 312 
 313         final static Map NUM_LOCALS = Map.of("testLocals", 8,
 314                                              "testLocalsKeepAlive",
 315                                              LOCAL_VALUES.length);
 316         private final static Collection<String> TEST_METHODS = NUM_LOCALS.keySet();
 317     }
 318 
 319     /**
 320      * Print stack trace with locals
 321      */
 322     public static void dumpStackWithLocals(StackFrame...frames) {
 323         Arrays.stream(frames).forEach(LocalsAndOperands::printLocals);
 324     }
 325 
 326     /**
 327      * Print the StackFrame and an indexed list of its locals
 328      */
 329     public static void printLocals(StackWalker.StackFrame frame) {
 330         try {
 331             System.out.println(frame);
 332             Object[] locals = (Object[]) getLocals.invoke(frame);
 333             for (int i = 0; i < locals.length; i++) {
 334                 System.out.format("  local %d: %s type %s\n", i, locals[i], type(locals[i]));
 335             }
 336 
 337             Object[] operands = (Object[]) getOperands.invoke(frame);
 338             for (int i = 0; i < operands.length; i++) {
 339                 System.out.format("  operand %d: %s type %s%n", i, operands[i],
 340                                   type(operands[i]));
 341             }
 342 
 343             Object[] monitors = (Object[]) getMonitors.invoke(frame);
 344             for (int i = 0; i < monitors.length; i++) {
 345                 System.out.format("  monitor %d: %s%n", i, monitors[i]);
 346             }
 347         } catch (Exception e) { throw new RuntimeException(e); }
 348     }
 349 
 350     private static String type(Object o) {
 351         try {
 352             if (o == null) {
 353                 return "null";
 354             } else if (primitiveValueClass.isInstance(o)) {
 355                 char c = (char)primitiveType.invoke(o);
 356                 return String.valueOf(c);
 357             } else {
 358                 return o.getClass().getName();
 359             }
 360         } catch(Exception e) { throw new RuntimeException(e); }
 361     }
 362 }