Send class bytecode using chunks with limited size
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / impl / ClassLoadingUtils.java
1 /*
2  * Copyright 2000-2017 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.debugger.impl;
17
18 import com.intellij.debugger.engine.DebugProcess;
19 import com.intellij.debugger.engine.JVMNameUtil;
20 import com.intellij.debugger.engine.evaluation.EvaluateException;
21 import com.intellij.debugger.engine.evaluation.EvaluationContext;
22 import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
23 import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
24 import com.intellij.openapi.util.io.StreamUtil;
25 import com.intellij.rt.debugger.ImageSerializer;
26 import com.sun.jdi.*;
27 import org.jetbrains.annotations.Nullable;
28
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.List;
35
36 /**
37  * @author egor
38  */
39 public class ClassLoadingUtils {
40   private static final int BATCH_SIZE = 4096;
41   private ClassLoadingUtils() {}
42
43   public static ClassLoaderReference getClassLoader(EvaluationContext context, DebugProcess process) throws EvaluateException {
44     try {
45       // TODO [egor]: cache?
46       ClassType loaderClass = (ClassType)process.findClass(context, "java.net.URLClassLoader", context.getClassLoader());
47       Method ctorMethod = loaderClass.concreteMethodByName(JVMNameUtil.CONSTRUCTOR_NAME, "([Ljava/net/URL;Ljava/lang/ClassLoader;)V");
48       ClassLoaderReference reference = (ClassLoaderReference)process.newInstance(context, loaderClass, ctorMethod, Arrays
49         .asList(createURLArray(context), context.getClassLoader()));
50       DebuggerUtilsEx.keep(reference, context);
51       return reference;
52     }
53     catch (Exception e) {
54       throw new EvaluateException("Error creating evaluation class loader: " + e, e);
55     }
56   }
57
58   public static void defineClass(String name,
59                                  byte[] bytes,
60                                  EvaluationContext context,
61                                  DebugProcess process,
62                                  ClassLoaderReference classLoader) throws EvaluateException {
63     try {
64       VirtualMachineProxyImpl proxy = (VirtualMachineProxyImpl)process.getVirtualMachineProxy();
65       Method defineMethod =
66         ((ClassType)classLoader.referenceType()).concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;");
67       StringReference nameObj = proxy.mirrorOf(name);
68       DebuggerUtilsEx.keep(nameObj, context);
69       process.invokeMethod(context, classLoader, defineMethod,
70                            Arrays.asList(nameObj,
71                                          mirrorOf(bytes, context, process),
72                                          proxy.mirrorOf(0),
73                                          proxy.mirrorOf(bytes.length)));
74     }
75     catch (Exception e) {
76       throw new EvaluateException("Error during class " + name + " definition: " + e, e);
77     }
78   }
79
80   /**
81    * Finds and if necessary defines helper class
82    * May modify class loader in evaluationContext
83    */
84   @Nullable
85   public static ClassType getHelperClass(String name, EvaluationContext evaluationContext, DebugProcess process) throws EvaluateException {
86     // TODO [egor]: cache and load in boostrap class loader
87     try {
88       ClassLoaderReference classLoader = evaluationContext.getClassLoader();
89       return (ClassType)process.findClass(evaluationContext, name, classLoader);
90     } catch (EvaluateException e) {
91       Throwable cause = e.getCause();
92       if (cause instanceof InvocationException) {
93         if ("java.lang.ClassNotFoundException".equals(((InvocationException)cause).exception().type().name())) {
94           // need to define
95           ClassLoaderReference classLoader = getClassLoader(evaluationContext, process);
96           InputStream stream = ImageSerializer.class.getResourceAsStream("/" + name.replaceAll("[.]", "/") + ".class");
97           try {
98             if (stream == null) return null;
99             defineClass(name, StreamUtil.loadFromStream(stream), evaluationContext, process, classLoader);
100             ((EvaluationContextImpl)evaluationContext).setClassLoader(classLoader);
101             return (ClassType)process.findClass(evaluationContext, name, classLoader);
102           }
103           catch (IOException ioe) {
104             throw new EvaluateException("Unable to read " + name + " class bytes", ioe);
105           }
106           finally {
107             try {
108               if (stream != null) {
109                 stream.close();
110               }
111             }
112             catch (IOException ignored) {}
113           }
114         }
115       }
116       throw e;
117     }
118   }
119
120   private static ArrayReference createURLArray(EvaluationContext context)
121     throws EvaluateException, InvalidTypeException, ClassNotLoadedException {
122     DebugProcess process = context.getDebugProcess();
123     ArrayType arrayType = (ArrayType)process.findClass(context, "java.net.URL[]", context.getClassLoader());
124     ArrayReference arrayRef = arrayType.newInstance(1);
125     DebuggerUtilsEx.keep(arrayRef, context);
126     ClassType classType = (ClassType)process.findClass(context, "java.net.URL", context.getClassLoader());
127     VirtualMachineProxyImpl proxy = (VirtualMachineProxyImpl)process.getVirtualMachineProxy();
128     StringReference url = proxy.mirrorOf("file:a");
129     DebuggerUtilsEx.keep(url, context);
130     ObjectReference reference = process.newInstance(context,
131                                                     classType,
132                                                     classType.concreteMethodByName(JVMNameUtil.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
133                                                     Collections.singletonList(url));
134     DebuggerUtilsEx.keep(reference, context);
135     arrayRef.setValues(Collections.singletonList(reference));
136     return arrayRef;
137   }
138
139   private static ArrayReference mirrorOf(byte[] bytes, EvaluationContext context, DebugProcess process)
140     throws EvaluateException, InvalidTypeException, ClassNotLoadedException {
141     ArrayType arrayClass = (ArrayType)process.findClass(context, "byte[]", context.getClassLoader());
142     ArrayReference reference = process.newInstance(arrayClass, bytes.length);
143     DebuggerUtilsEx.keep(reference, context);
144     List<Value> mirrors = new ArrayList<>(bytes.length);
145     for (byte b : bytes) {
146       mirrors.add(((VirtualMachineProxyImpl)process.getVirtualMachineProxy()).mirrorOf(b));
147     }
148
149     int loaded = 0;
150     while (loaded < mirrors.size()) {
151       int chunkSize = Math.min(BATCH_SIZE, mirrors.size() - loaded);
152       reference.setValues(loaded, mirrors, loaded, chunkSize);
153       loaded += chunkSize;
154     }
155
156     return reference;
157   }
158 }