Lines 44-50
Link Here
|
44 |
|
44 |
|
45 |
package org.netbeans; |
45 |
package org.netbeans; |
46 |
|
46 |
|
|
|
47 |
import java.io.IOException; |
48 |
import java.io.InputStream; |
49 |
import java.io.InputStreamReader; |
47 |
import java.io.UnsupportedEncodingException; |
50 |
import java.io.UnsupportedEncodingException; |
|
|
51 |
import java.net.URL; |
52 |
import java.util.Collection; |
53 |
import java.util.Enumeration; |
54 |
import java.util.HashMap; |
55 |
import java.util.Iterator; |
56 |
import java.util.Map; |
57 |
import java.util.Properties; |
58 |
import java.util.logging.Logger; |
59 |
import org.objectweb.asm.ClassReader; |
60 |
import org.objectweb.asm.ClassWriter; |
61 |
import org.objectweb.asm.Opcodes; |
62 |
import org.objectweb.asm.signature.SignatureReader; |
63 |
import org.objectweb.asm.signature.SignatureVisitor; |
64 |
import org.objectweb.asm.tree.AbstractInsnNode; |
65 |
import org.objectweb.asm.tree.AnnotationNode; |
66 |
import org.objectweb.asm.tree.ClassNode; |
67 |
import org.objectweb.asm.tree.MethodInsnNode; |
68 |
import org.objectweb.asm.tree.MethodNode; |
48 |
import org.openide.modules.PatchedPublic; |
69 |
import org.openide.modules.PatchedPublic; |
49 |
|
70 |
|
50 |
/** |
71 |
/** |
Lines 54-70
Link Here
|
54 |
* @see #patch |
75 |
* @see #patch |
55 |
*/ |
76 |
*/ |
56 |
public final class PatchByteCode { |
77 |
public final class PatchByteCode { |
57 |
|
78 |
private static final Logger LOG = Logger.getLogger(PatchByteCode.class.getName()); |
|
|
79 |
|
58 |
private static final byte[] RUNTIME_INVISIBLE_ANNOTATIONS, PATCHED_PUBLIC; |
80 |
private static final byte[] RUNTIME_INVISIBLE_ANNOTATIONS, PATCHED_PUBLIC; |
|
|
81 |
private static final String DESC_CTOR_ANNOTATION = "Lorg/openide/modules/ConstructorDelegate;"; |
82 |
private static final String DESC_PATCHED_PUBLIC_ANNOTATION = "Lorg/openide/modules/PatchedPublic;"; |
83 |
private static final String DESC_DEFAULT_CTOR = "()V"; |
84 |
private static final String CONSTRUCTOR_NAME = "<init>"; // NOI18N |
85 |
private static final String PREFIX_EXTEND = "extend."; // NOI18N |
86 |
|
59 |
static { |
87 |
static { |
60 |
try { |
88 |
try { |
61 |
RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations".getBytes("UTF-8"); // NOI18N |
89 |
RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations".getBytes("UTF-8"); // NOI18N |
62 |
PATCHED_PUBLIC = ("L" + PatchedPublic.class.getName().replace('.', '/') + ";").getBytes("UTF-8"); // NOI18N |
90 |
PATCHED_PUBLIC = DESC_PATCHED_PUBLIC_ANNOTATION.getBytes("UTF-8"); // NOI18N |
63 |
} catch (UnsupportedEncodingException x) { |
91 |
} catch (UnsupportedEncodingException x) { |
64 |
throw new ExceptionInInitializerError(x); |
92 |
throw new ExceptionInInitializerError(x); |
65 |
} |
93 |
} |
66 |
} |
94 |
} |
|
|
95 |
|
96 |
/** |
97 |
* Shared instance, which does just nothing |
98 |
*/ |
99 |
private static final PatchByteCode NOP = new PatchByteCode(false, null, null); |
100 |
|
101 |
/** |
102 |
* Shared instance, that performs a very fast PatchedPublic patch on the loaded class |
103 |
*/ |
104 |
private static final PatchByteCode PUBLIC_ONLY = new PatchByteCode(true, null, null); |
105 |
|
106 |
private final boolean patchPublic; |
107 |
private final Map<String, String> classToExtend; |
108 |
private final ClassLoader theClassLoader; |
109 |
|
110 |
private PatchByteCode() { |
111 |
this(false, null, null); |
112 |
} |
113 |
|
114 |
private PatchByteCode(boolean pub, Map<String, String> classToExtend, ClassLoader ldr) { |
115 |
this.patchPublic = pub; |
116 |
this.classToExtend = classToExtend; |
117 |
this.theClassLoader = ldr; |
118 |
} |
119 |
|
120 |
private void load(URL stream) throws IOException { |
121 |
try (InputStream istm = stream.openStream()) { |
122 |
Properties props = new Properties(); |
123 |
props.load(new InputStreamReader(istm, "UTF-8")); // NOI18N |
124 |
|
125 |
Enumeration<String> en = (Enumeration<String>)props.propertyNames(); |
126 |
|
127 |
while (en.hasMoreElements()) { |
128 |
String pn = en.nextElement(); |
129 |
if (pn.startsWith(PREFIX_EXTEND)) { |
130 |
String toExtend = pn.substring(PREFIX_EXTEND.length()); |
131 |
String extendWith = props.getProperty(pn); |
132 |
|
133 |
String old; |
134 |
|
135 |
if ((old = classToExtend.put(toExtend, extendWith)) != null) { |
136 |
throw new IOException("Multiple extend instructions for class" + toExtend + ": " + extendWith + " and " + old); |
137 |
} |
138 |
} |
139 |
} |
140 |
} |
141 |
} |
142 |
|
143 |
private PatchByteCode purify() { |
144 |
if (classToExtend == null || classToExtend.isEmpty()) { |
145 |
return PUBLIC_ONLY; |
146 |
} else { |
147 |
return this; |
148 |
} |
149 |
} |
150 |
|
151 |
static PatchByteCode fromStream(Enumeration<URL> streams, ClassLoader ldr) { |
152 |
PatchByteCode pb = new PatchByteCode(false, new HashMap<String, String>(3), ldr); |
153 |
boolean found = false; |
154 |
while (streams.hasMoreElements()) { |
155 |
URL stream = streams.nextElement(); |
156 |
try { |
157 |
pb.load(stream); |
158 |
} catch (IOException ex) { |
159 |
// TODO: log |
160 |
} |
161 |
found = true; |
162 |
} |
163 |
|
164 |
return found ? pb.purify() : NOP; |
165 |
} |
67 |
|
166 |
|
|
|
167 |
byte[] apply(String className, byte[] data) throws IOException { |
168 |
if (patchPublic) { |
169 |
return patch(data); |
170 |
} else if (classToExtend == null) { |
171 |
return data; |
172 |
} |
173 |
// more thorough analysis is needed. |
174 |
String extender = classToExtend.get(className); |
175 |
if (extender == null) { |
176 |
return patch(data); |
177 |
} |
178 |
// must analyze the extender class, as some annotations there may trigger |
179 |
ClassReader clr = new ClassReader(data); |
180 |
ClassWriter wr = new ClassWriter(clr, 0); |
181 |
ClassNode theClass = new ClassNode(); |
182 |
|
183 |
clr.accept(theClass, 0); |
184 |
|
185 |
MethodNode defCtor = null; |
186 |
|
187 |
String extInternalName = extender.replace(".", "/"); // NOI18N |
188 |
|
189 |
// patch the superclass |
190 |
theClass.superName = extInternalName; |
191 |
String resName = extInternalName + ".class"; // NOI18N |
192 |
|
193 |
try (InputStream istm = theClassLoader.getResourceAsStream(resName)) { |
194 |
if (istm == null) { |
195 |
throw new IOException("Could not find classfile for extender class"); // NOI18N |
196 |
} |
197 |
ClassReader extenderReader = new ClassReader(istm); |
198 |
ClassNode extenderClass = new ClassNode(); |
199 |
extenderReader.accept(extenderClass, ClassReader.SKIP_FRAMES); |
200 |
|
201 |
// search for a no-arg ctor, replace all invokespecial calls in ctors |
202 |
for (MethodNode m : (Collection<MethodNode>)theClass.methods) { |
203 |
if (CONSTRUCTOR_NAME.equals(m.name)) { |
204 |
if (DESC_DEFAULT_CTOR.equals(m.desc)) { // NOI18N |
205 |
defCtor = m; |
206 |
} |
207 |
replaceSuperCtorCalls(theClass, extenderClass, m); |
208 |
} |
209 |
} |
210 |
for (Object o : extenderClass.methods) { |
211 |
MethodNode mn = (MethodNode)o; |
212 |
|
213 |
if (mn.invisibleAnnotations != null && (mn.access & Opcodes.ACC_STATIC) > 0) { |
214 |
// constructor, possibly annotated |
215 |
for (AnnotationNode an : (Collection<AnnotationNode>)mn.invisibleAnnotations) { |
216 |
if (DESC_CTOR_ANNOTATION.equals(an.desc)) { |
217 |
delegateToFactory(extenderClass, mn, theClass, defCtor); |
218 |
break; |
219 |
} |
220 |
} |
221 |
} |
222 |
} |
223 |
|
224 |
for (MethodNode mn : (Collection<MethodNode>)theClass.methods) { |
225 |
if (mn.invisibleAnnotations == null) { |
226 |
continue; |
227 |
} |
228 |
for (AnnotationNode an : (Collection<AnnotationNode>)mn.invisibleAnnotations) { |
229 |
if (DESC_PATCHED_PUBLIC_ANNOTATION.equals(an.desc)) { |
230 |
mn.access = (mn.access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) | Opcodes.ACC_PUBLIC; |
231 |
break; |
232 |
} |
233 |
} |
234 |
} |
235 |
} |
236 |
|
237 |
theClass.accept(wr); |
238 |
byte[] result = wr.toByteArray(); |
239 |
return result; |
240 |
} |
241 |
|
242 |
/** |
243 |
* Replaces class references in super constructor invocations. |
244 |
* Must not replace references in this() constructor invocations. |
245 |
* |
246 |
* @param theClass the class being patched |
247 |
* @param extenderClass the injected superclass |
248 |
* @param mn method to process |
249 |
*/ |
250 |
private void replaceSuperCtorCalls(final ClassNode theClass, final ClassNode extenderClass, MethodNode mn) { |
251 |
for (Iterator it = mn.instructions.iterator(); it.hasNext(); ) { |
252 |
AbstractInsnNode aIns = (AbstractInsnNode)it.next(); |
253 |
if (aIns.getOpcode() == Opcodes.INVOKESPECIAL) { |
254 |
MethodInsnNode mins = (MethodInsnNode)aIns; |
255 |
if (CONSTRUCTOR_NAME.equals(mins.name) && !mins.owner.equals(theClass.name)) { |
256 |
// replace with the extender class name |
257 |
mins.owner = extenderClass.name; |
258 |
} |
259 |
break; |
260 |
} |
261 |
} |
262 |
} |
263 |
|
264 |
/** |
265 |
* No-op singature visitor |
266 |
*/ |
267 |
private static class NullSignVisitor extends SignatureVisitor { |
268 |
public NullSignVisitor() { |
269 |
super(Opcodes.ASM5); |
270 |
} |
271 |
} |
272 |
|
273 |
/** |
274 |
* Pushes parameters with correct opcodes that correspond to the |
275 |
* method's signature. Assumes that the first parameter is the |
276 |
* object's class itself. |
277 |
*/ |
278 |
private static class CallParametersWriter extends SignatureVisitor { |
279 |
private final MethodNode mn; |
280 |
private int localSize; |
281 |
|
282 |
/** |
283 |
* Adds opcodes to the method's code |
284 |
* |
285 |
* @param mn method to generate |
286 |
* @param firstSelf if true, assumes the first parameter is reference to self and will generate aload_0 |
287 |
*/ |
288 |
public CallParametersWriter(MethodNode mn, boolean firstSelf) { |
289 |
super(Opcodes.ASM5); |
290 |
this.mn = mn; |
291 |
this.paramIndex = firstSelf ? 0 : 1; |
292 |
} |
293 |
|
294 |
private int paramIndex = 1; |
295 |
|
296 |
@Override |
297 |
public void visitEnd() { |
298 |
// end of classtype |
299 |
mn.visitVarInsn(Opcodes.ALOAD, paramIndex++); |
300 |
localSize++; |
301 |
} |
302 |
|
303 |
@Override |
304 |
public void visitBaseType(char c) { |
305 |
int idx = paramIndex++; |
306 |
int opcode; |
307 |
|
308 |
switch (c) { |
309 |
// two-word data |
310 |
case 'J': opcode = Opcodes.LLOAD; paramIndex++; localSize++; break; |
311 |
case 'D': opcode = Opcodes.DLOAD; paramIndex++; localSize++; break; |
312 |
// float has a special opcode |
313 |
case 'F': opcode = Opcodes.FLOAD; break; |
314 |
default: opcode = Opcodes.ILOAD; break; |
315 |
|
316 |
} |
317 |
mn.visitVarInsn(opcode, idx); |
318 |
localSize++; |
319 |
} |
320 |
|
321 |
@Override |
322 |
public SignatureVisitor visitTypeArgument(char c) { |
323 |
return new NullSignVisitor(); |
324 |
} |
325 |
|
326 |
@Override |
327 |
public void visitTypeArgument() {} |
328 |
|
329 |
@Override |
330 |
public void visitInnerClassType(String string) {} |
331 |
|
332 |
@Override |
333 |
public void visitClassType(String string) {} |
334 |
|
335 |
@Override |
336 |
public SignatureVisitor visitArrayType() { |
337 |
mn.visitVarInsn(Opcodes.ALOAD, paramIndex++); |
338 |
return new NullSignVisitor(); |
339 |
} |
340 |
|
341 |
@Override |
342 |
public void visitTypeVariable(String string) {} |
343 |
|
344 |
@Override |
345 |
public SignatureVisitor visitExceptionType() { |
346 |
return new NullSignVisitor(); |
347 |
} |
348 |
|
349 |
@Override |
350 |
public SignatureVisitor visitReturnType() { |
351 |
return new NullSignVisitor(); |
352 |
} |
353 |
|
354 |
@Override |
355 |
public SignatureVisitor visitParameterType() { |
356 |
return this; |
357 |
} |
358 |
|
359 |
@Override |
360 |
public SignatureVisitor visitInterface() { |
361 |
return null; |
362 |
} |
363 |
|
364 |
@Override |
365 |
public SignatureVisitor visitSuperclass() { |
366 |
return null; |
367 |
} |
368 |
|
369 |
@Override |
370 |
public SignatureVisitor visitInterfaceBound() { |
371 |
return new NullSignVisitor(); |
372 |
} |
373 |
|
374 |
@Override |
375 |
public SignatureVisitor visitClassBound() { |
376 |
return new NullSignVisitor(); |
377 |
} |
378 |
|
379 |
@Override |
380 |
public void visitFormalTypeParameter(String string) { |
381 |
super.visitFormalTypeParameter(string); //To change body of generated methods, choose Tools | Templates. |
382 |
} |
383 |
|
384 |
} |
385 |
|
386 |
private void delegateToFactory(ClassNode targetClass, MethodNode targetMethod, ClassNode clazz, |
387 |
MethodNode noArgCtor) { |
388 |
String desc = targetMethod.desc; |
389 |
// assume the first parameter is the class: |
390 |
int nextPos = desc.indexOf(';', 2); // NOI18N |
391 |
desc = "(" + desc.substring(nextPos + 1); // NOI18N |
392 |
MethodNode mn = new MethodNode(Opcodes.ASM5, |
393 |
targetMethod.access & (~Opcodes.ACC_STATIC), CONSTRUCTOR_NAME, |
394 |
desc, |
395 |
targetMethod.signature, |
396 |
(String[])targetMethod.exceptions.toArray(new String[targetMethod.exceptions.size()])); |
397 |
|
398 |
mn.visibleAnnotations = targetMethod.visibleAnnotations; |
399 |
mn.visibleParameterAnnotations = targetMethod.visibleParameterAnnotations; |
400 |
mn.parameters = targetMethod.parameters; |
401 |
mn.exceptions = targetMethod.exceptions; |
402 |
mn.visitCode(); |
403 |
// this(); |
404 |
mn.visitVarInsn(Opcodes.ALOAD, 0); |
405 |
mn.visitMethodInsn(Opcodes.INVOKESPECIAL, |
406 |
clazz.name, |
407 |
noArgCtor.name, noArgCtor.desc, false); |
408 |
|
409 |
// push parameters |
410 |
SignatureReader r = new SignatureReader(targetMethod.desc); |
411 |
CallParametersWriter callWr = new CallParametersWriter(mn, true); |
412 |
r.accept(callWr); |
413 |
mn.visitMethodInsn(Opcodes.INVOKESTATIC, targetClass.name, targetMethod.name, targetMethod.desc, false); |
414 |
|
415 |
mn.visitInsn(Opcodes.RETURN); |
416 |
mn.maxStack = callWr.localSize; |
417 |
mn.maxLocals = callWr.localSize; |
418 |
|
419 |
clazz.methods.add(mn); |
420 |
} |
421 |
|
68 |
/** |
422 |
/** |
69 |
* Patches a class if it is needed. |
423 |
* Patches a class if it is needed. |
70 |
* @param arr the bytecode |
424 |
* @param arr the bytecode |