001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.object.bytecode;
006:
007: import com.tc.asm.MethodVisitor;
008: import com.tc.asm.Opcodes;
009: import com.tc.asm.Type;
010: import com.tc.object.ObjectID;
011: import com.tc.util.Assert;
012:
013: import java.io.ByteArrayOutputStream;
014: import java.io.IOException;
015: import java.io.InputStream;
016: import java.lang.reflect.Modifier;
017: import java.util.ArrayList;
018: import java.util.Arrays;
019: import java.util.Collections;
020: import java.util.HashSet;
021: import java.util.List;
022: import java.util.Set;
023:
024: /**
025: * Utility methods for working with byte code.
026: */
027: public class ByteCodeUtil implements Opcodes {
028: private static final String AUTOLOCK_PREFIX = "@";
029: private static final String NAMED_LOCK_PREFIX = "^";
030: private static final String LITERAL_LOCK_PREFIX = "#";
031:
032: public static final String TC_FIELD_PREFIX = "$__tc_";
033: public static final String TC_METHOD_PREFIX = "__tc_";
034: public static final String METHOD_RENAME_PREFIX = TC_METHOD_PREFIX
035: + "wrapped_";
036: public static final String SYNC_METHOD_RENAME_PREFIX = ByteCodeUtil.METHOD_RENAME_PREFIX
037: + "sync_";
038: public static final String DMI_METHOD_RENAME_PREFIX = TC_METHOD_PREFIX
039: + "dmi_";
040:
041: public static final String VALUES_GETTER = TC_METHOD_PREFIX
042: + "getallfields";
043: public static final String VALUES_GETTER_DESCRIPTION = "(Ljava/util/Map;)V";
044: public static final String VALUES_SETTER = TC_METHOD_PREFIX
045: + "setfield";
046: public static final String VALUES_SETTER_DESCRIPTION = "(Ljava/lang/String;Ljava/lang/Object;)V";
047: public static final String MANAGED_VALUES_GETTER = TC_METHOD_PREFIX
048: + "getmanagedfield";
049: public static final String MANAGED_VALUES_GETTER_DESCRIPTION = "(Ljava/lang/String;)Ljava/lang/Object;";
050: public static final String MANAGED_VALUES_SETTER = TC_METHOD_PREFIX
051: + "setmanagedfield";
052:
053: public static final String MANAGEABLE_CLASS = "com/tc/object/bytecode/Manageable";
054: public static final String MANAGEABLE_TYPE = "L" + MANAGEABLE_CLASS
055: + ";";
056:
057: public static final String TRANSPARENT_ACCESS_CLASS = "com/tc/object/bytecode/TransparentAccess";
058: public static final String TRANSPARENT_ACCESS_TYPE = "L"
059: + TRANSPARENT_ACCESS_CLASS + ";";
060:
061: public static final String NAMEDCLASSLOADER_CLASS = "com/tc/object/loaders/NamedClassLoader";
062: public static final String NAMEDCLASSLOADER_TYPE = "L"
063: + NAMEDCLASSLOADER_CLASS + ";";
064:
065: public static final String WEBAPPCONFIG_CLASS = "com/terracotta/session/WebAppConfig";
066: public static final String WEBAPPCONFIG_TYPE = "L"
067: + WEBAPPCONFIG_CLASS + ";";
068:
069: /**
070: * Given a set of existing interfaces, add some more (without duplicates)
071: * @param existing The existing interfaces
072: * @param toAdd The interfaces to add
073: * @return A set of interfaces containing all of existing and toAdd with no dups
074: */
075: public static String[] addInterfaces(String[] existing,
076: String[] toAdd) {
077: if (existing == null) {
078: return toAdd;
079: }
080: if (toAdd == null) {
081: return existing;
082: }
083:
084: List newList = new ArrayList(Arrays.asList(existing));
085: Set existingAsSet = Collections.unmodifiableSet(new HashSet(
086: newList));
087:
088: for (int i = 0, n = toAdd.length; i < n; i++) {
089: if (!existingAsSet.contains(toAdd[i])) {
090: newList.add(toAdd[i]);
091: }
092: }
093:
094: return (String[]) newList.toArray(new String[newList.size()]);
095: }
096:
097: /**
098: * Check whether the type is a primitve
099: * @param t The ASM type
100: * @return True if primitive
101: */
102: public static boolean isPrimitive(Type t) {
103: final int sort = t.getSort();
104: switch (sort) {
105: case Type.BOOLEAN:
106: case Type.BYTE:
107: case Type.CHAR:
108: case Type.DOUBLE:
109: case Type.FLOAT:
110: case Type.INT:
111: case Type.LONG:
112: case Type.SHORT:
113: return true;
114: default:
115: return false;
116: }
117:
118: // unreachable
119: }
120:
121: /**
122: * Map from primite type to wrapper class type
123: * @param sort Kind of primitve type as in {@link com.tc.asm.Type#getSort()}
124: * @return Wrapper class name, like "java/lang/Boolean"
125: */
126: public static String sortToWrapperName(int sort) {
127: switch (sort) {
128: case Type.BOOLEAN: // '\001'
129: return "java/lang/Boolean";
130:
131: case Type.CHAR: // '\002'
132: return "java/lang/Character";
133:
134: case Type.BYTE: // '\003'
135: return "java/lang/Byte";
136:
137: case Type.SHORT: // '\004'
138: return "java/lang/Short";
139:
140: case Type.INT: // '\005'
141: return "java/lang/Integer";
142:
143: case Type.FLOAT: // '\006'
144: return "java/lang/Float";
145:
146: case Type.LONG: // '\007'
147: return "java/lang/Long";
148:
149: case Type.DOUBLE: // '\b'
150: return "java/lang/Double";
151: default:
152: throw new AssertionError();
153: }
154:
155: }
156:
157: /**
158: * Translate type code to type name
159: * @param typeCode Code from bytecode like B, C, etc
160: * @return Primitive type name: "byte", "char", etc
161: */
162: public static String codeToName(String typeCode) {
163: if ((typeCode == null) || (typeCode.length() != 1)) {
164: throw new IllegalArgumentException("invalid type code: "
165: + typeCode);
166: }
167: char code = typeCode.charAt(0);
168:
169: switch (code) {
170: case 'B': {
171: return "byte";
172: }
173: case 'C': {
174: return "char";
175: }
176: case 'D': {
177: return "double";
178: }
179: case 'F': {
180: return "float";
181: }
182: case 'I': {
183: return "int";
184: }
185: case 'J': {
186: return "long";
187: }
188: case 'S': {
189: return "short";
190: }
191: case 'Z': {
192: return "boolean";
193: }
194: default: {
195: throw new IllegalArgumentException("unknown code: " + code);
196: }
197:
198: // unreachable
199: }
200: }
201:
202: /**
203: * Determine whether a lock is an autolock based on its name
204: * @param lockName The lock name
205: * @return True if an autolock
206: */
207: public static boolean isAutolockName(String lockName) {
208: return lockName == null ? false : lockName
209: .startsWith(AUTOLOCK_PREFIX);
210: }
211:
212: /**
213: * Get lock ID from autolock name
214: * @param lockName The lock name
215: * @return Lock ID
216: * @throws IllegalArgumentException If not an autolock
217: */
218: public static long objectIdFromLockName(String lockName) {
219: if (lockName == null || (!lockName.startsWith(AUTOLOCK_PREFIX))) {
220: // make formatter sane
221: throw new IllegalArgumentException("not an autolock name: "
222: + lockName);
223: }
224: return Long.valueOf(
225: lockName.substring(AUTOLOCK_PREFIX.length()))
226: .longValue();
227:
228: }
229:
230: /**
231: * Determine whether a field is synthetic
232: * @param fieldName The field name
233: * @return True if synthetic
234: */
235: public static boolean isSynthetic(String fieldName) {
236: return fieldName.indexOf("$") >= 0;
237: }
238:
239: /**
240: * Determine whether a field is synthetic and was added by Terracotta
241: * @param fieldName The field name
242: * @return True if synthetic and added by Terracotta
243: */
244: public static boolean isTCSynthetic(String fieldName) {
245: return fieldName.startsWith(TC_FIELD_PREFIX)
246: || isParent(fieldName);
247: }
248:
249: /**
250: * Determine whether an access modifier code indicates synthetic
251: * @param access Access modifier code
252: * @return True if synthetic flag is set
253: */
254: public static boolean isSynthetic(int access) {
255: return (ACC_SYNTHETIC & access) > 0;
256: }
257:
258: /**
259: * Check whether the field name indicates that this is an inner classes synthetic
260: * field referring to the parent "this" reference.
261: * @param fieldName The field name
262: * @return True if this field refers to the parent this
263: */
264: public static boolean isParent(String fieldName) {
265: return fieldName.matches("^this\\$\\d+$");
266:
267: // return SERIALIZATION_UTIL.isParent(fieldName);
268: }
269:
270: /**
271: * Add instruction to retrieve "this" from the local vars and load onto the stack
272: * @param c The current method visitor
273: */
274: public static void pushThis(MethodVisitor c) {
275: c.visitVarInsn(ALOAD, 0);
276: }
277:
278: /**
279: * Add instruction to retrieve specified field in the object on the stack and replace
280: * with the field value.
281: * @param c Current method visitor
282: * @param className The field class
283: * @param fieldName The field name
284: * @param description The field type
285: */
286: public static void pushInstanceVariable(MethodVisitor c,
287: String className, String fieldName, String description) {
288: c.visitFieldInsn(GETFIELD, className, fieldName, description);
289: }
290:
291: /**
292: * Add instructions to convert the local variables typed with parameters into an array
293: * assuming values start at local variable offset of 1
294: * @param c Method visitor
295: * @param parameters Paramater to convert
296: */
297: public static void createParametersToArrayByteCode(MethodVisitor c,
298: Type[] parameters) {
299: createParametersToArrayByteCode(c, parameters, 1);
300: }
301:
302: /**
303: * Add instructions to convert the parameters into an array
304: * @param c Method visitor
305: * @param parameters Paramater types to convert
306: * @param offset Offset into local variables for values
307: */
308: public static void createParametersToArrayByteCode(MethodVisitor c,
309: Type[] parameters, int offset) {
310: c.visitLdcInsn(new Integer(parameters.length));
311: c.visitTypeInsn(ANEWARRAY, "java/lang/Object");
312: for (int i = 0; i < parameters.length; i++) {
313: c.visitInsn(DUP);
314: c.visitLdcInsn(new Integer(i));
315: addTypeSpecificParameterLoad(c, parameters[i], offset);
316: c.visitInsn(AASTORE);
317: offset += parameters[i].getSize();
318: }
319: }
320:
321: /**
322: * Add instructions to load type-specific value from local variable onto stack. Primitve
323: * values are wrapped into their wrapper object.
324: * @param c Method visitor
325: * @param type The type of the variable
326: * @param offset The local variable offset
327: */
328: public static void addTypeSpecificParameterLoad(MethodVisitor c,
329: Type type, int offset) {
330:
331: switch (type.getSort()) {
332: case Type.ARRAY:
333: case Type.OBJECT:
334: c.visitVarInsn(type.getOpcode(ILOAD), offset);
335: break;
336: case Type.BOOLEAN:
337: c.visitTypeInsn(NEW, "java/lang/Boolean");
338: c.visitInsn(DUP);
339: c.visitVarInsn(type.getOpcode(ILOAD), offset);
340: c.visitMethodInsn(INVOKESPECIAL, "java/lang/Boolean",
341: "<init>", "(Z)V");
342: break;
343: case Type.BYTE:
344: c.visitTypeInsn(NEW, "java/lang/Byte");
345: c.visitInsn(DUP);
346: c.visitVarInsn(type.getOpcode(ILOAD), offset);
347: c.visitMethodInsn(INVOKESPECIAL, "java/lang/Byte",
348: "<init>", "(B)V");
349: break;
350: case Type.CHAR:
351: c.visitTypeInsn(NEW, "java/lang/Character");
352: c.visitInsn(DUP);
353: c.visitVarInsn(type.getOpcode(ILOAD), offset);
354: c.visitMethodInsn(INVOKESPECIAL, "java/lang/Character",
355: "<init>", "(C)V");
356: break;
357: case Type.DOUBLE:
358: c.visitTypeInsn(NEW, "java/lang/Double");
359: c.visitInsn(DUP);
360: c.visitVarInsn(type.getOpcode(ILOAD), offset);
361: c.visitMethodInsn(INVOKESPECIAL, "java/lang/Double",
362: "<init>", "(D)V");
363: break;
364: case Type.FLOAT:
365: c.visitTypeInsn(NEW, "java/lang/Float");
366: c.visitInsn(DUP);
367: c.visitVarInsn(type.getOpcode(ILOAD), offset);
368: c.visitMethodInsn(INVOKESPECIAL, "java/lang/Float",
369: "<init>", "(F)V");
370: break;
371: case Type.INT:
372: c.visitTypeInsn(NEW, "java/lang/Integer");
373: c.visitInsn(DUP);
374: c.visitVarInsn(type.getOpcode(ILOAD), offset);
375: c.visitMethodInsn(INVOKESPECIAL, "java/lang/Integer",
376: "<init>", "(I)V");
377: break;
378: case Type.LONG:
379: c.visitTypeInsn(NEW, "java/lang/Long");
380: c.visitInsn(DUP);
381: c.visitVarInsn(type.getOpcode(ILOAD), offset);
382: c.visitMethodInsn(INVOKESPECIAL, "java/lang/Long",
383: "<init>", "(J)V");
384: break;
385: case Type.SHORT:
386: c.visitTypeInsn(NEW, "java/lang/Short");
387: c.visitInsn(DUP);
388: c.visitVarInsn(type.getOpcode(ILOAD), offset);
389: c.visitMethodInsn(INVOKESPECIAL, "java/lang/Short",
390: "<init>", "(S)V");
391: break;
392: default:
393: throw new AssertionError("can't happen:" + type);
394: }
395: }
396:
397: /**
398: * Add instructions to load method args into the stack
399: * @param callingMethodModifier Calling method modifier
400: * @param desc Method descriptor
401: * @param c Current method visitor
402: */
403: public static void pushMethodArguments(int callingMethodModifier,
404: String desc, MethodVisitor c) {
405: int localVariableOffset = getLocalVariableOffset(callingMethodModifier);
406: Type[] args = Type.getArgumentTypes(desc);
407:
408: int pos = 0;
409: for (int i = 0; i < args.length; i++) {
410: c.visitVarInsn(args[i].getOpcode(ILOAD), pos
411: + localVariableOffset);
412: pos += args[i].getSize();
413: }
414: }
415:
416: /**
417: * Get offset of first local variable after method args
418: * @param callingMethodModifier Calling method modifier
419: * @param desc Method descriptor
420: * @return First local variable offset
421: */
422: public static int getFirstLocalVariableOffset(
423: int callingMethodModifier, String desc) {
424: int localVariableOffset = getLocalVariableOffset(callingMethodModifier);
425: Type[] args = Type.getArgumentTypes(desc);
426: for (int i = 0; i < args.length; i++) {
427: localVariableOffset += args[i].getSize();
428: }
429: return localVariableOffset;
430: }
431:
432: /**
433: * Push this (if not static) and all method args onto stack
434: * @param callingMethodModifier Calling method modifier
435: * @param desc Method descriptor
436: * @param c Calling method visitor
437: */
438: public static void prepareStackForMethodCall(
439: int callingMethodModifier, String desc, MethodVisitor c) {
440: if (!Modifier.isStatic(callingMethodModifier)) {
441: pushThis(c);
442: }
443: pushMethodArguments(callingMethodModifier, desc, c);
444: }
445:
446: /**
447: * Returns 0 if the method is static. 1 If the method is not.
448: * @param methodModifier
449: * @return 0 if static, 1 if not
450: */
451: public static int getLocalVariableOffset(int methodModifier) {
452: return Modifier.isStatic(methodModifier) ? 0 : 1;
453: }
454:
455: /**
456: * Get volatile lock name
457: * @param id Object identifier
458: * @param field Volatile field
459: * @return Lock name
460: */
461: public static String generateVolatileLockName(ObjectID id,
462: String fieldName) {
463: Assert.assertNotNull(id);
464: return AUTOLOCK_PREFIX + id.toLong() + fieldName;
465: }
466:
467: /**
468: * Get auto lock name for object identifier
469: * @param id Identifier
470: * @return Auto lock name
471: */
472: public static String generateAutolockName(ObjectID id) {
473: Assert.assertNotNull(id);
474: return generateAutolockName(id.toLong());
475: }
476:
477: /**
478: * Get named lock name for the lock object
479: * @param obj Lock object
480: * @return Named lock name
481: */
482: public static String generateNamedLockName(Object obj) {
483: Assert.assertNotNull(obj);
484: return NAMED_LOCK_PREFIX + obj;
485: }
486:
487: /**
488: * The first argument should be "(new LiteralValues()).valueFor(obj)", but I didn't want to slurp in a whole mess of
489: * classes into the boot jar by including LiteralValues. It's gross, but ManagerImpl just makes the call itself.
490: * @param literalValuesValueFor Literal value code
491: * @param obj The lock object
492: */
493: public static String generateLiteralLockName(
494: int literalValuesValueFor, Object obj) {
495: Assert.assertNotNull(obj);
496: return literalValuesValueFor + LITERAL_LOCK_PREFIX + obj;
497: }
498:
499: private static String generateAutolockName(long objectId) {
500: return AUTOLOCK_PREFIX + objectId;
501: }
502:
503: /**
504: * Strip generated lock header from lock name
505: * @param lockName Lock name
506: * @return Real lock name
507: */
508: public static String stripGeneratedLockHeader(String lockName) {
509: int index = lockName.indexOf(LITERAL_LOCK_PREFIX);
510: index = index < 0 ? 1 : index;
511: return lockName.substring(index);
512: }
513:
514: /**
515: * Convert from {@link com.tc.asm.Type#getSort()} to a primitive method
516: * name like "booleanValue".
517: * @param Type kind
518: * @return Primitive method name
519: */
520: public static String sortToPrimitiveMethodName(int sort) {
521: switch (sort) {
522: case Type.BOOLEAN: // '\001'
523: return "booleanValue";
524:
525: case Type.CHAR: // '\002'
526: return "charValue";
527:
528: case Type.BYTE: // '\003'
529: return "byteValue";
530:
531: case Type.SHORT: // '\004'
532: return "shortValue";
533:
534: case Type.INT: // '\005'
535: return "intValue";
536:
537: case Type.FLOAT: // '\006'
538: return "floatValue";
539:
540: case Type.LONG: // '\007'
541: return "longValue";
542:
543: case Type.DOUBLE: // '\b'
544: return "doubleValue";
545: default:
546: throw new AssertionError();
547: }
548: }
549:
550: /**
551: * Get return type (class name) from method descriptor
552: * @param desc Method descriptor
553: * @return Return type class name
554: */
555: public static String methodDescriptionToReturnType(String desc) {
556: Type type = Type.getReturnType(desc);
557: return type.getClassName();
558: }
559:
560: /**
561: * Turn method description with byte code types into a readable signature
562: * @param desc The bytecode description
563: * @return The method argument form
564: */
565: public static String methodDescriptionToMethodArgument(String desc) {
566: Type[] types = Type.getArgumentTypes(desc);
567: StringBuffer sb = new StringBuffer("(");
568: for (int i = 0; i < types.length; i++) {
569: sb.append(types[i].getClassName());
570: if (i < types.length - 1) {
571: sb.append(",");
572: }
573: }
574: sb.append(")");
575: return sb.toString();
576: }
577:
578: /**
579: * Get name of synthetic field getter method added by Terracotta
580: * @param fieldName The field name
581: * @return Getter method name
582: */
583: public static String fieldGetterMethod(String fieldName) {
584: return TC_METHOD_PREFIX + "get" + fieldName;
585: }
586:
587: /**
588: * Get name of synthetic field setter method added by Terracotta
589: * @param fieldName The field name
590: * @return Setter method name
591: */
592: public static String fieldSetterMethod(String fieldName) {
593: return TC_METHOD_PREFIX + "set" + fieldName;
594: }
595:
596: /**
597: * Add instructions to print msg to System.out
598: * @param mv Method visitor
599: * @param msg Message to print
600: */
601: public static void systemOutPrintln(MethodVisitor mv, String msg) {
602: mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
603: "Ljava/io/PrintStream;");
604: mv.visitLdcInsn(msg);
605: mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream",
606: "println", "(Ljava/lang/String;)V");
607: }
608:
609: /**
610: * Translate class name to file name
611: * @param className The class name "java.lang.String"
612: * @return The file name on the classpath: "java/lang/String.class"
613: */
614: public static final String classNameToFileName(String className) {
615: return className.replace('.', '/') + ".class";
616: }
617:
618: /**
619: * Read the bytes defining the class
620: * @param className The class
621: * @param loader The classloader
622: * @return The underlying bytes
623: */
624: public static final byte[] getBytesForClass(String className,
625: ClassLoader loader) throws ClassNotFoundException {
626: String resource = classNameToFileName(className);
627: InputStream is = loader.getResourceAsStream(resource);
628: if (is == null) {
629: throw new ClassNotFoundException(
630: "No resource found for class: " + className);
631: }
632: try {
633: return getBytesForInputstream(is);
634: } catch (IOException e) {
635: throw new ClassNotFoundException("Error reading bytes for "
636: + resource, e);
637: }
638: }
639:
640: /**
641: * Read input stream into a byte array using a 4k buffer. Close stream when done.
642: * @param is Input stream
643: * @return Bytes read from stream
644: * @throws IOException If there is an error reading the stream
645: */
646: public static final byte[] getBytesForInputstream(InputStream is)
647: throws IOException {
648: final int size = 4096;
649: byte[] buffer = new byte[size];
650: ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
651:
652: int read;
653: try {
654: while ((read = is.read(buffer, 0, size)) > 0) {
655: baos.write(buffer, 0, read);
656: }
657: } finally {
658: if (is != null) {
659: try {
660: is.close();
661: } catch (IOException ioe) {
662: // ignore
663: }
664: }
665: }
666:
667: return baos.toByteArray();
668: }
669:
670: /**
671: * Assign the default value to the variable
672: * @param variable The local variable to which the default value will be assigned
673: * @param c MethodVisitor
674: * @param type Type of the variable
675: */
676: public static void pushDefaultValue(int variable, MethodVisitor c,
677: Type type) {
678: if (type.getSort() == Type.OBJECT
679: || type.getSort() == Type.ARRAY) {
680: c.visitInsn(ACONST_NULL);
681: c.visitVarInsn(ASTORE, variable);
682: } else {
683: c.visitInsn(getConstant0(type));
684: c.visitVarInsn(type.getOpcode(ISTORE), variable);
685: }
686: }
687:
688: /**
689: * Return the constant 0 value according to the type.
690: * @param type
691: */
692: private static int getConstant0(Type type) {
693: if (type.getSort() == Type.INT) {
694: return ICONST_0;
695: }
696: if (type.getSort() == Type.LONG) {
697: return LCONST_0;
698: }
699: if (type.getSort() == Type.SHORT) {
700: return ICONST_0;
701: }
702: if (type.getSort() == Type.DOUBLE) {
703: return DCONST_0;
704: }
705: if (type.getSort() == Type.BOOLEAN) {
706: return ICONST_0;
707: }
708: if (type.getSort() == Type.FLOAT) {
709: return FCONST_0;
710: }
711: if (type.getSort() == Type.BYTE) {
712: return ICONST_0;
713: }
714: if (type.getSort() == Type.CHAR) {
715: return ICONST_0;
716: }
717:
718: throw new AssertionError(
719: "Cannot determine constant 0 of type: "
720: + type.getDescriptor());
721: }
722: }
|