1 /*
   2  * Copyright (c) 2016 Red Hat Inc.
   3  *
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * This code is free software; you can redistribute it and/or modify it
   7  * under the terms of the GNU General Public License version 2 only, as
   8  * published by the Free Software Foundation.
   9  *
  10  * This code is distributed in the hope that it will be useful, but WITHOUT
  11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  13  * version 2 for more details (a copy is included in the LICENSE file that
  14  * accompanied this code).
  15  *
  16  * You should have received a copy of the GNU General Public License version
  17  * 2 along with this work; if not, write to the Free Software Foundation,
  18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  19  *
  20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  21  * or visit www.oracle.com if you need additional information or have any
  22  * questions.
  23  */
  24 
  25 /**
  26  *  @test
  27  *  @bug 4858370
  28  *  @summary JDWP: Memory Leak (global references not deleted after invokeMethod).
  29  *
  30  *  @author Severin Gehwolf <sgehwolf@redhat.com>
  31  *
  32  *  @library ..
  33  *  @run build TestScaffold VMConnection TargetListener TargetAdapter
  34  *  @run compile -g OomDebugTest.java
  35  *  @run shell OomDebugTestSetup.sh
  36  *  @run main OomDebugTest OomDebugTestTarget test1
  37  *  @run main OomDebugTest OomDebugTestTarget test2
  38  *  @run main OomDebugTest OomDebugTestTarget test3
  39  *  @run main OomDebugTest OomDebugTestTarget test4
  40  *  @run main OomDebugTest OomDebugTestTarget test5
  41  */
  42 import java.util.ArrayList;
  43 import java.util.Collections;
  44 import java.util.List;
  45 
  46 import com.sun.jdi.ArrayReference;
  47 import com.sun.jdi.ArrayType;
  48 import com.sun.jdi.ClassType;
  49 import com.sun.jdi.Field;
  50 import com.sun.jdi.InvocationException;
  51 import com.sun.jdi.Method;
  52 import com.sun.jdi.ObjectReference;
  53 import com.sun.jdi.ReferenceType;
  54 import com.sun.jdi.StackFrame;
  55 import com.sun.jdi.VMOutOfMemoryException;
  56 import com.sun.jdi.Value;
  57 import com.sun.jdi.event.BreakpointEvent;
  58 
  59 /***************** Target program **********************/
  60 
  61 class OomDebugTestTarget {
  62 
  63     OomDebugTestTarget() {
  64         System.out.println("DEBUG: invoked constructor");
  65     }
  66     static class FooCls {
  67         @SuppressWarnings("unused")
  68         private byte[] bytes = new byte[3000000];
  69     };
  70 
  71     FooCls fooCls = new FooCls();
  72     byte[] byteArray = new byte[0];
  73 
  74     void testMethod(FooCls foo) {
  75         System.out.println("DEBUG: invoked 'void testMethod(FooCls)', foo == " + foo);
  76     }
  77 
  78     void testPrimitive(byte[] foo) {
  79         System.out.println("DEBUG: invoked 'void testPrimitive(byte[])', foo == " + foo);
  80     }
  81 
  82     byte[] testPrimitiveArrRetval() {
  83         System.out.println("DEBUG: invoked 'byte[] testPrimitiveArrRetval()'");
  84         return new byte[3000000];
  85     }
  86 
  87     FooCls testFooClsRetval() {
  88         System.out.println("DEBUG: invoked 'FooCls testFooClsRetval()'");
  89         return new FooCls();
  90     }
  91 
  92     public void entry() {}
  93 
  94     public static void main(String[] args){
  95         System.out.println("DEBUG: OomDebugTestTarget.main");
  96         new OomDebugTestTarget().entry();
  97     }
  98 }
  99 
 100 /***************** Test program ************************/
 101 
 102 public class OomDebugTest extends TestScaffold {
 103 
 104     private static final int TOTAL_TESTS = 1;
 105     private ReferenceType targetClass;
 106     private ObjectReference thisObject;
 107     private int failedTests;
 108     private final String testMethodName;
 109 
 110     public OomDebugTest(String[] args) {
 111         super(args);
 112         if (args.length != 2) {
 113             throw new RuntimeException("Test failed unexpectedly.");
 114         }
 115         testMethodName = args[1];
 116     }
 117 
 118     @Override
 119     protected void runTests() throws Exception {
 120         try {
 121             /*
 122              * Get to the top of entry()
 123              * to determine targetClass and mainThread
 124              */
 125             BreakpointEvent bpe = startTo("OomDebugTestTarget", "entry", "()V");
 126             targetClass = bpe.location().declaringType();
 127 
 128             mainThread = bpe.thread();
 129 
 130             StackFrame frame = mainThread.frame(0);
 131             thisObject = frame.thisObject();
 132             java.lang.reflect.Method m = findTestMethod();
 133             m.invoke(this);
 134         } catch (NoSuchMethodException e) {
 135             e.printStackTrace();
 136             failure();
 137         } catch (SecurityException e) {
 138             e.printStackTrace();
 139             failure();
 140         }
 141     }
 142 
 143     private java.lang.reflect.Method findTestMethod()
 144             throws NoSuchMethodException, SecurityException {
 145         return OomDebugTest.class.getDeclaredMethod(testMethodName);
 146     }
 147 
 148     private void failure() {
 149         failedTests++;
 150     }
 151 
 152     /*
 153      * Test case: Object reference as method parameter.
 154      */
 155     @SuppressWarnings("unused") // called via reflection
 156     private void test1() throws Exception {
 157         System.out.println("DEBUG: ------------> Running " + testMethodName);
 158         try {
 159             Field field = targetClass.fieldByName("fooCls");
 160             ClassType clsType = (ClassType)field.type();
 161             Method constructor = getConstructorForClass(clsType);
 162             for (int i = 0; i < 15; i++) {
 163                 @SuppressWarnings({ "rawtypes", "unchecked" })
 164                 ObjectReference objRef = clsType.newInstance(mainThread,
 165                                                              constructor,
 166                                                              new ArrayList(0),
 167                                                              ObjectReference.INVOKE_NONVIRTUAL);
 168                 invoke("testMethod", "(LOomDebugTestTarget$FooCls;)V", objRef);
 169             }
 170         } catch (InvocationException e) {
 171             handleFailure(e);
 172         }
 173     }
 174 
 175     /*
 176      * Test case: Array reference as method parameter.
 177      */
 178     @SuppressWarnings("unused") // called via reflection
 179     private void test2() throws Exception {
 180         System.out.println("DEBUG: ------------> Running " + testMethodName);
 181         try {
 182             Field field = targetClass.fieldByName("byteArray");
 183             ArrayType arrType = (ArrayType)field.type();
 184 
 185             for (int i = 0; i < 15; i++) {
 186                 ArrayReference byteArrayVal = arrType.newInstance(3000000);
 187                 invoke("testPrimitive", "([B)V", byteArrayVal);
 188             }
 189         } catch (VMOutOfMemoryException e) {
 190             defaultHandleOOMFailure(e);
 191         }
 192     }
 193 
 194     /*
 195      * Test case: Array reference as return value.
 196      */
 197     @SuppressWarnings("unused") // called via reflection
 198     private void test3() throws Exception {
 199         System.out.println("DEBUG: ------------> Running " + testMethodName);
 200         try {
 201             for (int i = 0; i < 15; i++) {
 202                 invoke("testPrimitiveArrRetval",
 203                        "()[B",
 204                        Collections.EMPTY_LIST,
 205                        vm().mirrorOfVoid());
 206             }
 207         } catch (InvocationException e) {
 208             handleFailure(e);
 209         }
 210     }
 211 
 212     /*
 213      * Test case: Object reference as return value.
 214      */
 215     @SuppressWarnings("unused") // called via reflection
 216     private void test4() throws Exception {
 217         System.out.println("DEBUG: ------------> Running " + testMethodName);
 218         try {
 219             for (int i = 0; i < 15; i++) {
 220                 invoke("testFooClsRetval",
 221                        "()LOomDebugTestTarget$FooCls;",
 222                        Collections.EMPTY_LIST,
 223                        vm().mirrorOfVoid());
 224             }
 225         } catch (InvocationException e) {
 226             handleFailure(e);
 227         }
 228     }
 229 
 230     /*
 231      * Test case: Constructor
 232      */
 233     @SuppressWarnings({ "unused", "unchecked", "rawtypes" }) // called via reflection
 234     private void test5() throws Exception {
 235         System.out.println("DEBUG: ------------> Running " + testMethodName);
 236         try {
 237             ClassType type = (ClassType)thisObject.type();
 238             for (int i = 0; i < 15; i++) {
 239                 type.newInstance(mainThread,
 240                                  findMethod(targetClass, "<init>", "()V"),
 241                                  new ArrayList(0),
 242                                  ObjectReference.INVOKE_NONVIRTUAL);
 243             }
 244         } catch (InvocationException e) {
 245             handleFailure(e);
 246         }
 247     }
 248 
 249     private Method getConstructorForClass(ClassType clsType) {
 250         List<Method> methods = clsType.methodsByName("<init>");
 251         if (methods.size() != 1) {
 252             throw new RuntimeException("FAIL. Expected only one, the default, constructor");
 253         }
 254         return methods.get(0);
 255     }
 256 
 257     private void handleFailure(InvocationException e) {
 258         // There is no good way to see the OOME diagnostic message in the target since the
 259         // TestScaffold might throw an exception while trying to print the stack trace. I.e
 260         // it might get a a VMDisconnectedException before the stack trace printing finishes.
 261         System.err.println("FAILURE: InvocationException caused by OOM");
 262         defaultHandleOOMFailure(e);
 263     }
 264 
 265     private void defaultHandleOOMFailure(Exception e) {
 266         e.printStackTrace();
 267         failure();
 268     }
 269 
 270     @SuppressWarnings({ "rawtypes", "unchecked" })
 271     void invoke(String methodName, String methodSig, Value value)
 272             throws Exception {
 273         List args = new ArrayList(1);
 274         args.add(value);
 275         invoke(methodName, methodSig, args, value);
 276     }
 277 
 278     void invoke(String methodName,
 279                 String methodSig,
 280                 @SuppressWarnings("rawtypes") List args,
 281                 Value value) throws Exception {
 282         Method method = findMethod(targetClass, methodName, methodSig);
 283         if ( method == null) {
 284             failure("FAILED: Can't find method: "
 285                     + methodName  + " for class = " + targetClass);
 286             return;
 287         }
 288         invoke(method, args, value);
 289     }
 290 
 291     @SuppressWarnings({ "rawtypes", "unchecked" })
 292     void invoke(Method method, List args, Value value) throws Exception {
 293         thisObject.invokeMethod(mainThread, method, args, 0);
 294         System.out.println("DEBUG: Done invoking method via debugger.");
 295     }
 296 
 297     Value fieldValue(String fieldName) {
 298         Field field = targetClass.fieldByName(fieldName);
 299         return thisObject.getValue(field);
 300     }
 301 
 302     public static void main(String[] args) throws Exception {
 303         OomDebugTest oomTest = new OomDebugTest(args);
 304         oomTest.startTests();
 305         if (oomTest.failedTests > 0) {
 306             throw new RuntimeException(oomTest.failedTests
 307                                        + " of " + TOTAL_TESTS + " test(s) failed.");
 308         }
 309         System.out.println("All " + TOTAL_TESTS + " tests passed.");
 310     }
 311 
 312 }