1 /* |
|
2 * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. 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 sun.invoke.anon; |
|
27 |
|
28 import java.io.IOException; |
|
29 import java.io.OutputStream; |
|
30 import java.util.Arrays; |
|
31 import java.util.HashSet; |
|
32 import java.util.IdentityHashMap; |
|
33 import java.util.Map; |
|
34 |
|
35 import static sun.invoke.anon.ConstantPoolVisitor.*; |
|
36 |
|
37 /** A class and its patched constant pool. |
|
38 * |
|
39 * This class allow to modify (patch) a constant pool |
|
40 * by changing the value of its entry. |
|
41 * Entry are referenced using index that can be get |
|
42 * by parsing the constant pool using |
|
43 * {@link ConstantPoolParser#parse(ConstantPoolVisitor)}. |
|
44 * |
|
45 * @see ConstantPoolVisitor |
|
46 * @see ConstantPoolParser#createPatch() |
|
47 */ |
|
48 public class ConstantPoolPatch { |
|
49 final ConstantPoolParser outer; |
|
50 final Object[] patchArray; |
|
51 |
|
52 ConstantPoolPatch(ConstantPoolParser outer) { |
|
53 this.outer = outer; |
|
54 this.patchArray = new Object[outer.getLength()]; |
|
55 } |
|
56 |
|
57 /** Create a {@link ConstantPoolParser} and |
|
58 * a {@link ConstantPoolPatch} in one step. |
|
59 * Equivalent to {@code new ConstantPoolParser(classFile).createPatch()}. |
|
60 * |
|
61 * @param classFile an array of bytes containing a class. |
|
62 * @see #ConstantPoolParser(Class) |
|
63 */ |
|
64 public ConstantPoolPatch(byte[] classFile) throws InvalidConstantPoolFormatException { |
|
65 this(new ConstantPoolParser(classFile)); |
|
66 } |
|
67 |
|
68 /** Create a {@link ConstantPoolParser} and |
|
69 * a {@link ConstantPoolPatch} in one step. |
|
70 * Equivalent to {@code new ConstantPoolParser(templateClass).createPatch()}. |
|
71 * |
|
72 * @param templateClass the class to parse. |
|
73 * @see #ConstantPoolParser(Class) |
|
74 */ |
|
75 public ConstantPoolPatch(Class<?> templateClass) throws IOException, InvalidConstantPoolFormatException { |
|
76 this(new ConstantPoolParser(templateClass)); |
|
77 } |
|
78 |
|
79 |
|
80 /** Creates a patch from an existing patch. |
|
81 * All changes are copied from that patch. |
|
82 * @param patch a patch |
|
83 * |
|
84 * @see ConstantPoolParser#createPatch() |
|
85 */ |
|
86 public ConstantPoolPatch(ConstantPoolPatch patch) { |
|
87 outer = patch.outer; |
|
88 patchArray = patch.patchArray.clone(); |
|
89 } |
|
90 |
|
91 /** Which parser built this patch? */ |
|
92 public ConstantPoolParser getParser() { |
|
93 return outer; |
|
94 } |
|
95 |
|
96 /** Report the tag at the given index in the constant pool. */ |
|
97 public byte getTag(int index) { |
|
98 return outer.getTag(index); |
|
99 } |
|
100 |
|
101 /** Report the current patch at the given index of the constant pool. |
|
102 * Null means no patch will be made. |
|
103 * To observe the unpatched entry at the given index, use |
|
104 * {@link #getParser()}{@code .}@link ConstantPoolParser#parse(ConstantPoolVisitor)} |
|
105 */ |
|
106 public Object getPatch(int index) { |
|
107 Object value = patchArray[index]; |
|
108 if (value == null) return null; |
|
109 switch (getTag(index)) { |
|
110 case CONSTANT_Fieldref: |
|
111 case CONSTANT_Methodref: |
|
112 case CONSTANT_InterfaceMethodref: |
|
113 if (value instanceof String) |
|
114 value = stripSemis(2, (String) value); |
|
115 break; |
|
116 case CONSTANT_NameAndType: |
|
117 if (value instanceof String) |
|
118 value = stripSemis(1, (String) value); |
|
119 break; |
|
120 } |
|
121 return value; |
|
122 } |
|
123 |
|
124 /** Clear all patches. */ |
|
125 public void clear() { |
|
126 Arrays.fill(patchArray, null); |
|
127 } |
|
128 |
|
129 /** Clear one patch. */ |
|
130 public void clear(int index) { |
|
131 patchArray[index] = null; |
|
132 } |
|
133 |
|
134 /** Produce the patches as an array. */ |
|
135 public Object[] getPatches() { |
|
136 return patchArray.clone(); |
|
137 } |
|
138 |
|
139 /** Produce the original constant pool as an array. */ |
|
140 public Object[] getOriginalCP() throws InvalidConstantPoolFormatException { |
|
141 return getOriginalCP(0, patchArray.length, -1); |
|
142 } |
|
143 |
|
144 /** Walk the constant pool, applying patches using the given map. |
|
145 * |
|
146 * @param utf8Map Utf8 strings to modify, if encountered |
|
147 * @param classMap Classes (or their names) to modify, if encountered |
|
148 * @param valueMap Constant values to modify, if encountered |
|
149 * @param deleteUsedEntries if true, delete map entries that are used |
|
150 */ |
|
151 public void putPatches(final Map<String,String> utf8Map, |
|
152 final Map<String,Object> classMap, |
|
153 final Map<Object,Object> valueMap, |
|
154 boolean deleteUsedEntries) throws InvalidConstantPoolFormatException { |
|
155 final HashSet<String> usedUtf8Keys; |
|
156 final HashSet<String> usedClassKeys; |
|
157 final HashSet<Object> usedValueKeys; |
|
158 if (deleteUsedEntries) { |
|
159 usedUtf8Keys = (utf8Map == null) ? null : new HashSet<String>(); |
|
160 usedClassKeys = (classMap == null) ? null : new HashSet<String>(); |
|
161 usedValueKeys = (valueMap == null) ? null : new HashSet<Object>(); |
|
162 } else { |
|
163 usedUtf8Keys = null; |
|
164 usedClassKeys = null; |
|
165 usedValueKeys = null; |
|
166 } |
|
167 |
|
168 outer.parse(new ConstantPoolVisitor() { |
|
169 |
|
170 @Override |
|
171 public void visitUTF8(int index, byte tag, String utf8) { |
|
172 putUTF8(index, utf8Map.get(utf8)); |
|
173 if (usedUtf8Keys != null) usedUtf8Keys.add(utf8); |
|
174 } |
|
175 |
|
176 @Override |
|
177 public void visitConstantValue(int index, byte tag, Object value) { |
|
178 putConstantValue(index, tag, valueMap.get(value)); |
|
179 if (usedValueKeys != null) usedValueKeys.add(value); |
|
180 } |
|
181 |
|
182 @Override |
|
183 public void visitConstantString(int index, byte tag, String name, int nameIndex) { |
|
184 if (tag == CONSTANT_Class) { |
|
185 putConstantValue(index, tag, classMap.get(name)); |
|
186 if (usedClassKeys != null) usedClassKeys.add(name); |
|
187 } else { |
|
188 assert(tag == CONSTANT_String); |
|
189 visitConstantValue(index, tag, name); |
|
190 } |
|
191 } |
|
192 }); |
|
193 if (usedUtf8Keys != null) utf8Map.keySet().removeAll(usedUtf8Keys); |
|
194 if (usedClassKeys != null) classMap.keySet().removeAll(usedClassKeys); |
|
195 if (usedValueKeys != null) valueMap.keySet().removeAll(usedValueKeys); |
|
196 } |
|
197 |
|
198 Object[] getOriginalCP(final int startIndex, |
|
199 final int endIndex, |
|
200 final int tagMask) throws InvalidConstantPoolFormatException { |
|
201 final Object[] cpArray = new Object[endIndex - startIndex]; |
|
202 outer.parse(new ConstantPoolVisitor() { |
|
203 |
|
204 void show(int index, byte tag, Object value) { |
|
205 if (index < startIndex || index >= endIndex) return; |
|
206 if (((1 << tag) & tagMask) == 0) return; |
|
207 cpArray[index - startIndex] = value; |
|
208 } |
|
209 |
|
210 @Override |
|
211 public void visitUTF8(int index, byte tag, String utf8) { |
|
212 show(index, tag, utf8); |
|
213 } |
|
214 |
|
215 @Override |
|
216 public void visitConstantValue(int index, byte tag, Object value) { |
|
217 assert(tag != CONSTANT_String); |
|
218 show(index, tag, value); |
|
219 } |
|
220 |
|
221 @Override |
|
222 public void visitConstantString(int index, byte tag, |
|
223 String value, int j) { |
|
224 show(index, tag, value); |
|
225 } |
|
226 |
|
227 @Override |
|
228 public void visitMemberRef(int index, byte tag, |
|
229 String className, String memberName, |
|
230 String signature, |
|
231 int j, int k) { |
|
232 show(index, tag, new String[]{ className, memberName, signature }); |
|
233 } |
|
234 |
|
235 @Override |
|
236 public void visitDescriptor(int index, byte tag, |
|
237 String memberName, String signature, |
|
238 int j, int k) { |
|
239 show(index, tag, new String[]{ memberName, signature }); |
|
240 } |
|
241 }); |
|
242 return cpArray; |
|
243 } |
|
244 |
|
245 /** Write the head (header plus constant pool) |
|
246 * of the patched class file to the indicated stream. |
|
247 */ |
|
248 void writeHead(OutputStream out) throws IOException { |
|
249 outer.writePatchedHead(out, patchArray); |
|
250 } |
|
251 |
|
252 /** Write the tail (everything after the constant pool) |
|
253 * of the patched class file to the indicated stream. |
|
254 */ |
|
255 void writeTail(OutputStream out) throws IOException { |
|
256 outer.writeTail(out); |
|
257 } |
|
258 |
|
259 private void checkConstantTag(byte tag, Object value) { |
|
260 if (value == null) |
|
261 throw new IllegalArgumentException( |
|
262 "invalid null constant value"); |
|
263 if (classForTag(tag) != value.getClass()) |
|
264 throw new IllegalArgumentException( |
|
265 "invalid constant value" |
|
266 + (tag == CONSTANT_None ? "" |
|
267 : " for tag "+tagName(tag)) |
|
268 + " of class "+value.getClass()); |
|
269 } |
|
270 |
|
271 private void checkTag(int index, byte putTag) { |
|
272 byte tag = outer.tags[index]; |
|
273 if (tag != putTag) |
|
274 throw new IllegalArgumentException( |
|
275 "invalid put operation" |
|
276 + " for " + tagName(putTag) |
|
277 + " at index " + index + " found " + tagName(tag)); |
|
278 } |
|
279 |
|
280 private void checkTagMask(int index, int tagBitMask) { |
|
281 byte tag = outer.tags[index]; |
|
282 int tagBit = ((tag & 0x1F) == tag) ? (1 << tag) : 0; |
|
283 if ((tagBit & tagBitMask) == 0) |
|
284 throw new IllegalArgumentException( |
|
285 "invalid put operation" |
|
286 + " at index " + index + " found " + tagName(tag)); |
|
287 } |
|
288 |
|
289 private static void checkMemberName(String memberName) { |
|
290 if (memberName.indexOf(';') >= 0) |
|
291 throw new IllegalArgumentException("memberName " + memberName + " contains a ';'"); |
|
292 } |
|
293 |
|
294 /** Set the entry of the constant pool indexed by index to |
|
295 * a new string. |
|
296 * |
|
297 * @param index an index to a constant pool entry containing a |
|
298 * {@link ConstantPoolVisitor#CONSTANT_Utf8} value. |
|
299 * @param utf8 a string |
|
300 * |
|
301 * @see ConstantPoolVisitor#visitUTF8(int, byte, String) |
|
302 */ |
|
303 public void putUTF8(int index, String utf8) { |
|
304 if (utf8 == null) { clear(index); return; } |
|
305 checkTag(index, CONSTANT_Utf8); |
|
306 patchArray[index] = utf8; |
|
307 } |
|
308 |
|
309 /** Set the entry of the constant pool indexed by index to |
|
310 * a new value, depending on its dynamic type. |
|
311 * |
|
312 * @param index an index to a constant pool entry containing a |
|
313 * one of the following structures: |
|
314 * {@link ConstantPoolVisitor#CONSTANT_Integer}, |
|
315 * {@link ConstantPoolVisitor#CONSTANT_Float}, |
|
316 * {@link ConstantPoolVisitor#CONSTANT_Long}, |
|
317 * {@link ConstantPoolVisitor#CONSTANT_Double}, |
|
318 * {@link ConstantPoolVisitor#CONSTANT_String}, or |
|
319 * {@link ConstantPoolVisitor#CONSTANT_Class} |
|
320 * @param value a boxed int, float, long or double; or a string or class object |
|
321 * @throws IllegalArgumentException if the type of the constant does not |
|
322 * match the constant pool entry type, |
|
323 * as reported by {@link #getTag(int)} |
|
324 * |
|
325 * @see #putConstantValue(int, byte, Object) |
|
326 * @see ConstantPoolVisitor#visitConstantValue(int, byte, Object) |
|
327 * @see ConstantPoolVisitor#visitConstantString(int, byte, String, int) |
|
328 */ |
|
329 public void putConstantValue(int index, Object value) { |
|
330 if (value == null) { clear(index); return; } |
|
331 byte tag = tagForConstant(value.getClass()); |
|
332 checkConstantTag(tag, value); |
|
333 checkTag(index, tag); |
|
334 patchArray[index] = value; |
|
335 } |
|
336 |
|
337 /** Set the entry of the constant pool indexed by index to |
|
338 * a new value. |
|
339 * |
|
340 * @param index an index to a constant pool entry matching the given tag |
|
341 * @param tag one of the following values: |
|
342 * {@link ConstantPoolVisitor#CONSTANT_Integer}, |
|
343 * {@link ConstantPoolVisitor#CONSTANT_Float}, |
|
344 * {@link ConstantPoolVisitor#CONSTANT_Long}, |
|
345 * {@link ConstantPoolVisitor#CONSTANT_Double}, |
|
346 * {@link ConstantPoolVisitor#CONSTANT_String}, or |
|
347 * {@link ConstantPoolVisitor#CONSTANT_Class} |
|
348 * @param value a boxed number, string, or class object |
|
349 * @throws IllegalArgumentException if the type of the constant does not |
|
350 * match the constant pool entry type, or if a class name contains |
|
351 * '/' or ';' |
|
352 * |
|
353 * @see #putConstantValue(int, Object) |
|
354 * @see ConstantPoolVisitor#visitConstantValue(int, byte, Object) |
|
355 * @see ConstantPoolVisitor#visitConstantString(int, byte, String, int) |
|
356 */ |
|
357 public void putConstantValue(int index, byte tag, Object value) { |
|
358 if (value == null) { clear(index); return; } |
|
359 checkTag(index, tag); |
|
360 if (tag == CONSTANT_Class && value instanceof String) { |
|
361 checkClassName((String) value); |
|
362 } else if (tag == CONSTANT_String) { |
|
363 // the JVM accepts any object as a patch for a string |
|
364 } else { |
|
365 // make sure the incoming value is the right type |
|
366 checkConstantTag(tag, value); |
|
367 } |
|
368 checkTag(index, tag); |
|
369 patchArray[index] = value; |
|
370 } |
|
371 |
|
372 /** Set the entry of the constant pool indexed by index to |
|
373 * a new {@link ConstantPoolVisitor#CONSTANT_NameAndType} value. |
|
374 * |
|
375 * @param index an index to a constant pool entry containing a |
|
376 * {@link ConstantPoolVisitor#CONSTANT_NameAndType} value. |
|
377 * @param memberName a memberName |
|
378 * @param signature a signature |
|
379 * @throws IllegalArgumentException if memberName contains the character ';' |
|
380 * |
|
381 * @see ConstantPoolVisitor#visitDescriptor(int, byte, String, String, int, int) |
|
382 */ |
|
383 public void putDescriptor(int index, String memberName, String signature) { |
|
384 checkTag(index, CONSTANT_NameAndType); |
|
385 checkMemberName(memberName); |
|
386 patchArray[index] = addSemis(memberName, signature); |
|
387 } |
|
388 |
|
389 /** Set the entry of the constant pool indexed by index to |
|
390 * a new {@link ConstantPoolVisitor#CONSTANT_Fieldref}, |
|
391 * {@link ConstantPoolVisitor#CONSTANT_Methodref}, or |
|
392 * {@link ConstantPoolVisitor#CONSTANT_InterfaceMethodref} value. |
|
393 * |
|
394 * @param index an index to a constant pool entry containing a member reference |
|
395 * @param className a class name |
|
396 * @param memberName a field or method name |
|
397 * @param signature a field or method signature |
|
398 * @throws IllegalArgumentException if memberName contains the character ';' |
|
399 * or signature is not a correct signature |
|
400 * |
|
401 * @see ConstantPoolVisitor#visitMemberRef(int, byte, String, String, String, int, int) |
|
402 */ |
|
403 public void putMemberRef(int index, byte tag, |
|
404 String className, String memberName, String signature) { |
|
405 checkTagMask(tag, CONSTANT_MemberRef_MASK); |
|
406 checkTag(index, tag); |
|
407 checkClassName(className); |
|
408 checkMemberName(memberName); |
|
409 if (signature.startsWith("(") == (tag == CONSTANT_Fieldref)) |
|
410 throw new IllegalArgumentException("bad signature: "+signature); |
|
411 patchArray[index] = addSemis(className, memberName, signature); |
|
412 } |
|
413 |
|
414 private static final int CONSTANT_MemberRef_MASK = |
|
415 CONSTANT_Fieldref |
|
416 | CONSTANT_Methodref |
|
417 | CONSTANT_InterfaceMethodref; |
|
418 |
|
419 private static final Map<Class<?>, Byte> CONSTANT_VALUE_CLASS_TAG |
|
420 = new IdentityHashMap<Class<?>, Byte>(6); |
|
421 private static final Class<?>[] CONSTANT_VALUE_CLASS = new Class<?>[16]; |
|
422 static { |
|
423 Object[][] values = { |
|
424 {Integer.class, CONSTANT_Integer}, |
|
425 {Long.class, CONSTANT_Long}, |
|
426 {Float.class, CONSTANT_Float}, |
|
427 {Double.class, CONSTANT_Double}, |
|
428 {String.class, CONSTANT_String}, |
|
429 {Class.class, CONSTANT_Class} |
|
430 }; |
|
431 for (Object[] value : values) { |
|
432 Class<?> cls = (Class<?>)value[0]; |
|
433 Byte tag = (Byte) value[1]; |
|
434 CONSTANT_VALUE_CLASS_TAG.put(cls, tag); |
|
435 CONSTANT_VALUE_CLASS[(byte)tag] = cls; |
|
436 } |
|
437 } |
|
438 |
|
439 static Class<?> classForTag(byte tag) { |
|
440 if ((tag & 0xFF) >= CONSTANT_VALUE_CLASS.length) |
|
441 return null; |
|
442 return CONSTANT_VALUE_CLASS[tag]; |
|
443 } |
|
444 |
|
445 static byte tagForConstant(Class<?> cls) { |
|
446 Byte tag = CONSTANT_VALUE_CLASS_TAG.get(cls); |
|
447 return (tag == null) ? CONSTANT_None : (byte)tag; |
|
448 } |
|
449 |
|
450 private static void checkClassName(String className) { |
|
451 if (className.indexOf('/') >= 0 || className.indexOf(';') >= 0) |
|
452 throw new IllegalArgumentException("invalid class name " + className); |
|
453 } |
|
454 |
|
455 static String addSemis(String name, String... names) { |
|
456 StringBuilder buf = new StringBuilder(name.length() * 5); |
|
457 buf.append(name); |
|
458 for (String name2 : names) { |
|
459 buf.append(';').append(name2); |
|
460 } |
|
461 String res = buf.toString(); |
|
462 assert(stripSemis(names.length, res)[0].equals(name)); |
|
463 assert(stripSemis(names.length, res)[1].equals(names[0])); |
|
464 assert(names.length == 1 || |
|
465 stripSemis(names.length, res)[2].equals(names[1])); |
|
466 return res; |
|
467 } |
|
468 |
|
469 static String[] stripSemis(int count, String string) { |
|
470 String[] res = new String[count+1]; |
|
471 int pos = 0; |
|
472 for (int i = 0; i < count; i++) { |
|
473 int pos2 = string.indexOf(';', pos); |
|
474 if (pos2 < 0) pos2 = string.length(); // yuck |
|
475 res[i] = string.substring(pos, pos2); |
|
476 pos = pos2; |
|
477 } |
|
478 res[count] = string.substring(pos); |
|
479 return res; |
|
480 } |
|
481 |
|
482 public String toString() { |
|
483 StringBuilder buf = new StringBuilder(this.getClass().getName()); |
|
484 buf.append("{"); |
|
485 Object[] origCP = null; |
|
486 for (int i = 0; i < patchArray.length; i++) { |
|
487 if (patchArray[i] == null) continue; |
|
488 if (origCP != null) { |
|
489 buf.append(", "); |
|
490 } else { |
|
491 try { |
|
492 origCP = getOriginalCP(); |
|
493 } catch (InvalidConstantPoolFormatException ee) { |
|
494 origCP = new Object[0]; |
|
495 } |
|
496 } |
|
497 Object orig = (i < origCP.length) ? origCP[i] : "?"; |
|
498 buf.append(orig).append("=").append(patchArray[i]); |
|
499 } |
|
500 buf.append("}"); |
|
501 return buf.toString(); |
|
502 } |
|
503 } |
|