author | ohair |
Wed, 06 Apr 2011 22:06:11 -0700 | |
changeset 9035 | 1255eb81cc2f |
parent 8822 | 8145ab9f5f86 |
child 9050 | 26c2c1de1631 |
permissions | -rw-r--r-- |
7558 | 1 |
/* |
9035
1255eb81cc2f
7033660: Update copyright year to 2011 on any files changed in 2011
ohair
parents:
8822
diff
changeset
|
2 |
* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. |
7558 | 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. Oracle designates this |
|
8 |
* particular file as subject to the "Classpath" exception as provided |
|
9 |
* by Oracle in the LICENSE file that accompanied this code. |
|
10 |
* |
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that |
|
15 |
* accompanied this code). |
|
16 |
* |
|
17 |
* You should have received a copy of the GNU General Public License version |
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation, |
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 |
* |
|
21 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 |
* or visit www.oracle.com if you need additional information or have any |
|
23 |
* questions. |
|
24 |
*/ |
|
25 |
||
26 |
package indify; |
|
27 |
||
28 |
import java.util.*; |
|
29 |
import java.io.*; |
|
30 |
import java.lang.reflect.Modifier; |
|
31 |
import java.util.regex.*; |
|
32 |
||
33 |
/** |
|
34 |
* Transform one or more class files to incorporate JSR 292 features, |
|
35 |
* such as {@code invokedynamic}. |
|
36 |
* <p> |
|
37 |
* This is a standalone program in a single source file. |
|
38 |
* In this form, it may be useful for test harnesses, small experiments, and javadoc examples. |
|
39 |
* Copies of this file may show up in multiple locations for standalone usage. |
|
40 |
* The primary maintained location of this file is as follows: |
|
41 |
* <a href="http://kenai.com/projects/ninja/sources/indify-repo/content/src/indify/Indify.java"> |
|
42 |
* http://kenai.com/projects/ninja/sources/indify-repo/content/src/indify/Indify.java</a> |
|
43 |
* <p> |
|
44 |
* Static private methods named MH_x and MT_x (where x is arbitrary) |
|
45 |
* must be stereotyped generators of MethodHandle and MethodType |
|
46 |
* constants. All calls to them are transformed to {@code CONSTANT_MethodHandle} |
|
47 |
* and {@code CONSTANT_MethodType} "ldc" instructions. |
|
48 |
* The stereotyped code must create method types by calls to {@code methodType} or |
|
49 |
* {@code fromMethodDescriptorString}. The "lookup" argument must be created |
|
8822
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
50 |
* by calls to {@code java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}. |
7558 | 51 |
* The class and string arguments must be constant. |
8822
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
52 |
* The following methods of {@code java.lang.invoke.MethodHandle.Lookup Lookup} are |
7558 | 53 |
* allowed for method handle creation: {@code findStatic}, {@code findVirtual}, |
54 |
* {@code findConstructor}, {@code findSpecial}, |
|
55 |
* {@code findGetter}, {@code findSetter}, |
|
56 |
* {@code findStaticGetter}, or {@code findStaticSetter}. |
|
57 |
* The call to one of these methods must be followed immediately |
|
58 |
* by an {@code areturn} instruction. |
|
59 |
* The net result of the call to the MH_x or MT_x method must be |
|
60 |
* the creation of a constant method handle. Thus, replacing calls |
|
61 |
* to MH_x or MT_x methods by {@code ldc} instructions should leave |
|
62 |
* the meaning of the program unchanged. |
|
63 |
* <p> |
|
64 |
* Static private methods named INDY_x must be stereotyped generators |
|
65 |
* of {@code invokedynamic} call sites. |
|
66 |
* All calls to them must be immediately followed by |
|
67 |
* {@code invokeExact} calls. |
|
68 |
* All such pairs of calls are transformed to {@code invokedynamic} |
|
69 |
* instructions. Each INDY_x method must begin with a call to a |
|
70 |
* MH_x method, which is taken to be its bootstrap method. |
|
71 |
* The method must be immediately invoked (via {@code invokeGeneric} |
|
72 |
* on constant lookup, name, and type arguments. An object array of |
|
73 |
* constants may also be appended to the {@code invokeGeneric call}. |
|
74 |
* This call must be cast to {@code CallSite}, and the result must be |
|
75 |
* immediately followed by a call to {@code dynamicInvoker}, with the |
|
76 |
* resulting method handle returned. |
|
77 |
* <p> |
|
78 |
* The net result of all of these actions is equivalent to the JVM's |
|
79 |
* execution of an {@code invokedynamic} instruction in the unlinked state. |
|
80 |
* Running this code once should produce the same results as running |
|
81 |
* the corresponding {@code invokedynamic} instruction. |
|
82 |
* In order to model the caching behavior, the code of an INDY_x |
|
83 |
* method is allowed to begin with getstatic, aaload, and if_acmpne |
|
84 |
* instructions which load a static method handle value and return it |
|
85 |
* if the value is non-null. |
|
86 |
* <p> |
|
87 |
* Example usage: |
|
88 |
* <blockquote><pre> |
|
89 |
$ JAVA_HOME=(some recent OpenJDK 7 build) |
|
90 |
$ ant |
|
91 |
$ $JAVA_HOME/bin/java -cp build/classes indify.Indify --overwrite --dest build/testout build/classes/indify/Example.class |
|
92 |
$ $JAVA_HOME/bin/java -XX:+UnlockExperimentalVMOptions -XX:+EnableInvokeDynamic -cp build/classes indify.Example |
|
93 |
MT = (java.lang.Object)java.lang.Object |
|
94 |
MH = adder(int,int)java.lang.Integer |
|
95 |
adder(1,2) = 3 |
|
96 |
calling indy: 42 |
|
97 |
$ $JAVA_HOME/bin/java -XX:+UnlockExperimentalVMOptions -XX:+EnableInvokeDynamic -cp build/testout indify.Example |
|
98 |
(same output as above) |
|
99 |
* </pre></blockquote> |
|
100 |
* <p> |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
101 |
* Before OpenJDK build b123, the format of {@code CONSTANT_InvokeDynamic} is in transition, |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
102 |
* and the switch {@code --transitionalJSR292=yes} is recommended. |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
103 |
* It is turned <em>off</em> by default, but users of earlier builds may need to turn it on. |
7558 | 104 |
* <p> |
105 |
* A version of this transformation built on top of <a href="http://asm.ow2.org/">http://asm.ow2.org/</a> would be welcome. |
|
106 |
* @author John Rose |
|
107 |
*/ |
|
108 |
public class Indify { |
|
109 |
public static void main(String... av) throws IOException { |
|
110 |
new Indify().run(av); |
|
111 |
} |
|
112 |
||
113 |
public File dest; |
|
114 |
public String[] classpath = {"."}; |
|
115 |
public boolean keepgoing = false; |
|
116 |
public boolean expandProperties = false; |
|
117 |
public boolean overwrite = false; |
|
118 |
public boolean quiet = false; |
|
119 |
public boolean verbose = false; |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
120 |
public boolean transitionalJSR292 = false; // final version is distributed |
7558 | 121 |
public boolean all = false; |
122 |
public int verifySpecifierCount = -1; |
|
123 |
||
124 |
public void run(String... av) throws IOException { |
|
125 |
List<String> avl = new ArrayList<>(Arrays.asList(av)); |
|
126 |
parseOptions(avl); |
|
127 |
if (avl.isEmpty()) |
|
128 |
throw new IllegalArgumentException("Usage: indify [--dest dir] [option...] file..."); |
|
129 |
if ("--java".equals(avl.get(0))) { |
|
130 |
avl.remove(0); |
|
131 |
try { |
|
132 |
runApplication(avl.toArray(new String[0])); |
|
133 |
} catch (Exception ex) { |
|
134 |
if (ex instanceof RuntimeException) throw (RuntimeException) ex; |
|
135 |
throw new RuntimeException(ex); |
|
136 |
} |
|
137 |
return; |
|
138 |
} |
|
139 |
Exception err = null; |
|
140 |
for (String a : avl) { |
|
141 |
try { |
|
142 |
indify(a); |
|
143 |
} catch (Exception ex) { |
|
144 |
if (err == null) err = ex; |
|
145 |
System.err.println("failure on "+a); |
|
146 |
if (!keepgoing) break; |
|
147 |
} |
|
148 |
} |
|
149 |
if (err != null) { |
|
150 |
if (err instanceof IOException) throw (IOException) err; |
|
151 |
throw (RuntimeException) err; |
|
152 |
} |
|
153 |
} |
|
154 |
||
155 |
/** Execute the given application under a class loader which indifies all application classes. */ |
|
156 |
public void runApplication(String... av) throws Exception { |
|
157 |
List<String> avl = new ArrayList<>(Arrays.asList(av)); |
|
158 |
String mainClassName = avl.remove(0); |
|
159 |
av = avl.toArray(new String[0]); |
|
160 |
Class<?> mainClass = Class.forName(mainClassName, true, makeClassLoader()); |
|
161 |
java.lang.reflect.Method main = mainClass.getMethod("main", String[].class); |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
162 |
try { main.setAccessible(true); } catch (SecurityException ex) { } |
7558 | 163 |
main.invoke(null, (Object) av); |
164 |
} |
|
165 |
||
166 |
public void parseOptions(List<String> av) throws IOException { |
|
167 |
for (; !av.isEmpty(); av.remove(0)) { |
|
168 |
String a = av.get(0); |
|
169 |
if (a.startsWith("-")) { |
|
170 |
String a2 = null; |
|
171 |
int eq = a.indexOf('='); |
|
172 |
if (eq > 0) { |
|
173 |
a2 = maybeExpandProperties(a.substring(eq+1)); |
|
174 |
a = a.substring(0, eq+1); |
|
175 |
} |
|
176 |
switch (a) { |
|
177 |
case "--java": |
|
178 |
return; // keep this argument |
|
179 |
case "-d": case "--dest": case "-d=": case "--dest=": |
|
180 |
dest = new File(a2 != null ? a2 : maybeExpandProperties(av.remove(1))); |
|
181 |
break; |
|
182 |
case "-cp": case "--classpath": |
|
183 |
classpath = maybeExpandProperties(av.remove(1)).split("["+File.pathSeparatorChar+"]"); |
|
184 |
break; |
|
185 |
case "-k": case "--keepgoing": case "--keepgoing=": |
|
186 |
keepgoing = booleanOption(a2); // print errors but keep going |
|
187 |
break; |
|
188 |
case "--expand-properties": case "--expand-properties=": |
|
189 |
expandProperties = booleanOption(a2); // expand property references in subsequent arguments |
|
190 |
break; |
|
191 |
case "--verify-specifier-count": case "--verify-specifier-count=": |
|
192 |
verifySpecifierCount = Integer.valueOf(a2); |
|
193 |
break; |
|
194 |
case "--overwrite": case "--overwrite=": |
|
195 |
overwrite = booleanOption(a2); // overwrite output files |
|
196 |
break; |
|
197 |
case "--all": case "--all=": |
|
198 |
all = booleanOption(a2); // copy all classes, even if no patterns |
|
199 |
break; |
|
200 |
case "-q": case "--quiet": case "--quiet=": |
|
201 |
quiet = booleanOption(a2); // less output |
|
202 |
break; |
|
203 |
case "-v": case "--verbose": case "--verbose=": |
|
204 |
verbose = booleanOption(a2); // more output |
|
205 |
break; |
|
206 |
case "--transitionalJSR292": case "--transitionalJSR292=": |
|
207 |
transitionalJSR292 = booleanOption(a2); // use older invokedynamic format |
|
208 |
break; |
|
209 |
default: |
|
210 |
throw new IllegalArgumentException("unrecognized flag: "+a); |
|
211 |
} |
|
212 |
continue; |
|
213 |
} else { |
|
214 |
break; |
|
215 |
} |
|
216 |
} |
|
217 |
if (dest == null && !overwrite) |
|
218 |
throw new RuntimeException("no output specified; need --dest d or --overwrite"); |
|
219 |
if (expandProperties) { |
|
220 |
for (int i = 0; i < av.size(); i++) |
|
221 |
av.set(i, maybeExpandProperties(av.get(i))); |
|
222 |
} |
|
223 |
} |
|
224 |
||
225 |
private boolean booleanOption(String s) { |
|
226 |
if (s == null) return true; |
|
227 |
switch (s) { |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
228 |
case "true": case "yes": case "on": case "1": return true; |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
229 |
case "false": case "no": case "off": case "0": return false; |
7558 | 230 |
} |
231 |
throw new IllegalArgumentException("unrecognized boolean flag="+s); |
|
232 |
} |
|
233 |
||
234 |
private String maybeExpandProperties(String s) { |
|
235 |
if (!expandProperties) return s; |
|
236 |
Set<String> propsDone = new HashSet<>(); |
|
237 |
while (s.contains("${")) { |
|
238 |
int lbrk = s.indexOf("${"); |
|
239 |
int rbrk = s.indexOf('}', lbrk); |
|
240 |
if (rbrk < 0) break; |
|
241 |
String prop = s.substring(lbrk+2, rbrk); |
|
242 |
if (!propsDone.add(prop)) break; |
|
243 |
String value = System.getProperty(prop); |
|
244 |
if (verbose) System.err.println("expanding ${"+prop+"} => "+value); |
|
245 |
if (value == null) break; |
|
246 |
s = s.substring(0, lbrk) + value + s.substring(rbrk+1); |
|
247 |
} |
|
248 |
return s; |
|
249 |
} |
|
250 |
||
251 |
public void indify(String a) throws IOException { |
|
252 |
File f = new File(a); |
|
253 |
String fn = f.getName(); |
|
254 |
if (fn.endsWith(".class") && f.isFile()) |
|
255 |
indifyFile(f, dest); |
|
256 |
else if (fn.endsWith(".jar") && f.isFile()) |
|
257 |
indifyJar(f, dest); |
|
258 |
else if (f.isDirectory()) |
|
259 |
indifyTree(f, dest); |
|
260 |
else if (!keepgoing) |
|
261 |
throw new RuntimeException("unrecognized file: "+a); |
|
262 |
} |
|
263 |
||
264 |
private void ensureDirectory(File dir) { |
|
265 |
if (dir.mkdirs() && !quiet) |
|
266 |
System.err.println("created "+dir); |
|
267 |
} |
|
268 |
||
269 |
public void indifyFile(File f, File dest) throws IOException { |
|
270 |
if (verbose) System.err.println("reading "+f); |
|
271 |
ClassFile cf = new ClassFile(f); |
|
272 |
Logic logic = new Logic(cf); |
|
273 |
boolean changed = logic.transform(); |
|
274 |
logic.reportPatternMethods(quiet, keepgoing); |
|
275 |
if (changed || all) { |
|
276 |
File outfile; |
|
277 |
if (dest != null) { |
|
278 |
ensureDirectory(dest); |
|
279 |
outfile = classPathFile(dest, cf.nameString()); |
|
280 |
} else { |
|
281 |
outfile = f; // overwrite input file, no matter where it is |
|
282 |
} |
|
283 |
cf.writeTo(outfile); |
|
284 |
if (!quiet) System.err.println("wrote "+outfile); |
|
285 |
} |
|
286 |
} |
|
287 |
||
288 |
File classPathFile(File pathDir, String className) { |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
289 |
String qualname = className.replace('.','/')+".class"; |
7558 | 290 |
qualname = qualname.replace('/', File.separatorChar); |
291 |
return new File(pathDir, qualname); |
|
292 |
} |
|
293 |
||
294 |
public void indifyJar(File f, Object dest) throws IOException { |
|
295 |
throw new UnsupportedOperationException("Not yet implemented"); |
|
296 |
} |
|
297 |
||
298 |
public void indifyTree(File f, File dest) throws IOException { |
|
299 |
if (verbose) System.err.println("reading directory: "+f); |
|
300 |
for (File f2 : f.listFiles(new FilenameFilter() { |
|
301 |
public boolean accept(File dir, String name) { |
|
302 |
if (name.endsWith(".class")) return true; |
|
303 |
if (name.contains(".")) return false; |
|
304 |
// return true if it might be a package name: |
|
305 |
return Character.isJavaIdentifierStart(name.charAt(0)); |
|
306 |
}})) { |
|
307 |
if (f2.getName().endsWith(".class")) |
|
308 |
indifyFile(f2, dest); |
|
309 |
else if (f2.isDirectory()) |
|
310 |
indifyTree(f2, dest); |
|
311 |
} |
|
312 |
} |
|
313 |
||
314 |
public ClassLoader makeClassLoader() { |
|
315 |
return new Loader(); |
|
316 |
} |
|
317 |
private class Loader extends ClassLoader { |
|
318 |
Loader() { |
|
319 |
this(Indify.class.getClassLoader()); |
|
320 |
} |
|
321 |
Loader(ClassLoader parent) { |
|
322 |
super(parent); |
|
323 |
} |
|
324 |
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { |
|
325 |
File f = findClassInPath(name); |
|
326 |
if (f != null) { |
|
327 |
try { |
|
328 |
Class<?> c = transformAndLoadClass(f); |
|
329 |
if (c != null) { |
|
330 |
if (resolve) resolveClass(c); |
|
331 |
return c; |
|
332 |
} |
|
333 |
} catch (Exception ex) { |
|
334 |
if (ex instanceof IllegalArgumentException) |
|
335 |
// pass error from reportPatternMethods |
|
336 |
throw (IllegalArgumentException) ex; |
|
337 |
} |
|
338 |
} |
|
339 |
return super.loadClass(name, resolve); |
|
340 |
} |
|
341 |
private File findClassInPath(String name) { |
|
342 |
for (String s : classpath) { |
|
343 |
File f = classPathFile(new File(s), name); |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
344 |
//System.out.println("Checking for "+f); |
7558 | 345 |
if (f.exists() && f.canRead()) { |
346 |
return f; |
|
347 |
} |
|
348 |
} |
|
349 |
return null; |
|
350 |
} |
|
351 |
protected Class<?> findClass(String name) throws ClassNotFoundException { |
|
352 |
try { |
|
8822
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
353 |
File f = findClassInPath(name); |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
354 |
if (f != null) { |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
355 |
Class<?> c = transformAndLoadClass(f); |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
356 |
if (c != null) return c; |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
357 |
} |
7558 | 358 |
} catch (IOException ex) { |
359 |
throw new ClassNotFoundException("IO error", ex); |
|
360 |
} |
|
8822
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
361 |
throw new ClassNotFoundException(); |
7558 | 362 |
} |
363 |
private Class<?> transformAndLoadClass(File f) throws ClassNotFoundException, IOException { |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
364 |
if (verbose) System.err.println("Loading class from "+f); |
7558 | 365 |
ClassFile cf = new ClassFile(f); |
366 |
Logic logic = new Logic(cf); |
|
367 |
boolean changed = logic.transform(); |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
368 |
if (verbose && !changed) System.err.println("(no change)"); |
7558 | 369 |
logic.reportPatternMethods(!verbose, keepgoing); |
370 |
byte[] bytes = cf.toByteArray(); |
|
371 |
return defineClass(null, bytes, 0, bytes.length); |
|
372 |
} |
|
373 |
} |
|
374 |
||
375 |
private class Logic { |
|
376 |
// Indify logic, per se. |
|
377 |
ClassFile cf; |
|
378 |
final char[] poolMarks; |
|
379 |
final Map<Method,Constant> constants = new HashMap<>(); |
|
380 |
final Map<Method,String> indySignatures = new HashMap<>(); |
|
381 |
Logic(ClassFile cf) { |
|
382 |
this.cf = cf; |
|
383 |
poolMarks = new char[cf.pool.size()]; |
|
384 |
} |
|
385 |
boolean transform() { |
|
386 |
if (!initializeMarks()) return false; |
|
387 |
if (!findPatternMethods()) return false; |
|
388 |
Pool pool = cf.pool; |
|
389 |
//for (Constant c : cp) System.out.println(" # "+c); |
|
390 |
for (Method m : cf.methods) { |
|
391 |
if (constants.containsKey(m)) continue; // don't bother |
|
392 |
// Transform references. |
|
393 |
int blab = 0; |
|
394 |
for (Instruction i = m.instructions(); i != null; i = i.next()) { |
|
395 |
if (i.bc != opc_invokestatic) continue; |
|
396 |
int methi = i.u2At(1); |
|
397 |
if (poolMarks[methi] == 0) continue; |
|
398 |
Short[] ref = pool.getMemberRef((short)methi); |
|
399 |
Method conm = findMember(cf.methods, ref[1], ref[2]); |
|
400 |
if (conm == null) continue; |
|
401 |
Constant con = constants.get(conm); |
|
402 |
if (con == null) continue; |
|
403 |
if (blab++ == 0 && !quiet) |
|
404 |
System.err.println("patching "+cf.nameString()+"."+m); |
|
405 |
//if (blab == 1) { for (Instruction j = m.instructions(); j != null; j = j.next()) System.out.println(" |"+j); } |
|
406 |
if (con.tag == CONSTANT_InvokeDynamic || |
|
407 |
con.tag == CONSTANT_InvokeDynamic_17) { |
|
408 |
// need to patch the following instruction too, |
|
409 |
// but there are usually intervening argument pushes too |
|
410 |
Instruction i2 = findPop(i); |
|
411 |
Short[] ref2 = null; |
|
412 |
short ref2i = 0; |
|
413 |
if (i2 != null && i2.bc == opc_invokevirtual && |
|
414 |
poolMarks[(char)(ref2i = (short) i2.u2At(1))] == 'D') |
|
415 |
ref2 = pool.getMemberRef(ref2i); |
|
416 |
if (ref2 == null || !"invokeExact".equals(pool.getString(ref2[1]))) { |
|
417 |
System.err.println(m+": failed to create invokedynamic at "+i.pc); |
|
418 |
continue; |
|
419 |
} |
|
420 |
String invType = pool.getString(ref2[2]); |
|
421 |
String bsmType = indySignatures.get(conm); |
|
422 |
if (!invType.equals(bsmType)) { |
|
423 |
System.err.println(m+": warning: "+conm+" call type and local invoke type differ: " |
|
424 |
+bsmType+", "+invType); |
|
425 |
} |
|
426 |
assert(i.len == 3 || i2.len == 3); |
|
427 |
if (!quiet) System.err.println(i+" "+conm+";...; "+i2+" => invokedynamic "+con); |
|
428 |
int start = i.pc + 3, end = i2.pc; |
|
429 |
System.arraycopy(i.codeBase, start, i.codeBase, i.pc, end-start); |
|
430 |
i.forceNext(0); // force revisit of new instruction |
|
431 |
i2.u1AtPut(-3, opc_invokedynamic); |
|
432 |
i2.u2AtPut(-2, con.index); |
|
433 |
i2.u2AtPut(0, (short)0); |
|
434 |
i2.u1AtPut(2, opc_nop); |
|
435 |
//System.out.println(new Instruction(i.codeBase, i2.pc-3)); |
|
436 |
} else { |
|
437 |
if (!quiet) System.err.println(i+" "+conm+" => ldc "+con); |
|
438 |
assert(i.len == 3); |
|
439 |
i.u1AtPut(0, opc_ldc_w); |
|
440 |
i.u2AtPut(1, con.index); |
|
441 |
} |
|
442 |
} |
|
443 |
//if (blab >= 1) { for (Instruction j = m.instructions(); j != null; j = j.next()) System.out.println(" |"+j); } |
|
444 |
} |
|
445 |
cf.methods.removeAll(constants.keySet()); |
|
446 |
return true; |
|
447 |
} |
|
448 |
||
449 |
// Scan forward from the instruction to find where the stack p |
|
450 |
// below the current sp at the instruction. |
|
451 |
Instruction findPop(Instruction i) { |
|
452 |
//System.out.println("findPop from "+i); |
|
453 |
Pool pool = cf.pool; |
|
454 |
JVMState jvm = new JVMState(); |
|
455 |
decode: |
|
456 |
for (i = i.clone().next(); i != null; i = i.next()) { |
|
457 |
String pops = INSTRUCTION_POPS[i.bc]; |
|
458 |
//System.out.println(" "+i+" "+jvm.stack+" : "+pops.replace("$", " => ")); |
|
459 |
if (pops == null) break; |
|
460 |
if (jvm.stackMotion(i.bc)) continue decode; |
|
461 |
if (pops.indexOf('Q') >= 0) { |
|
462 |
Short[] ref = pool.getMemberRef((short) i.u2At(1)); |
|
463 |
String type = simplifyType(pool.getString(CONSTANT_Utf8, ref[2])); |
|
464 |
switch (i.bc) { |
|
465 |
case opc_getstatic: |
|
466 |
case opc_getfield: |
|
467 |
case opc_putstatic: |
|
468 |
case opc_putfield: |
|
469 |
pops = pops.replace("Q", type); |
|
470 |
break; |
|
471 |
default: |
|
472 |
if (!type.startsWith("(")) |
|
473 |
throw new InternalError(i.toString()); |
|
474 |
pops = pops.replace("Q$Q", type.substring(1).replace(")","$")); |
|
475 |
break; |
|
476 |
} |
|
477 |
//System.out.println("special type: "+type+" => "+pops); |
|
478 |
} |
|
479 |
int npops = pops.indexOf('$'); |
|
480 |
if (npops < 0) throw new InternalError(); |
|
481 |
if (npops > jvm.sp()) return i; |
|
482 |
List<Object> args = jvm.args(npops); |
|
483 |
int k = 0; |
|
484 |
for (Object x : args) { |
|
485 |
char have = (Character) x; |
|
486 |
char want = pops.charAt(k++); |
|
487 |
if (have == 'X' || want == 'X') continue; |
|
488 |
if (have != want) break decode; |
|
489 |
} |
|
490 |
if (pops.charAt(k++) != '$') break decode; |
|
491 |
args.clear(); |
|
492 |
while (k < pops.length()) |
|
493 |
args.add(pops.charAt(k++)); |
|
494 |
} |
|
495 |
System.err.println("*** bailout on jvm: "+jvm.stack+" "+i); |
|
496 |
return null; |
|
497 |
} |
|
498 |
||
499 |
boolean findPatternMethods() { |
|
500 |
boolean found = false; |
|
501 |
for (char mark : "THI".toCharArray()) { |
|
502 |
for (Method m : cf.methods) { |
|
503 |
if (!Modifier.isPrivate(m.access)) continue; |
|
504 |
if (!Modifier.isStatic(m.access)) continue; |
|
505 |
if (nameAndTypeMark(m.name, m.type) == mark) { |
|
506 |
Constant con = scanPattern(m, mark); |
|
507 |
if (con == null) continue; |
|
508 |
constants.put(m, con); |
|
509 |
found = true; |
|
510 |
} |
|
511 |
} |
|
512 |
} |
|
513 |
return found; |
|
514 |
} |
|
515 |
||
516 |
void reportPatternMethods(boolean quietly, boolean allowMatchFailure) { |
|
517 |
if (!quietly && !constants.keySet().isEmpty()) |
|
518 |
System.err.println("pattern methods removed: "+constants.keySet()); |
|
519 |
for (Method m : cf.methods) { |
|
520 |
if (nameMark(cf.pool.getString(m.name)) != 0 && |
|
521 |
constants.get(m) == null) { |
|
522 |
String failure = "method has special name but fails to match pattern: "+m; |
|
523 |
if (!allowMatchFailure) |
|
524 |
throw new IllegalArgumentException(failure); |
|
525 |
else if (!quietly) |
|
526 |
System.err.println("warning: "+failure); |
|
527 |
} |
|
528 |
} |
|
529 |
if (verifySpecifierCount >= 0) { |
|
530 |
List<Object[]> specs = bootstrapMethodSpecifiers(false); |
|
531 |
int specsLen = (specs == null ? 0 : specs.size()); |
|
532 |
if (specsLen != verifySpecifierCount) { |
|
533 |
throw new IllegalArgumentException("BootstrapMethods length is "+specsLen+" but should be "+verifySpecifierCount); |
|
534 |
} |
|
535 |
} |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
536 |
if (!quiet) System.err.flush(); |
7558 | 537 |
} |
538 |
||
539 |
// mark constant pool entries according to participation in patterns |
|
540 |
boolean initializeMarks() { |
|
541 |
boolean changed = false; |
|
542 |
for (;;) { |
|
543 |
boolean changed1 = false; |
|
544 |
int cpindex = -1; |
|
545 |
for (Constant e : cf.pool) { |
|
546 |
++cpindex; |
|
547 |
if (e == null) continue; |
|
548 |
char mark = poolMarks[cpindex]; |
|
549 |
if (mark != 0) continue; |
|
550 |
switch (e.tag) { |
|
551 |
case CONSTANT_Utf8: |
|
552 |
mark = nameMark(e.itemString()); break; |
|
553 |
case CONSTANT_NameAndType: |
|
554 |
mark = nameAndTypeMark(e.itemIndexes()); break; |
|
555 |
case CONSTANT_Class: { |
|
556 |
int n1 = e.itemIndex(); |
|
557 |
char nmark = poolMarks[(char)n1]; |
|
558 |
if ("DJ".indexOf(nmark) >= 0) |
|
559 |
mark = nmark; |
|
560 |
break; |
|
561 |
} |
|
562 |
case CONSTANT_Field: |
|
563 |
case CONSTANT_Method: { |
|
564 |
Short[] n12 = e.itemIndexes(); |
|
565 |
short cl = n12[0]; |
|
566 |
short nt = n12[1]; |
|
567 |
char cmark = poolMarks[(char)cl]; |
|
568 |
if (cmark != 0) { |
|
569 |
mark = cmark; // it is a java.dyn.* or java.lang.* method |
|
570 |
break; |
|
571 |
} |
|
572 |
String cls = cf.pool.getString(CONSTANT_Class, cl); |
|
573 |
if (cls.equals(cf.nameString())) { |
|
574 |
switch (poolMarks[(char)nt]) { |
|
575 |
// it is a private MH/MT/INDY method |
|
576 |
case 'T': case 'H': case 'I': |
|
577 |
mark = poolMarks[(char)nt]; |
|
578 |
break; |
|
579 |
} |
|
580 |
} |
|
581 |
break; |
|
582 |
} |
|
583 |
default: break; |
|
584 |
} |
|
585 |
if (mark != 0) { |
|
586 |
poolMarks[cpindex] = mark; |
|
587 |
changed1 = true; |
|
588 |
} |
|
589 |
} |
|
590 |
if (!changed1) |
|
591 |
break; |
|
592 |
changed = true; |
|
593 |
} |
|
594 |
return changed; |
|
595 |
} |
|
596 |
char nameMark(String s) { |
|
597 |
if (s.startsWith("MT_")) return 'T'; |
|
598 |
else if (s.startsWith("MH_")) return 'H'; |
|
599 |
else if (s.startsWith("INDY_")) return 'I'; |
|
8822
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
600 |
else if (transitionalJSR292 && |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
601 |
s.startsWith("java/dyn/")) return 'D'; |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
602 |
else if (s.startsWith("java/lang/invoke/")) return 'D'; |
7558 | 603 |
else if (s.startsWith("java/lang/")) return 'J'; |
604 |
return 0; |
|
605 |
} |
|
606 |
char nameAndTypeMark(Short[] n12) { |
|
607 |
return nameAndTypeMark(n12[0], n12[1]); |
|
608 |
} |
|
609 |
char nameAndTypeMark(short n1, short n2) { |
|
610 |
char mark = poolMarks[(char)n1]; |
|
611 |
if (mark == 0) return 0; |
|
612 |
String descr = cf.pool.getString(CONSTANT_Utf8, n2); |
|
613 |
String requiredType; |
|
614 |
switch (poolMarks[(char)n1]) { |
|
8822
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
615 |
case 'H': requiredType = "()Ljava/lang/invoke/MethodHandle;"; break; |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
616 |
case 'T': requiredType = "()Ljava/lang/invoke/MethodType;"; break; |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
617 |
case 'I': requiredType = "()Ljava/lang/invoke/MethodHandle;"; break; |
7558 | 618 |
default: return 0; |
619 |
} |
|
8822
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
620 |
if (matchType(descr, requiredType)) return mark; |
7558 | 621 |
return 0; |
622 |
} |
|
623 |
||
8822
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
624 |
boolean matchType(String descr, String requiredType) { |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
625 |
if (descr.equals(requiredType)) return true; |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
626 |
if (transitionalJSR292) { |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
627 |
String oldType = requiredType.replace("Ljava/lang/invoke/", "Ljava/dyn/"); |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
628 |
if (descr.equals(oldType)) return true; |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
629 |
} |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
630 |
return false; |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
631 |
} |
8145ab9f5f86
7012648: move JSR 292 to package java.lang.invoke and adjust names
jrose
parents:
8345
diff
changeset
|
632 |
|
7558 | 633 |
private class JVMState { |
634 |
final List<Object> stack = new ArrayList<>(); |
|
635 |
int sp() { return stack.size(); } |
|
636 |
void push(Object x) { stack.add(x); } |
|
637 |
void push2(Object x) { stack.add(EMPTY_SLOT); stack.add(x); } |
|
638 |
void pushAt(int pos, Object x) { stack.add(stack.size()+pos, x); } |
|
639 |
Object pop() { return stack.remove(sp()-1); } |
|
640 |
Object top() { return stack.get(sp()-1); } |
|
641 |
List<Object> args(boolean hasRecv, String type) { |
|
642 |
return args(argsize(type) + (hasRecv ? 1 : 0)); |
|
643 |
} |
|
644 |
List<Object> args(int argsize) { |
|
645 |
return stack.subList(sp()-argsize, sp()); |
|
646 |
} |
|
647 |
boolean stackMotion(int bc) { |
|
648 |
switch (bc) { |
|
649 |
case opc_pop: pop(); break; |
|
650 |
case opc_pop2: pop(); pop(); break; |
|
651 |
case opc_swap: pushAt(-1, pop()); break; |
|
652 |
case opc_dup: push(top()); break; |
|
653 |
case opc_dup_x1: pushAt(-2, top()); break; |
|
654 |
case opc_dup_x2: pushAt(-3, top()); break; |
|
655 |
// ? also: dup2{,_x1,_x2} |
|
656 |
default: return false; |
|
657 |
} |
|
658 |
return true; |
|
659 |
} |
|
660 |
} |
|
661 |
private final String EMPTY_SLOT = "_"; |
|
662 |
private void removeEmptyJVMSlots(List<Object> args) { |
|
663 |
for (;;) { |
|
664 |
int i = args.indexOf(EMPTY_SLOT); |
|
665 |
if (i >= 0 && i+1 < args.size() |
|
666 |
&& (isConstant(args.get(i+1), CONSTANT_Long) || |
|
667 |
isConstant(args.get(i+1), CONSTANT_Double))) |
|
668 |
args.remove(i); |
|
669 |
else break; |
|
670 |
} |
|
671 |
} |
|
672 |
||
673 |
private Constant scanPattern(Method m, char patternMark) { |
|
674 |
if (verbose) System.err.println("scan "+m+" for pattern="+patternMark); |
|
675 |
int wantTag; |
|
676 |
switch (patternMark) { |
|
677 |
case 'T': wantTag = CONSTANT_MethodType; break; |
|
678 |
case 'H': wantTag = CONSTANT_MethodHandle; break; |
|
679 |
case 'I': wantTag = CONSTANT_InvokeDynamic; break; |
|
680 |
default: throw new InternalError(); |
|
681 |
} |
|
682 |
Instruction i = m.instructions(); |
|
683 |
JVMState jvm = new JVMState(); |
|
684 |
Pool pool = cf.pool; |
|
685 |
int branchCount = 0; |
|
686 |
Object arg; |
|
687 |
List<Object> args; |
|
688 |
List<Object> bsmArgs = null; // args to invokeGeneric |
|
689 |
decode: |
|
690 |
for (; i != null; i = i.next()) { |
|
691 |
//System.out.println(jvm.stack+" "+i); |
|
692 |
int bc = i.bc; |
|
693 |
switch (bc) { |
|
694 |
case opc_ldc: jvm.push(pool.get(i.u1At(1))); break; |
|
695 |
case opc_ldc_w: jvm.push(pool.get(i.u2At(1))); break; |
|
696 |
case opc_ldc2_w: jvm.push2(pool.get(i.u2At(1))); break; |
|
697 |
case opc_aconst_null: jvm.push(null); break; |
|
698 |
case opc_bipush: jvm.push((int)(byte) i.u1At(1)); break; |
|
699 |
case opc_sipush: jvm.push((int)(short)i.u2At(1)); break; |
|
700 |
||
701 |
// these support creation of a restarg array |
|
702 |
case opc_anewarray: |
|
703 |
arg = jvm.pop(); |
|
704 |
if (!(arg instanceof Integer)) break decode; |
|
705 |
arg = Arrays.asList(new Object[(Integer)arg]); |
|
706 |
jvm.push(arg); |
|
707 |
break; |
|
708 |
case opc_dup: |
|
709 |
jvm.push(jvm.top()); break; |
|
710 |
case opc_aastore: |
|
711 |
args = jvm.args(3); // array, index, value |
|
712 |
if (args.get(0) instanceof List && |
|
713 |
args.get(1) instanceof Integer) { |
|
714 |
((List<Object>)args.get(0)).set( (Integer)args.get(1), args.get(2) ); |
|
715 |
} |
|
716 |
args.clear(); |
|
717 |
break; |
|
718 |
||
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
719 |
case opc_new: |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
720 |
{ |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
721 |
String type = pool.getString(CONSTANT_Class, (short)i.u2At(1)); |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
722 |
//System.out.println("new "+type); |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
723 |
switch (type) { |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
724 |
case "java/lang/StringBuilder": |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
725 |
jvm.push("StringBuilder"); |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
726 |
continue decode; // go to next instruction |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
727 |
} |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
728 |
break decode; // bail out |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
729 |
} |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
730 |
|
7558 | 731 |
case opc_getstatic: |
732 |
{ |
|
733 |
// int.class compiles to getstatic Integer.TYPE |
|
734 |
int fieldi = i.u2At(1); |
|
735 |
char mark = poolMarks[fieldi]; |
|
736 |
//System.err.println("getstatic "+fieldi+Arrays.asList(pool.getStrings(pool.getMemberRef((short)fieldi)))+mark); |
|
737 |
if (mark == 'J') { |
|
738 |
Short[] ref = pool.getMemberRef((short) fieldi); |
|
739 |
String name = pool.getString(CONSTANT_Utf8, ref[1]); |
|
740 |
if ("TYPE".equals(name)) { |
|
741 |
String wrapperName = pool.getString(CONSTANT_Class, ref[0]).replace('/', '.'); |
|
742 |
// a primitive type descriptor |
|
743 |
Class<?> primClass; |
|
744 |
try { |
|
745 |
primClass = (Class<?>) Class.forName(wrapperName).getField(name).get(null); |
|
746 |
} catch (Exception ex) { |
|
747 |
throw new InternalError("cannot load "+wrapperName+"."+name); |
|
748 |
} |
|
749 |
jvm.push(primClass); |
|
750 |
break; |
|
751 |
} |
|
752 |
} |
|
753 |
// unknown field; keep going... |
|
754 |
jvm.push(UNKNOWN_CON); |
|
755 |
break; |
|
756 |
} |
|
757 |
case opc_putstatic: |
|
758 |
{ |
|
759 |
if (patternMark != 'I') break decode; |
|
760 |
jvm.pop(); |
|
761 |
// unknown field; keep going... |
|
762 |
break; |
|
763 |
} |
|
764 |
||
765 |
case opc_invokestatic: |
|
766 |
case opc_invokevirtual: |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
767 |
case opc_invokespecial: |
7558 | 768 |
{ |
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
769 |
boolean hasRecv = (bc != opc_invokestatic); |
7558 | 770 |
int methi = i.u2At(1); |
771 |
char mark = poolMarks[methi]; |
|
772 |
Short[] ref = pool.getMemberRef((short)methi); |
|
773 |
String type = pool.getString(CONSTANT_Utf8, ref[2]); |
|
774 |
//System.out.println("invoke "+pool.getString(CONSTANT_Utf8, ref[1])+" "+Arrays.asList(ref)+" : "+type); |
|
775 |
args = jvm.args(hasRecv, type); |
|
776 |
String intrinsic = null; |
|
777 |
Constant con; |
|
778 |
if (mark == 'D' || mark == 'J') { |
|
779 |
intrinsic = pool.getString(CONSTANT_Utf8, ref[1]); |
|
780 |
if (mark == 'J') { |
|
781 |
String cls = pool.getString(CONSTANT_Class, ref[0]); |
|
782 |
cls = cls.substring(1+cls.lastIndexOf('/')); |
|
783 |
intrinsic = cls+"."+intrinsic; |
|
784 |
} |
|
785 |
//System.out.println("recognized intrinsic "+intrinsic); |
|
786 |
byte refKind = -1; |
|
787 |
switch (intrinsic) { |
|
788 |
case "findGetter": refKind = REF_getField; break; |
|
789 |
case "findStaticGetter": refKind = REF_getStatic; break; |
|
790 |
case "findSetter": refKind = REF_putField; break; |
|
791 |
case "findStaticSetter": refKind = REF_putStatic; break; |
|
792 |
case "findVirtual": refKind = REF_invokeVirtual; break; |
|
793 |
case "findStatic": refKind = REF_invokeStatic; break; |
|
794 |
case "findSpecial": refKind = REF_invokeSpecial; break; |
|
795 |
case "findConstructor": refKind = REF_newInvokeSpecial; break; |
|
796 |
} |
|
797 |
if (refKind >= 0 && (con = parseMemberLookup(refKind, args)) != null) { |
|
798 |
args.clear(); args.add(con); |
|
799 |
continue; |
|
800 |
} |
|
801 |
} |
|
802 |
Method ownMethod = null; |
|
803 |
if (mark == 'T' || mark == 'H' || mark == 'I') { |
|
804 |
ownMethod = findMember(cf.methods, ref[1], ref[2]); |
|
805 |
} |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
806 |
//if (intrinsic != null) System.out.println("intrinsic = "+intrinsic); |
7558 | 807 |
switch (intrinsic == null ? "" : intrinsic) { |
808 |
case "fromMethodDescriptorString": |
|
809 |
con = makeMethodTypeCon(args.get(0)); |
|
810 |
args.clear(); args.add(con); |
|
811 |
continue; |
|
812 |
case "methodType": { |
|
813 |
flattenVarargs(args); // there are several overloadings, some with varargs |
|
814 |
StringBuilder buf = new StringBuilder(); |
|
815 |
String rtype = null; |
|
816 |
for (Object typeArg : args) { |
|
817 |
if (typeArg instanceof Class) { |
|
818 |
Class<?> argClass = (Class<?>) typeArg; |
|
819 |
if (argClass.isPrimitive()) { |
|
820 |
char tchar; |
|
821 |
switch (argClass.getName()) { |
|
822 |
case "void": tchar = 'V'; break; |
|
823 |
case "boolean": tchar = 'Z'; break; |
|
824 |
case "byte": tchar = 'B'; break; |
|
825 |
case "char": tchar = 'C'; break; |
|
826 |
case "short": tchar = 'S'; break; |
|
827 |
case "int": tchar = 'I'; break; |
|
828 |
case "long": tchar = 'J'; break; |
|
829 |
case "float": tchar = 'F'; break; |
|
830 |
case "double": tchar = 'D'; break; |
|
831 |
default: throw new InternalError(argClass.toString()); |
|
832 |
} |
|
833 |
buf.append(tchar); |
|
834 |
} else { |
|
835 |
// should not happen, but... |
|
836 |
buf.append('L').append(argClass.getName().replace('.','/')).append(';'); |
|
837 |
} |
|
838 |
} else if (typeArg instanceof Constant) { |
|
839 |
Constant argCon = (Constant) typeArg; |
|
840 |
if (argCon.tag == CONSTANT_Class) { |
|
841 |
String cn = pool.get(argCon.itemIndex()).itemString(); |
|
842 |
if (cn.endsWith(";")) |
|
843 |
buf.append(cn); |
|
844 |
else |
|
845 |
buf.append('L').append(cn).append(';'); |
|
846 |
} else { |
|
847 |
break decode; |
|
848 |
} |
|
849 |
} else { |
|
850 |
break decode; |
|
851 |
} |
|
852 |
if (rtype == null) { |
|
853 |
// first arg is treated differently |
|
854 |
rtype = buf.toString(); |
|
855 |
buf.setLength(0); |
|
856 |
buf.append('('); |
|
857 |
} |
|
858 |
} |
|
859 |
buf.append(')').append(rtype); |
|
860 |
con = con = makeMethodTypeCon(buf.toString()); |
|
861 |
args.clear(); args.add(con); |
|
862 |
continue; |
|
863 |
} |
|
864 |
case "lookup": |
|
865 |
case "dynamicInvoker": |
|
866 |
args.clear(); args.add(intrinsic); |
|
867 |
continue; |
|
868 |
case "lookupClass": |
|
869 |
if (args.equals(Arrays.asList("lookup"))) { |
|
870 |
// fold lookup().lookupClass() to the enclosing class |
|
871 |
args.clear(); args.add(pool.get(cf.thisc)); |
|
872 |
continue; |
|
873 |
} |
|
874 |
break; |
|
875 |
case "invokeGeneric": |
|
876 |
case "invokeWithArguments": |
|
877 |
if (patternMark != 'I') break decode; |
|
878 |
if ("invokeWithArguments".equals(intrinsic)) |
|
879 |
flattenVarargs(args); |
|
880 |
bsmArgs = new ArrayList(args); |
|
881 |
args.clear(); args.add("invokeGeneric"); |
|
882 |
continue; |
|
883 |
case "Integer.valueOf": |
|
884 |
case "Float.valueOf": |
|
885 |
case "Long.valueOf": |
|
886 |
case "Double.valueOf": |
|
887 |
removeEmptyJVMSlots(args); |
|
888 |
if (args.size() == 1) { |
|
889 |
arg = args.remove(0); |
|
890 |
assert(3456 == (CONSTANT_Integer*1000 + CONSTANT_Float*100 + CONSTANT_Long*10 + CONSTANT_Double)); |
|
891 |
if (isConstant(arg, CONSTANT_Integer + "IFLD".indexOf(intrinsic.charAt(0))) |
|
892 |
|| arg instanceof Number) { |
|
893 |
args.add(arg); continue; |
|
894 |
} |
|
895 |
} |
|
896 |
break decode; |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
897 |
case "StringBuilder.append": |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
898 |
// allow calls like ("value = "+x) |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
899 |
removeEmptyJVMSlots(args); |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
900 |
args.subList(1, args.size()).clear(); |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
901 |
continue; |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
902 |
case "StringBuilder.toString": |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
903 |
args.clear(); |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
904 |
args.add(intrinsic); |
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
905 |
continue; |
7558 | 906 |
} |
907 |
if (!hasRecv && ownMethod != null && patternMark != 0) { |
|
908 |
con = constants.get(ownMethod); |
|
909 |
if (con == null) break decode; |
|
910 |
args.clear(); args.add(con); |
|
911 |
continue; |
|
912 |
} else if (type.endsWith(")V")) { |
|
913 |
// allow calls like println("reached the pattern method") |
|
914 |
args.clear(); |
|
915 |
continue; |
|
916 |
} |
|
917 |
break decode; // bail out for most calls |
|
918 |
} |
|
919 |
case opc_areturn: |
|
920 |
{ |
|
921 |
++branchCount; |
|
922 |
if (bsmArgs != null) { |
|
923 |
// parse bsmArgs as (MH, lookup, String, MT, [extra]) |
|
924 |
Constant indyCon = makeInvokeDynamicCon(bsmArgs); |
|
925 |
if (indyCon != null) { |
|
926 |
Constant typeCon = (Constant) bsmArgs.get(3); |
|
927 |
indySignatures.put(m, pool.getString(typeCon.itemIndex())); |
|
928 |
return indyCon; |
|
929 |
} |
|
930 |
System.err.println(m+": inscrutable bsm arguments: "+bsmArgs); |
|
931 |
break decode; // bail out |
|
932 |
} |
|
933 |
arg = jvm.pop(); |
|
934 |
if (branchCount == 2 && UNKNOWN_CON.equals(arg)) |
|
935 |
break; // merge to next path |
|
936 |
if (isConstant(arg, wantTag)) |
|
937 |
return (Constant) arg; |
|
938 |
break decode; // bail out |
|
939 |
} |
|
940 |
default: |
|
941 |
if (jvm.stackMotion(i.bc)) break; |
|
942 |
if (bc >= opc_nconst_MIN && bc <= opc_nconst_MAX) |
|
943 |
{ jvm.push(INSTRUCTION_CONSTANTS[bc - opc_nconst_MIN]); break; } |
|
944 |
if (patternMark == 'I') { |
|
945 |
// these support caching paths in INDY_x methods |
|
946 |
if (bc == opc_aload || bc >= opc_aload_0 && bc <= opc_aload_MAX) |
|
947 |
{ jvm.push(UNKNOWN_CON); break; } |
|
948 |
if (bc == opc_astore || bc >= opc_astore_0 && bc <= opc_astore_MAX) |
|
949 |
{ jvm.pop(); break; } |
|
950 |
switch (bc) { |
|
951 |
case opc_getfield: |
|
952 |
case opc_aaload: |
|
953 |
jvm.push(UNKNOWN_CON); break; |
|
954 |
case opc_ifnull: |
|
955 |
case opc_ifnonnull: |
|
956 |
// ignore branch target |
|
957 |
if (++branchCount != 1) break decode; |
|
958 |
jvm.pop(); |
|
959 |
break; |
|
960 |
case opc_checkcast: |
|
961 |
arg = jvm.top(); |
|
962 |
if ("invokeWithArguments".equals(arg) || |
|
963 |
"invokeGeneric".equals(arg)) |
|
964 |
break; // assume it is a helpful cast |
|
965 |
break decode; |
|
966 |
default: |
|
967 |
break decode; // bail out |
|
968 |
} |
|
969 |
continue decode; // go to next instruction |
|
970 |
} |
|
971 |
break decode; // bail out |
|
972 |
} //end switch |
|
973 |
} |
|
974 |
System.err.println(m+": bailout on "+i+" jvm stack: "+jvm.stack); |
|
975 |
return null; |
|
976 |
} |
|
977 |
private final String UNKNOWN_CON = "<unknown>"; |
|
978 |
||
979 |
private void flattenVarargs(List<Object> args) { |
|
980 |
int size = args.size(); |
|
981 |
if (size > 0 && args.get(size-1) instanceof List) |
|
982 |
args.addAll((List<Object>) args.remove(size-1)); |
|
983 |
} |
|
984 |
||
985 |
private boolean isConstant(Object x, int tag) { |
|
986 |
return x instanceof Constant && ((Constant)x).tag == tag; |
|
987 |
} |
|
988 |
private Constant makeMethodTypeCon(Object x) { |
|
989 |
short utfIndex; |
|
990 |
if (x instanceof String) |
|
991 |
utfIndex = (short) cf.pool.addConstant(CONSTANT_Utf8, x).index; |
|
992 |
else if (isConstant(x, CONSTANT_String)) |
|
993 |
utfIndex = ((Constant)x).itemIndex(); |
|
994 |
else return null; |
|
995 |
return cf.pool.addConstant(CONSTANT_MethodType, utfIndex); |
|
996 |
} |
|
997 |
private Constant parseMemberLookup(byte refKind, List<Object> args) { |
|
998 |
// E.g.: lookup().findStatic(Foo.class, "name", MethodType) |
|
999 |
if (args.size() != 4) return null; |
|
1000 |
int argi = 0; |
|
1001 |
if (!"lookup".equals(args.get(argi++))) return null; |
|
1002 |
short refindex, cindex, ntindex, nindex, tindex; |
|
1003 |
Object con; |
|
1004 |
if (!isConstant(con = args.get(argi++), CONSTANT_Class)) return null; |
|
1005 |
cindex = (short)((Constant)con).index; |
|
1006 |
if (!isConstant(con = args.get(argi++), CONSTANT_String)) return null; |
|
1007 |
nindex = ((Constant)con).itemIndex(); |
|
1008 |
if (isConstant(con = args.get(argi++), CONSTANT_MethodType) || |
|
1009 |
isConstant(con, CONSTANT_Class)) { |
|
1010 |
tindex = ((Constant)con).itemIndex(); |
|
1011 |
} else return null; |
|
1012 |
ntindex = (short) cf.pool.addConstant(CONSTANT_NameAndType, |
|
1013 |
new Short[]{ nindex, tindex }).index; |
|
1014 |
byte reftag = CONSTANT_Method; |
|
1015 |
if (refKind <= REF_putStatic) |
|
1016 |
reftag = CONSTANT_Field; |
|
1017 |
else if (refKind == REF_invokeInterface) |
|
1018 |
reftag = CONSTANT_InterfaceMethod; |
|
1019 |
Constant ref = cf.pool.addConstant(reftag, new Short[]{ cindex, ntindex }); |
|
1020 |
return cf.pool.addConstant(CONSTANT_MethodHandle, new Object[]{ refKind, (short)ref.index }); |
|
1021 |
} |
|
1022 |
private Constant makeInvokeDynamicCon(List<Object> args) { |
|
1023 |
// E.g.: MH_bsm.invokeGeneric(lookup(), "name", MethodType, "extraArg") |
|
1024 |
removeEmptyJVMSlots(args); |
|
1025 |
if (args.size() != 4 && args.size() != 5) return null; |
|
1026 |
int argi = 0; |
|
1027 |
short nindex, tindex, ntindex, bsmindex; |
|
1028 |
Object con; |
|
1029 |
if (!isConstant(con = args.get(argi++), CONSTANT_MethodHandle)) return null; |
|
1030 |
bsmindex = (short) ((Constant)con).index; |
|
1031 |
if (!"lookup".equals(args.get(argi++))) return null; |
|
1032 |
if (!isConstant(con = args.get(argi++), CONSTANT_String)) return null; |
|
1033 |
nindex = ((Constant)con).itemIndex(); |
|
1034 |
if (!isConstant(con = args.get(argi++), CONSTANT_MethodType)) return null; |
|
1035 |
tindex = ((Constant)con).itemIndex(); |
|
1036 |
ntindex = (short) cf.pool.addConstant(CONSTANT_NameAndType, |
|
1037 |
new Short[]{ nindex, tindex }).index; |
|
1038 |
if (transitionalJSR292) { |
|
1039 |
if (argi != args.size()) { |
|
1040 |
System.err.println("BSM specifier has extra arguments but transitionalJSR292=1"); |
|
1041 |
return null; |
|
1042 |
} |
|
1043 |
return cf.pool.addConstant(CONSTANT_InvokeDynamic_17, |
|
1044 |
new Short[]{ bsmindex, ntindex }); |
|
1045 |
} |
|
1046 |
List<Object> extraArgs = Collections.emptyList(); |
|
1047 |
if (argi < args.size()) { |
|
1048 |
Object arg = args.get(argi); |
|
1049 |
if (arg instanceof List) |
|
1050 |
extraArgs = (List<Object>) arg; |
|
1051 |
else |
|
1052 |
extraArgs = Arrays.asList(arg); |
|
1053 |
removeEmptyJVMSlots(args); |
|
1054 |
} |
|
1055 |
List<Short> extraArgIndexes = new CountedList<>(Short.class); |
|
1056 |
for (Object x : extraArgs) { |
|
1057 |
if (x instanceof Number) { |
|
1058 |
Object num = null; byte numTag = 0; |
|
1059 |
if (x instanceof Integer) { num = x; numTag = CONSTANT_Integer; } |
|
1060 |
if (x instanceof Float) { num = Float.floatToRawIntBits((Float)x); numTag = CONSTANT_Float; } |
|
1061 |
if (x instanceof Long) { num = x; numTag = CONSTANT_Long; } |
|
1062 |
if (x instanceof Double) { num = Double.doubleToRawLongBits((Double)x); numTag = CONSTANT_Double; } |
|
1063 |
if (num != null) x = cf.pool.addConstant(numTag, x); |
|
1064 |
} |
|
1065 |
if (!(x instanceof Constant)) return null; |
|
1066 |
extraArgIndexes.add((short) ((Constant)x).index); |
|
1067 |
} |
|
1068 |
List<Object[]> specs = bootstrapMethodSpecifiers(true); |
|
1069 |
int specindex = -1; |
|
1070 |
Object[] spec = new Object[]{ bsmindex, extraArgIndexes }; |
|
1071 |
for (Object[] spec1 : specs) { |
|
1072 |
if (Arrays.equals(spec1, spec)) { |
|
1073 |
specindex = specs.indexOf(spec1); |
|
1074 |
if (verbose) System.err.println("reusing BSM specifier: "+spec1[0]+spec1[1]); |
|
1075 |
break; |
|
1076 |
} |
|
1077 |
} |
|
1078 |
if (specindex == -1) { |
|
1079 |
specindex = (short) specs.size(); |
|
1080 |
specs.add(spec); |
|
1081 |
if (verbose) System.err.println("adding BSM specifier: "+spec[0]+spec[1]); |
|
1082 |
} |
|
1083 |
return cf.pool.addConstant(CONSTANT_InvokeDynamic, |
|
1084 |
new Short[]{ (short)specindex, ntindex }); |
|
1085 |
} |
|
1086 |
||
1087 |
List<Object[]> bootstrapMethodSpecifiers(boolean createIfNotFound) { |
|
1088 |
Attr bsms = cf.findAttr("BootstrapMethods"); |
|
1089 |
if (bsms == null) { |
|
1090 |
if (!createIfNotFound) return null; |
|
1091 |
bsms = new Attr(cf, "BootstrapMethods", new byte[]{0,0}); |
|
1092 |
assert(bsms == cf.findAttr("BootstrapMethods")); |
|
1093 |
} |
|
1094 |
if (bsms.item instanceof byte[]) { |
|
1095 |
// unflatten |
|
1096 |
List<Object[]> specs = new CountedList<>(Object[].class); |
|
1097 |
DataInputStream in = new DataInputStream(new ByteArrayInputStream((byte[]) bsms.item)); |
|
1098 |
try { |
|
1099 |
int len = (char) in.readShort(); |
|
1100 |
for (int i = 0; i < len; i++) { |
|
1101 |
short bsm = in.readShort(); |
|
1102 |
int argc = (char) in.readShort(); |
|
1103 |
List<Short> argv = new CountedList<>(Short.class); |
|
1104 |
for (int j = 0; j < argc; j++) |
|
1105 |
argv.add(in.readShort()); |
|
1106 |
specs.add(new Object[]{ bsm, argv }); |
|
1107 |
} |
|
1108 |
} catch (IOException ex) { throw new InternalError(); } |
|
1109 |
bsms.item = specs; |
|
1110 |
} |
|
1111 |
return (List<Object[]>) bsms.item; |
|
1112 |
} |
|
1113 |
} |
|
1114 |
||
1115 |
private DataInputStream openInput(File f) throws IOException { |
|
1116 |
return new DataInputStream(new BufferedInputStream(new FileInputStream(f))); |
|
1117 |
} |
|
1118 |
||
1119 |
private DataOutputStream openOutput(File f) throws IOException { |
|
1120 |
if (!overwrite && f.exists()) |
|
1121 |
throw new IOException("file already exists: "+f); |
|
1122 |
ensureDirectory(f.getParentFile()); |
|
1123 |
return new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f))); |
|
1124 |
} |
|
1125 |
||
1126 |
static byte[] readRawBytes(DataInputStream in, int size) throws IOException { |
|
1127 |
byte[] bytes = new byte[size]; |
|
1128 |
int nr = in.read(bytes); |
|
1129 |
if (nr != size) |
|
1130 |
throw new InternalError("wrong size: "+nr); |
|
1131 |
return bytes; |
|
1132 |
} |
|
1133 |
||
1134 |
private interface Chunk { |
|
1135 |
void readFrom(DataInputStream in) throws IOException; |
|
1136 |
void writeTo(DataOutputStream out) throws IOException; |
|
1137 |
} |
|
1138 |
||
1139 |
private static class CountedList<T> extends ArrayList<T> implements Chunk { |
|
1140 |
final Class<? extends T> itemClass; |
|
1141 |
final int rowlen; |
|
1142 |
CountedList(Class<? extends T> itemClass, int rowlen) { |
|
1143 |
this.itemClass = itemClass; |
|
1144 |
this.rowlen = rowlen; |
|
1145 |
} |
|
1146 |
CountedList(Class<? extends T> itemClass) { this(itemClass, -1); } |
|
1147 |
public void readFrom(DataInputStream in) throws IOException { |
|
1148 |
int count = in.readUnsignedShort(); |
|
1149 |
while (size() < count) { |
|
1150 |
if (rowlen < 0) { |
|
1151 |
add(readInput(in, itemClass)); |
|
1152 |
} else { |
|
1153 |
Class<?> elemClass = itemClass.getComponentType(); |
|
1154 |
Object[] row = (Object[]) java.lang.reflect.Array.newInstance(elemClass, rowlen); |
|
1155 |
for (int i = 0; i < rowlen; i++) |
|
1156 |
row[i] = readInput(in, elemClass); |
|
1157 |
add(itemClass.cast(row)); |
|
1158 |
} |
|
1159 |
} |
|
1160 |
} |
|
1161 |
public void writeTo(DataOutputStream out) throws IOException { |
|
1162 |
out.writeShort((short)size()); |
|
1163 |
for (T item : this) { |
|
1164 |
writeOutput(out, item); |
|
1165 |
} |
|
1166 |
} |
|
1167 |
} |
|
1168 |
||
1169 |
private static <T> T readInput(DataInputStream in, Class<T> dataClass) throws IOException { |
|
1170 |
Object data; |
|
1171 |
if (dataClass == Integer.class) { |
|
1172 |
data = in.readInt(); |
|
1173 |
} else if (dataClass == Short.class) { |
|
1174 |
data = in.readShort(); |
|
1175 |
} else if (dataClass == Byte.class) { |
|
1176 |
data = in.readByte(); |
|
1177 |
} else if (dataClass == String.class) { |
|
1178 |
data = in.readUTF(); |
|
1179 |
} else if (Chunk.class.isAssignableFrom(dataClass)) { |
|
1180 |
T obj; |
|
1181 |
try { obj = dataClass.newInstance(); } |
|
1182 |
catch (Exception ex) { throw new RuntimeException(ex); } |
|
1183 |
((Chunk)obj).readFrom(in); |
|
1184 |
data = obj; |
|
1185 |
} else { |
|
1186 |
throw new InternalError("bad input datum: "+dataClass); |
|
1187 |
} |
|
1188 |
return dataClass.cast(data); |
|
1189 |
} |
|
1190 |
private static <T> T readInput(byte[] bytes, Class<T> dataClass) { |
|
1191 |
try { |
|
1192 |
return readInput(new DataInputStream(new ByteArrayInputStream(bytes)), dataClass); |
|
1193 |
} catch (IOException ex) { |
|
1194 |
throw new InternalError(); |
|
1195 |
} |
|
1196 |
} |
|
1197 |
private static void readInputs(DataInputStream in, Object... data) throws IOException { |
|
1198 |
for (Object x : data) ((Chunk)x).readFrom(in); |
|
1199 |
} |
|
1200 |
||
1201 |
private static void writeOutput(DataOutputStream out, Object data) throws IOException { |
|
1202 |
if (data == null) { |
|
1203 |
return; |
|
1204 |
} if (data instanceof Integer) { |
|
1205 |
out.writeInt((Integer)data); |
|
1206 |
} else if (data instanceof Long) { |
|
1207 |
out.writeLong((Long)data); |
|
1208 |
} else if (data instanceof Short) { |
|
1209 |
out.writeShort((Short)data); |
|
1210 |
} else if (data instanceof Byte) { |
|
1211 |
out.writeByte((Byte)data); |
|
1212 |
} else if (data instanceof String) { |
|
1213 |
out.writeUTF((String)data); |
|
1214 |
} else if (data instanceof byte[]) { |
|
1215 |
out.write((byte[])data); |
|
1216 |
} else if (data instanceof Object[]) { |
|
1217 |
for (Object x : (Object[]) data) |
|
1218 |
writeOutput(out, x); |
|
1219 |
} else if (data instanceof Chunk) { |
|
1220 |
Chunk x = (Chunk) data; |
|
1221 |
x.writeTo(out); |
|
1222 |
} else if (data instanceof List) { |
|
1223 |
for (Object x : (List<?>) data) |
|
1224 |
writeOutput(out, x); |
|
1225 |
} else { |
|
1226 |
throw new InternalError("bad output datum: "+data+" : "+data.getClass().getName()); |
|
1227 |
} |
|
1228 |
} |
|
1229 |
private static void writeOutputs(DataOutputStream out, Object... data) throws IOException { |
|
1230 |
for (Object x : data) writeOutput(out, x); |
|
1231 |
} |
|
1232 |
||
1233 |
public static abstract class Outer { |
|
1234 |
public abstract List<? extends Inner> inners(); |
|
1235 |
protected void linkInners() { |
|
1236 |
for (Inner i : inners()) { |
|
1237 |
i.linkOuter(this); |
|
1238 |
if (i instanceof Outer) |
|
1239 |
((Outer)i).linkInners(); |
|
1240 |
} |
|
1241 |
} |
|
1242 |
public <T extends Outer> T outer(Class<T> c) { |
|
1243 |
for (Outer walk = this;; walk = ((Inner)walk).outer()) { |
|
1244 |
if (c.isInstance(walk)) |
|
1245 |
return c.cast(walk); |
|
1246 |
//if (!(walk instanceof Inner)) return null; |
|
1247 |
} |
|
1248 |
} |
|
1249 |
||
1250 |
public abstract List<Attr> attrs(); |
|
1251 |
public Attr findAttr(String name) { |
|
1252 |
return findAttr(outer(ClassFile.class).pool.stringIndex(name, false)); |
|
1253 |
} |
|
1254 |
public Attr findAttr(int name) { |
|
1255 |
if (name == 0) return null; |
|
1256 |
for (Attr a : attrs()) { |
|
1257 |
if (a.name == name) return a; |
|
1258 |
} |
|
1259 |
return null; |
|
1260 |
} |
|
1261 |
} |
|
1262 |
public interface Inner { Outer outer(); void linkOuter(Outer o); } |
|
1263 |
public static abstract class InnerOuter extends Outer implements Inner { |
|
1264 |
public Outer outer; |
|
1265 |
public Outer outer() { return outer; } |
|
1266 |
public void linkOuter(Outer o) { assert(outer == null); outer = o; } |
|
1267 |
} |
|
1268 |
public static class Constant<T> implements Chunk { |
|
1269 |
public final byte tag; |
|
1270 |
public final T item; |
|
1271 |
public final int index; |
|
1272 |
public Constant(int index, byte tag, T item) { |
|
1273 |
this.index = index; |
|
1274 |
this.tag = tag; |
|
1275 |
this.item = item; |
|
1276 |
} |
|
1277 |
public Constant checkTag(byte tag) { |
|
1278 |
if (this.tag != tag) throw new InternalError(this.toString()); |
|
1279 |
return this; |
|
1280 |
} |
|
1281 |
public String itemString() { return (String)item; } |
|
1282 |
public Short itemIndex() { return (Short)item; } |
|
1283 |
public Short[] itemIndexes() { return (Short[])item; } |
|
1284 |
public void readFrom(DataInputStream in) throws IOException { |
|
1285 |
throw new InternalError("do not call"); |
|
1286 |
} |
|
1287 |
public void writeTo(DataOutputStream out) throws IOException { |
|
1288 |
writeOutputs(out, tag, item); |
|
1289 |
} |
|
1290 |
public boolean equals(Object x) { return (x instanceof Constant && equals((Constant)x)); } |
|
1291 |
public boolean equals(Constant that) { |
|
1292 |
return (this.tag == that.tag && this.itemAsComparable().equals(that.itemAsComparable())); |
|
1293 |
} |
|
1294 |
public int hashCode() { return (tag * 31) + this.itemAsComparable().hashCode(); } |
|
1295 |
public Object itemAsComparable() { |
|
1296 |
switch (tag) { |
|
1297 |
case CONSTANT_Double: return Double.longBitsToDouble((Long)item); |
|
1298 |
case CONSTANT_Float: return Float.intBitsToFloat((Integer)item); |
|
1299 |
} |
|
1300 |
return (item instanceof Object[] ? Arrays.asList((Object[])item) : item); |
|
1301 |
} |
|
1302 |
public String toString() { |
|
1303 |
String itstr = String.valueOf(itemAsComparable()); |
|
1304 |
return (index + ":" + tagName(tag) + (itstr.startsWith("[")?"":"=") + itstr); |
|
1305 |
} |
|
1306 |
private static String[] TAG_NAMES; |
|
1307 |
public static String tagName(byte tag) { // used for error messages |
|
1308 |
if (TAG_NAMES == null) |
|
1309 |
TAG_NAMES = ("None Utf8 Unicode Integer Float Long Double Class String" |
|
1310 |
+" Fieldref Methodref InterfaceMethodref NameAndType #13 #14" |
|
1311 |
+" MethodHandle MethodType InvokeDynamic#17 InvokeDynamic").split(" "); |
|
1312 |
if ((tag & 0xFF) >= TAG_NAMES.length) return "#"+(tag & 0xFF); |
|
1313 |
return TAG_NAMES[tag & 0xFF]; |
|
1314 |
} |
|
1315 |
} |
|
1316 |
||
1317 |
public static class Pool extends CountedList<Constant> implements Chunk { |
|
1318 |
private Map<String,Short> strings = new TreeMap<>(); |
|
1319 |
||
1320 |
public Pool() { |
|
1321 |
super(Constant.class); |
|
1322 |
} |
|
1323 |
public void readFrom(DataInputStream in) throws IOException { |
|
1324 |
int count = in.readUnsignedShort(); |
|
1325 |
add(null); // always ignore first item |
|
1326 |
while (size() < count) { |
|
1327 |
readConstant(in); |
|
1328 |
} |
|
1329 |
} |
|
1330 |
public <T> Constant<T> addConstant(byte tag, T item) { |
|
1331 |
Constant<T> con = new Constant<>(size(), tag, item); |
|
1332 |
int idx = indexOf(con); |
|
1333 |
if (idx >= 0) return get(idx); |
|
1334 |
add(con); |
|
1335 |
if (tag == CONSTANT_Utf8) strings.put((String)item, (short) con.index); |
|
1336 |
return con; |
|
1337 |
} |
|
1338 |
private void readConstant(DataInputStream in) throws IOException { |
|
1339 |
byte tag = in.readByte(); |
|
1340 |
int index = size(); |
|
1341 |
Object arg; |
|
1342 |
switch (tag) { |
|
1343 |
case CONSTANT_Utf8: |
|
1344 |
arg = in.readUTF(); |
|
1345 |
strings.put((String) arg, (short) size()); |
|
1346 |
break; |
|
1347 |
case CONSTANT_Integer: |
|
1348 |
case CONSTANT_Float: |
|
1349 |
arg = in.readInt(); break; |
|
1350 |
case CONSTANT_Long: |
|
1351 |
case CONSTANT_Double: |
|
1352 |
add(new Constant(index, tag, in.readLong())); |
|
1353 |
add(null); |
|
1354 |
return; |
|
1355 |
case CONSTANT_Class: |
|
1356 |
case CONSTANT_String: |
|
1357 |
arg = in.readShort(); break; |
|
1358 |
case CONSTANT_Field: |
|
1359 |
case CONSTANT_Method: |
|
1360 |
case CONSTANT_InterfaceMethod: |
|
1361 |
case CONSTANT_NameAndType: |
|
1362 |
case CONSTANT_InvokeDynamic_17: |
|
1363 |
case CONSTANT_InvokeDynamic: |
|
1364 |
// read an ordered pair |
|
1365 |
arg = new Short[] { in.readShort(), in.readShort() }; |
|
1366 |
break; |
|
1367 |
case CONSTANT_MethodHandle: |
|
1368 |
// read an ordered pair; first part is a u1 (not u2) |
|
1369 |
arg = new Object[] { in.readByte(), in.readShort() }; |
|
1370 |
break; |
|
1371 |
case CONSTANT_MethodType: |
|
1372 |
arg = in.readShort(); break; |
|
1373 |
default: |
|
1374 |
throw new InternalError("bad CP tag "+tag); |
|
1375 |
} |
|
1376 |
add(new Constant(index, tag, arg)); |
|
1377 |
} |
|
1378 |
||
1379 |
// Access: |
|
1380 |
public Constant get(int index) { |
|
1381 |
// extra 1-bits get into the shorts |
|
1382 |
return super.get((char) index); |
|
1383 |
} |
|
1384 |
String getString(byte tag, short index) { |
|
1385 |
get(index).checkTag(tag); |
|
1386 |
return getString(index); |
|
1387 |
} |
|
1388 |
String getString(short index) { |
|
1389 |
Object v = get(index).item; |
|
1390 |
if (v instanceof Short) |
|
1391 |
v = get((Short)v).checkTag(CONSTANT_Utf8).item; |
|
1392 |
return (String) v; |
|
1393 |
} |
|
1394 |
String[] getStrings(Short[] indexes) { |
|
1395 |
String[] res = new String[indexes.length]; |
|
1396 |
for (int i = 0; i < indexes.length; i++) |
|
1397 |
res[i] = getString(indexes[i]); |
|
1398 |
return res; |
|
1399 |
} |
|
1400 |
int stringIndex(String name, boolean createIfNotFound) { |
|
1401 |
Short x = strings.get(name); |
|
1402 |
if (x != null) return (char)(int) x; |
|
1403 |
if (!createIfNotFound) return 0; |
|
1404 |
return addConstant(CONSTANT_Utf8, name).index; |
|
1405 |
} |
|
1406 |
Short[] getMemberRef(short index) { |
|
1407 |
Short[] cls_nnt = get(index).itemIndexes(); |
|
1408 |
Short[] name_type = get(cls_nnt[1]).itemIndexes(); |
|
1409 |
return new Short[]{ cls_nnt[0], name_type[0], name_type[1] }; |
|
1410 |
} |
|
1411 |
} |
|
1412 |
||
1413 |
public class ClassFile extends Outer implements Chunk { |
|
1414 |
ClassFile(File f) throws IOException { |
|
1415 |
DataInputStream in = openInput(f); |
|
1416 |
try { |
|
1417 |
readFrom(in); |
|
1418 |
} finally { |
|
1419 |
if (in != null) in.close(); |
|
1420 |
} |
|
1421 |
} |
|
1422 |
||
1423 |
public int magic, version; // <min:maj> |
|
1424 |
public final Pool pool = new Pool(); |
|
1425 |
public short access, thisc, superc; |
|
1426 |
public final List<Short> interfaces = new CountedList<>(Short.class); |
|
1427 |
public final List<Field> fields = new CountedList<>(Field.class); |
|
1428 |
public final List<Method> methods = new CountedList<>(Method.class); |
|
1429 |
public final List<Attr> attrs = new CountedList<>(Attr.class); |
|
1430 |
||
1431 |
public final void readFrom(DataInputStream in) throws IOException { |
|
1432 |
magic = in.readInt(); version = in.readInt(); |
|
1433 |
if (magic != 0xCAFEBABE) throw new IOException("bad magic number"); |
|
1434 |
pool.readFrom(in); |
|
1435 |
Code_index = pool.stringIndex("Code", false); |
|
1436 |
access = in.readShort(); thisc = in.readShort(); superc = in.readShort(); |
|
1437 |
readInputs(in, interfaces, fields, methods, attrs); |
|
1438 |
if (in.read() >= 0) throw new IOException("junk after end of file"); |
|
1439 |
linkInners(); |
|
1440 |
} |
|
1441 |
||
1442 |
void writeTo(File f) throws IOException { |
|
1443 |
DataOutputStream out = openOutput(f); |
|
1444 |
try { |
|
1445 |
writeTo(out); |
|
1446 |
} finally { |
|
1447 |
out.close(); |
|
1448 |
} |
|
1449 |
} |
|
1450 |
||
1451 |
public void writeTo(DataOutputStream out) throws IOException { |
|
1452 |
writeOutputs(out, magic, version, pool, |
|
1453 |
access, thisc, superc, interfaces, |
|
1454 |
fields, methods, attrs); |
|
1455 |
} |
|
1456 |
||
1457 |
public byte[] toByteArray() { |
|
1458 |
try { |
|
1459 |
ByteArrayOutputStream buf = new ByteArrayOutputStream(); |
|
1460 |
writeTo(new DataOutputStream(buf)); |
|
1461 |
return buf.toByteArray(); |
|
1462 |
} catch (IOException ex) { |
|
1463 |
throw new InternalError(); |
|
1464 |
} |
|
1465 |
} |
|
1466 |
||
1467 |
public List<Inner> inners() { |
|
1468 |
List<Inner> inns = new ArrayList<>(); |
|
1469 |
inns.addAll(fields); inns.addAll(methods); inns.addAll(attrs); |
|
1470 |
return inns; |
|
1471 |
} |
|
1472 |
public List<Attr> attrs() { return attrs; } |
|
1473 |
||
1474 |
// derived stuff: |
|
1475 |
public String nameString() { return pool.getString(CONSTANT_Class, thisc); } |
|
1476 |
int Code_index; |
|
1477 |
} |
|
1478 |
||
1479 |
private static <T extends Member> T findMember(List<T> mems, int name, int type) { |
|
1480 |
if (name == 0 || type == 0) return null; |
|
1481 |
for (T m : mems) { |
|
1482 |
if (m.name == name && m.type == type) return m; |
|
1483 |
} |
|
1484 |
return null; |
|
1485 |
} |
|
1486 |
||
1487 |
public static class Member extends InnerOuter implements Chunk { |
|
1488 |
public short access, name, type; |
|
1489 |
public final List<Attr> attrs = new CountedList<>(Attr.class); |
|
1490 |
public void readFrom(DataInputStream in) throws IOException { |
|
1491 |
access = in.readShort(); name = in.readShort(); type = in.readShort(); |
|
1492 |
readInputs(in, attrs); |
|
1493 |
} |
|
1494 |
public void writeTo(DataOutputStream out) throws IOException { |
|
1495 |
writeOutputs(out, access, name, type, attrs); |
|
1496 |
} |
|
1497 |
public List<Attr> inners() { return attrs; } |
|
1498 |
public List<Attr> attrs() { return attrs; } |
|
1499 |
public ClassFile outer() { return (ClassFile) outer; } |
|
1500 |
public String nameString() { return outer().pool.getString(CONSTANT_Utf8, name); } |
|
1501 |
public String typeString() { return outer().pool.getString(CONSTANT_Utf8, type); } |
|
1502 |
public String toString() { |
|
1503 |
if (outer == null) return super.toString(); |
|
1504 |
return nameString() + (this instanceof Method ? "" : ":") |
|
1505 |
+ simplifyType(typeString()); |
|
1506 |
} |
|
1507 |
} |
|
1508 |
public static class Field extends Member { |
|
1509 |
} |
|
1510 |
public static class Method extends Member { |
|
1511 |
public Code code() { |
|
1512 |
Attr a = findAttr("Code"); |
|
1513 |
if (a == null) return null; |
|
1514 |
return (Code) a.item; |
|
1515 |
} |
|
1516 |
public Instruction instructions() { |
|
1517 |
Code code = code(); |
|
1518 |
if (code == null) return null; |
|
1519 |
return code.instructions(); |
|
1520 |
} |
|
1521 |
} |
|
1522 |
||
1523 |
public static class Attr extends InnerOuter implements Chunk { |
|
1524 |
public short name; |
|
1525 |
public int size = -1; // no pre-declared size |
|
1526 |
public Object item; |
|
1527 |
||
1528 |
public Attr() {} |
|
1529 |
public Attr(Outer outer, String name, Object item) { |
|
1530 |
ClassFile cf = outer.outer(ClassFile.class); |
|
1531 |
linkOuter(outer); |
|
1532 |
this.name = (short) cf.pool.stringIndex(name, true); |
|
1533 |
this.item = item; |
|
1534 |
outer.attrs().add(this); |
|
1535 |
} |
|
1536 |
public void readFrom(DataInputStream in) throws IOException { |
|
1537 |
name = in.readShort(); |
|
1538 |
size = in.readInt(); |
|
1539 |
item = readRawBytes(in, size); |
|
1540 |
} |
|
1541 |
public void writeTo(DataOutputStream out) throws IOException { |
|
1542 |
out.writeShort(name); |
|
1543 |
// write the 4-byte size header and then the contents: |
|
1544 |
byte[] bytes; |
|
1545 |
int trueSize; |
|
1546 |
if (item instanceof byte[]) { |
|
1547 |
bytes = (byte[]) item; |
|
1548 |
out.writeInt(trueSize = bytes.length); |
|
1549 |
out.write(bytes); |
|
1550 |
} else { |
|
1551 |
trueSize = flatten(out); |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
1552 |
//if (!(item instanceof Code)) System.err.println("wrote complex attr name="+(int)(char)name+" size="+trueSize+" data="+Arrays.toString(flatten())); |
7558 | 1553 |
} |
1554 |
if (trueSize != size && size >= 0) |
|
1555 |
System.err.println("warning: attribute size changed "+size+" to "+trueSize); |
|
1556 |
} |
|
1557 |
public void linkOuter(Outer o) { |
|
1558 |
super.linkOuter(o); |
|
1559 |
if (item instanceof byte[] && |
|
1560 |
outer instanceof Method && |
|
1561 |
((Method)outer).outer().Code_index == name) { |
|
1562 |
item = readInput((byte[])item, Code.class); |
|
1563 |
} |
|
1564 |
} |
|
1565 |
public List<Inner> inners() { |
|
1566 |
if (item instanceof Inner) |
|
1567 |
return Collections.nCopies(1, (Inner)item); |
|
1568 |
return Collections.emptyList(); |
|
1569 |
} |
|
1570 |
public List<Attr> attrs() { return null; } // Code overrides this |
|
1571 |
public byte[] flatten() { |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
1572 |
ByteArrayOutputStream buf = new ByteArrayOutputStream(Math.max(20, size)); |
7558 | 1573 |
flatten(buf); |
1574 |
return buf.toByteArray(); |
|
1575 |
} |
|
1576 |
public int flatten(DataOutputStream out) throws IOException { |
|
1577 |
ByteArrayOutputStream buf = new ByteArrayOutputStream(Math.max(20, size)); |
|
1578 |
int trueSize = flatten(buf); |
|
1579 |
out.writeInt(trueSize); |
|
1580 |
buf.writeTo(out); |
|
1581 |
return trueSize; |
|
1582 |
} |
|
1583 |
private int flatten(ByteArrayOutputStream buf) { |
|
1584 |
try { |
|
1585 |
writeOutput(new DataOutputStream(buf), item); |
|
1586 |
return buf.size(); |
|
1587 |
} catch (IOException ex) { |
|
1588 |
throw new InternalError(); |
|
1589 |
} |
|
1590 |
} |
|
1591 |
public String nameString() { |
|
1592 |
ClassFile cf = outer(ClassFile.class); |
|
1593 |
if (cf == null) return "#"+name; |
|
1594 |
return cf.pool.getString(name); |
|
1595 |
} |
|
1596 |
public String toString() { |
|
1597 |
return nameString()+(size < 0 ? "=" : "["+size+"]=")+item; |
|
1598 |
} |
|
1599 |
} |
|
1600 |
||
1601 |
public static class Code extends InnerOuter implements Chunk { |
|
1602 |
public short stacks, locals; |
|
1603 |
public byte[] bytes; |
|
1604 |
public final List<Short[]> etable = new CountedList<>(Short[].class, 4); |
|
1605 |
public final List<Attr> attrs = new CountedList<>(Attr.class); |
|
1606 |
// etable[N] = (N)*{ startpc, endpc, handlerpc, catchtype } |
|
1607 |
public void readFrom(DataInputStream in) throws IOException { |
|
1608 |
stacks = in.readShort(); locals = in.readShort(); |
|
1609 |
bytes = readRawBytes(in, in.readInt()); |
|
1610 |
readInputs(in, etable, attrs); |
|
1611 |
} |
|
1612 |
public void writeTo(DataOutputStream out) throws IOException { |
|
1613 |
writeOutputs(out, stacks, locals, bytes.length, bytes, etable, attrs); |
|
1614 |
} |
|
1615 |
public List<Attr> inners() { return attrs; } |
|
1616 |
public List<Attr> attrs() { return attrs; } |
|
1617 |
public Instruction instructions() { |
|
1618 |
return new Instruction(bytes, 0); |
|
1619 |
} |
|
1620 |
} |
|
1621 |
||
1622 |
// lots of constants |
|
1623 |
private static final byte |
|
1624 |
CONSTANT_Utf8 = 1, |
|
1625 |
CONSTANT_Integer = 3, |
|
1626 |
CONSTANT_Float = 4, |
|
1627 |
CONSTANT_Long = 5, |
|
1628 |
CONSTANT_Double = 6, |
|
1629 |
CONSTANT_Class = 7, |
|
1630 |
CONSTANT_String = 8, |
|
1631 |
CONSTANT_Field = 9, |
|
1632 |
CONSTANT_Method = 10, |
|
1633 |
CONSTANT_InterfaceMethod = 11, |
|
1634 |
CONSTANT_NameAndType = 12, |
|
1635 |
CONSTANT_MethodHandle = 15, // JSR 292 |
|
1636 |
CONSTANT_MethodType = 16, // JSR 292 |
|
1637 |
CONSTANT_InvokeDynamic_17 = 17, // JSR 292, only occurs in old class files |
|
1638 |
CONSTANT_InvokeDynamic = 18; // JSR 292 |
|
1639 |
private static final byte |
|
1640 |
REF_getField = 1, |
|
1641 |
REF_getStatic = 2, |
|
1642 |
REF_putField = 3, |
|
1643 |
REF_putStatic = 4, |
|
1644 |
REF_invokeVirtual = 5, |
|
1645 |
REF_invokeStatic = 6, |
|
1646 |
REF_invokeSpecial = 7, |
|
1647 |
REF_newInvokeSpecial = 8, |
|
1648 |
REF_invokeInterface = 9; |
|
1649 |
||
1650 |
private static final int |
|
1651 |
opc_nop = 0, |
|
1652 |
opc_aconst_null = 1, |
|
1653 |
opc_nconst_MIN = 2, // iconst_m1 |
|
1654 |
opc_nconst_MAX = 15, // dconst_1 |
|
1655 |
opc_bipush = 16, |
|
1656 |
opc_sipush = 17, |
|
1657 |
opc_ldc = 18, |
|
1658 |
opc_ldc_w = 19, |
|
1659 |
opc_ldc2_w = 20, |
|
1660 |
opc_aload = 25, |
|
1661 |
opc_aload_0 = 42, |
|
1662 |
opc_aload_MAX = 45, |
|
1663 |
opc_aaload = 50, |
|
1664 |
opc_astore = 58, |
|
1665 |
opc_astore_0 = 75, |
|
1666 |
opc_astore_MAX = 78, |
|
1667 |
opc_aastore = 83, |
|
1668 |
opc_pop = 87, |
|
1669 |
opc_pop2 = 88, |
|
1670 |
opc_dup = 89, |
|
1671 |
opc_dup_x1 = 90, |
|
1672 |
opc_dup_x2 = 91, |
|
1673 |
opc_dup2 = 92, |
|
1674 |
opc_dup2_x1 = 93, |
|
1675 |
opc_dup2_x2 = 94, |
|
1676 |
opc_swap = 95, |
|
1677 |
opc_tableswitch = 170, |
|
1678 |
opc_lookupswitch = 171, |
|
1679 |
opc_areturn = 176, |
|
1680 |
opc_getstatic = 178, |
|
1681 |
opc_putstatic = 179, |
|
1682 |
opc_getfield = 180, |
|
1683 |
opc_putfield = 181, |
|
1684 |
opc_invokevirtual = 182, |
|
1685 |
opc_invokespecial = 183, |
|
1686 |
opc_invokestatic = 184, |
|
1687 |
opc_invokeinterface = 185, |
|
1688 |
opc_invokedynamic = 186, |
|
8345
9e2483e6cfab
7013417: JSR 292 needs to support variadic method handle calls
jrose
parents:
7558
diff
changeset
|
1689 |
opc_new = 187, |
7558 | 1690 |
opc_anewarray = 189, |
1691 |
opc_checkcast = 192, |
|
1692 |
opc_ifnull = 198, |
|
1693 |
opc_ifnonnull = 199, |
|
1694 |
opc_wide = 196; |
|
1695 |
||
1696 |
private static final Object[] INSTRUCTION_CONSTANTS = { |
|
1697 |
-1, 0, 1, 2, 3, 4, 5, 0L, 1L, 0.0F, 1.0F, 2.0F, 0.0D, 1.0D |
|
1698 |
}; |
|
1699 |
||
1700 |
private static final String INSTRUCTION_FORMATS = |
|
1701 |
"nop$ aconst_null$L iconst_m1$I iconst_0$I iconst_1$I "+ |
|
1702 |
"iconst_2$I iconst_3$I iconst_4$I iconst_5$I lconst_0$J_ "+ |
|
1703 |
"lconst_1$J_ fconst_0$F fconst_1$F fconst_2$F dconst_0$D_ "+ |
|
1704 |
"dconst_1$D_ bipush=bx$I sipush=bxx$I ldc=bk$X ldc_w=bkk$X "+ |
|
1705 |
"ldc2_w=bkk$X_ iload=bl/wbll$I lload=bl/wbll$J_ fload=bl/wbll$F "+ |
|
1706 |
"dload=bl/wbll$D_ aload=bl/wbll$L iload_0$I iload_1$I "+ |
|
1707 |
"iload_2$I iload_3$I lload_0$J_ lload_1$J_ lload_2$J_ "+ |
|
1708 |
"lload_3$J_ fload_0$F fload_1$F fload_2$F fload_3$F dload_0$D_ "+ |
|
1709 |
"dload_1$D_ dload_2$D_ dload_3$D_ aload_0$L aload_1$L "+ |
|
1710 |
"aload_2$L aload_3$L iaload$LI$I laload$LI$J_ faload$LI$F "+ |
|
1711 |
"daload$LI$D_ aaload$LI$L baload$LI$I caload$LI$I saload$LI$I "+ |
|
1712 |
"istore=bl/wbll$I$ lstore=bl/wbll$J_$ fstore=bl/wbll$F$ "+ |
|
1713 |
"dstore=bl/wbll$D_$ astore=bl/wbll$L$ istore_0$I$ istore_1$I$ "+ |
|
1714 |
"istore_2$I$ istore_3$I$ lstore_0$J_$ lstore_1$J_$ "+ |
|
1715 |
"lstore_2$J_$ lstore_3$J_$ fstore_0$F$ fstore_1$F$ fstore_2$F$ "+ |
|
1716 |
"fstore_3$F$ dstore_0$D_$ dstore_1$D_$ dstore_2$D_$ "+ |
|
1717 |
"dstore_3$D_$ astore_0$L$ astore_1$L$ astore_2$L$ astore_3$L$ "+ |
|
1718 |
"iastore$LII$ lastore$LIJ_$ fastore$LIF$ dastore$LID_$ "+ |
|
1719 |
"aastore$LIL$ bastore$LII$ castore$LII$ sastore$LII$ pop$X$ "+ |
|
1720 |
"pop2$XX$ dup$X$XX dup_x1$XX$XXX dup_x2$XXX$XXXX dup2$XX$XXXX "+ |
|
1721 |
"dup2_x1$XXX$XXXXX dup2_x2$XXXX$XXXXXX swap$XX$XX "+ |
|
1722 |
"iadd$II$I ladd$J_J_$J_ fadd$FF$F dadd$D_D_$D_ isub$II$I "+ |
|
1723 |
"lsub$J_J_$J_ fsub$FF$F dsub$D_D_$D_ imul$II$I lmul$J_J_$J_ "+ |
|
1724 |
"fmul$FF$F dmul$D_D_$D_ idiv$II$I ldiv$J_J_$J_ fdiv$FF$F "+ |
|
1725 |
"ddiv$D_D_$D_ irem$II$I lrem$J_J_$J_ frem$FF$F drem$D_D_$D_ "+ |
|
1726 |
"ineg$I$I lneg$J_$J_ fneg$F$F dneg$D_$D_ ishl$II$I lshl$J_I$J_ "+ |
|
1727 |
"ishr$II$I lshr$J_I$J_ iushr$II$I lushr$J_I$J_ iand$II$I "+ |
|
1728 |
"land$J_J_$J_ ior$II$I lor$J_J_$J_ ixor$II$I lxor$J_J_$J_ "+ |
|
1729 |
"iinc=blx/wbllxx$ i2l$I$J_ i2f$I$F i2d$I$D_ l2i$J_$I l2f$J_$F "+ |
|
1730 |
"l2d$J_$D_ f2i$F$I f2l$F$J_ f2d$F$D_ d2i$D_$I d2l$D_$J_ "+ |
|
1731 |
"d2f$D_$F i2b$I$I i2c$I$I i2s$I$I lcmp fcmpl fcmpg dcmpl dcmpg "+ |
|
1732 |
"ifeq=boo ifne=boo iflt=boo ifge=boo ifgt=boo ifle=boo "+ |
|
1733 |
"if_icmpeq=boo if_icmpne=boo if_icmplt=boo if_icmpge=boo "+ |
|
1734 |
"if_icmpgt=boo if_icmple=boo if_acmpeq=boo if_acmpne=boo "+ |
|
1735 |
"goto=boo jsr=boo ret=bl/wbll tableswitch=* lookupswitch=* "+ |
|
1736 |
"ireturn lreturn freturn dreturn areturn return "+ |
|
1737 |
"getstatic=bkf$Q putstatic=bkf$Q$ getfield=bkf$L$Q "+ |
|
1738 |
"putfield=bkf$LQ$ invokevirtual=bkm$LQ$Q "+ |
|
1739 |
"invokespecial=bkm$LQ$Q invokestatic=bkm$Q$Q "+ |
|
1740 |
"invokeinterface=bkixx$LQ$Q invokedynamic=bkd__$Q$Q new=bkc$L "+ |
|
1741 |
"newarray=bx$I$L anewarray=bkc$I$L arraylength$L$I athrow "+ |
|
1742 |
"checkcast=bkc$L$L instanceof=bkc$L$I monitorenter$L "+ |
|
1743 |
"monitorexit$L wide=* multianewarray=bkcx ifnull=boo "+ |
|
1744 |
"ifnonnull=boo goto_w=boooo jsr_w=boooo "; |
|
1745 |
private static final String[] INSTRUCTION_NAMES; |
|
1746 |
private static final String[] INSTRUCTION_POPS; |
|
1747 |
private static final int[] INSTRUCTION_INFO; |
|
1748 |
static { |
|
1749 |
String[] insns = INSTRUCTION_FORMATS.split(" "); |
|
1750 |
assert(insns[opc_lookupswitch].startsWith("lookupswitch")); |
|
1751 |
assert(insns[opc_tableswitch].startsWith("tableswitch")); |
|
1752 |
assert(insns[opc_wide].startsWith("wide")); |
|
1753 |
assert(insns[opc_invokedynamic].startsWith("invokedynamic")); |
|
1754 |
int[] info = new int[256]; |
|
1755 |
String[] names = new String[256]; |
|
1756 |
String[] pops = new String[256]; |
|
1757 |
for (int i = 0; i < insns.length; i++) { |
|
1758 |
String insn = insns[i]; |
|
1759 |
int dl = insn.indexOf('$'); |
|
1760 |
if (dl > 0) { |
|
1761 |
String p = insn.substring(dl+1); |
|
1762 |
if (p.indexOf('$') < 0) p = "$" + p; |
|
1763 |
pops[i] = p; |
|
1764 |
insn = insn.substring(0, dl); |
|
1765 |
} |
|
1766 |
int eq = insn.indexOf('='); |
|
1767 |
if (eq < 0) { |
|
1768 |
info[i] = 1; |
|
1769 |
names[i] = insn; |
|
1770 |
continue; |
|
1771 |
} |
|
1772 |
names[i] = insn.substring(0, eq); |
|
1773 |
String fmt = insn.substring(eq+1); |
|
1774 |
if (fmt.equals("*")) { |
|
1775 |
info[i] = 0; |
|
1776 |
continue; |
|
1777 |
} |
|
1778 |
int sl = fmt.indexOf('/'); |
|
1779 |
if (sl < 0) { |
|
1780 |
info[i] = (char) fmt.length(); |
|
1781 |
} else { |
|
1782 |
String wfmt = fmt.substring(sl+1); |
|
1783 |
fmt = fmt.substring(0, sl); |
|
1784 |
info[i] = (char)( fmt.length() + (wfmt.length() * 16) ); |
|
1785 |
} |
|
1786 |
} |
|
1787 |
INSTRUCTION_INFO = info; |
|
1788 |
INSTRUCTION_NAMES = names; |
|
1789 |
INSTRUCTION_POPS = pops; |
|
1790 |
} |
|
1791 |
||
1792 |
public static class Instruction implements Cloneable { |
|
1793 |
byte[] codeBase; |
|
1794 |
int pc; |
|
1795 |
int bc; |
|
1796 |
int info; |
|
1797 |
int wide; |
|
1798 |
int len; |
|
1799 |
Instruction(byte[] codeBase, int pc) { |
|
1800 |
this.codeBase = codeBase; |
|
1801 |
init(pc); |
|
1802 |
} |
|
1803 |
public Instruction clone() { |
|
1804 |
try { |
|
1805 |
return (Instruction) super.clone(); |
|
1806 |
} catch (CloneNotSupportedException ex) { |
|
1807 |
throw new InternalError(); |
|
1808 |
} |
|
1809 |
} |
|
1810 |
private Instruction init(int pc) { |
|
1811 |
this.pc = pc; |
|
1812 |
this.bc = codeBase[pc] & 0xFF; |
|
1813 |
this.info = INSTRUCTION_INFO[bc]; |
|
1814 |
this.wide = 0; |
|
1815 |
this.len = (info & 0x0F); |
|
1816 |
if (len == 0) |
|
1817 |
computeLength(); |
|
1818 |
return this; |
|
1819 |
} |
|
1820 |
Instruction next() { |
|
1821 |
if (len == 0 && bc != 0) throw new InternalError(); |
|
1822 |
int npc = pc + len; |
|
1823 |
if (npc == codeBase.length) |
|
1824 |
return null; |
|
1825 |
return init(npc); |
|
1826 |
} |
|
1827 |
void forceNext(int newLen) { |
|
1828 |
bc = opc_nop; |
|
1829 |
len = newLen; |
|
1830 |
} |
|
1831 |
||
1832 |
public String toString() { |
|
1833 |
StringBuilder buf = new StringBuilder(); |
|
1834 |
buf.append(pc).append(":").append(INSTRUCTION_NAMES[bc]); |
|
1835 |
switch (len) { |
|
1836 |
case 3: buf.append(" ").append(u2At(1)); break; |
|
1837 |
case 5: buf.append(" ").append(u2At(1)).append(" ").append(u2At(3)); break; |
|
1838 |
default: for (int i = 1; i < len; i++) buf.append(" ").append(u1At(1)); |
|
1839 |
} |
|
1840 |
return buf.toString(); |
|
1841 |
} |
|
1842 |
||
1843 |
// these are the hard parts |
|
1844 |
private void computeLength() { |
|
1845 |
int cases; |
|
1846 |
switch (bc) { |
|
1847 |
case opc_wide: |
|
1848 |
bc = codeBase[pc + 1]; |
|
1849 |
info = INSTRUCTION_INFO[bc]; |
|
1850 |
len = ((info >> 4) & 0x0F); |
|
1851 |
if (len == 0) throw new RuntimeException("misplaced wide bytecode: "+bc); |
|
1852 |
return; |
|
1853 |
||
1854 |
case opc_tableswitch: |
|
1855 |
cases = (u4At(alignedIntOffset(2)) - u4At(alignedIntOffset(1)) + 1); |
|
1856 |
len = alignedIntOffset(3 + cases*1); |
|
1857 |
return; |
|
1858 |
||
1859 |
case opc_lookupswitch: |
|
1860 |
cases = u4At(alignedIntOffset(1)); |
|
1861 |
len = alignedIntOffset(2 + cases*2); |
|
1862 |
return; |
|
1863 |
||
1864 |
default: |
|
1865 |
throw new RuntimeException("unknown bytecode: "+bc); |
|
1866 |
} |
|
1867 |
} |
|
1868 |
// switch code |
|
1869 |
// clget the Nth int (where 0 is the first after the opcode itself) |
|
1870 |
public int alignedIntOffset(int n) { |
|
1871 |
int pos = pc + 1; |
|
1872 |
pos += ((-pos) & 0x03); // align it |
|
1873 |
pos += (n * 4); |
|
1874 |
return pos - pc; |
|
1875 |
} |
|
1876 |
public int u1At(int pos) { |
|
1877 |
return (codeBase[pc+pos] & 0xFF); |
|
1878 |
} |
|
1879 |
public int u2At(int pos) { |
|
1880 |
return (u1At(pos+0)<<8) + u1At(pos+1); |
|
1881 |
} |
|
1882 |
public int u4At(int pos) { |
|
1883 |
return (u2At(pos+0)<<16) + u2At(pos+2); |
|
1884 |
} |
|
1885 |
public void u1AtPut(int pos, int x) { |
|
1886 |
codeBase[pc+pos] = (byte)x; |
|
1887 |
} |
|
1888 |
public void u2AtPut(int pos, int x) { |
|
1889 |
codeBase[pc+pos+0] = (byte)(x >> 8); |
|
1890 |
codeBase[pc+pos+1] = (byte)(x >> 0); |
|
1891 |
} |
|
1892 |
} |
|
1893 |
||
1894 |
static String simplifyType(String type) { |
|
1895 |
String simpleType = OBJ_SIGNATURE.matcher(type).replaceAll("L"); |
|
1896 |
assert(simpleType.matches("^\\([A-Z]*\\)[A-Z]$")); |
|
1897 |
// change (DD)D to (D_D_)D_ |
|
1898 |
simpleType = WIDE_SIGNATURE.matcher(simpleType).replaceAll("\\0_"); |
|
1899 |
return simpleType; |
|
1900 |
} |
|
1901 |
static int argsize(String type) { |
|
1902 |
return simplifyType(type).length()-3; |
|
1903 |
} |
|
1904 |
private static final Pattern OBJ_SIGNATURE = Pattern.compile("\\[*L[^;]*;|\\[+[A-Z]"); |
|
1905 |
private static final Pattern WIDE_SIGNATURE = Pattern.compile("[JD]"); |
|
1906 |
} |