InstrVisitorpublic final class InstrVisitor extends AbstractClassDefVisitor implements IAttributeVisitor, IClassDefVisitor, com.vladium.util.IConstants, com.vladium.jcd.opcodes.IOpcodes
Fields Summary |
---|
private final boolean | m_excludeSyntheticMethods | private final boolean | m_excludeBridgeMethods | private final boolean | m_doSUIDCompensation | private final com.vladium.logging.Logger | m_log | private boolean | m_warningIssued | private boolean | m_instrument | private boolean | m_metadata | private boolean | m_ignoreAlreadyInstrumented | ClassDef | m_cls | private String | m_classPackageName | private String | m_className | private String | m_classSrcFileName | private int[] | m_classBlockMetadata | private com.vladium.emma.data.MethodDescriptor[] | m_classMethodDescriptors | private int | m_syntheticStringIndex | int | m_coverageFieldrefIndex | private int | m_registerMethodrefIndex | int | m_preclinitMethodrefIndex | int | m_classNameConstantIndex | private int | m_stampIndex | private int | m_clinitID | private int | m_clinitStatus | int | m_classInstrMethodCount | int[] | m_classBlockCounts | private long | m_classSignature | int | m_methodID | private String | m_methodName | private int | m_methodFirstLine | private int[] | m_methodBlockOffsets | private int[] | m_methodBlockSizes | private int[] | m_methodJumpAdjOffsets | private int[] | m_methodJumpAdjValues | private static final long | NBEAST | private static final String | COVERAGE_FIELD_NAME | private static final String | SUID_FIELD_NAME | private static final String | PRECLINIT_METHOD_NAME | private static final String | JAVA_IO_SERIALIZABLE_NAME | private static final String | JAVA_IO_EXTERNALIZABLE_NAME | private static final int | EMIT_CTX_MIN_INIT_CAPACITY | private static final int | PRECLINIT_INIT_CAPACITY | private static final boolean | MARK_ADDED_ELEMENTS_SYNTHETIC | private static final boolean | SKIP_SYNTHETIC_CLASSES | private static final LineNumberComparator | LINE_NUMBER_COMPARATOR | private static final byte[] | EMPTY_BYTE_ARRAY |
Constructors Summary |
---|
public InstrVisitor(com.vladium.emma.data.CoverageOptions options)
m_excludeSyntheticMethods = options.excludeSyntheticMethods ();
m_excludeBridgeMethods = options.excludeBridgeMethods ();
m_doSUIDCompensation = options.doSUIDCompensation ();
m_log = Logger.getLogger ();
|
Methods Summary |
---|
private void | consumeSignatureData(int methodID, int[] basicBlockOffsets)
// note: by itself, this is not a very good checksum for a class def;
// however, it is fast to compute and since it will be used along with
// a class name it should be good at detecting structural changes that
// matter to us (method and basic block ordering/sizes)
final int temp1 = basicBlockOffsets.length;
long temp2 = NBEAST * m_classSignature + (methodID + 1) * temp1;
for (int i = 1; i < temp1; ++ i) // skip the initial 0 offset
{
temp2 = NBEAST * temp2 + basicBlockOffsets [i];
}
m_classSignature = temp2;
| private static int | lowbound(int[] values, int x)Returns the maximum index 'i' such that (values[i] <= x). values[]
contains distinct non-negative integers in increasing order. values[0] is 0,
'x' is non-negative.
Edge case:
returns values.length-1 if values [values.length - 1] < x
int low = 0, high = values.length - 1;
// assertion: lb is in [low, high]
while (low <= high)
{
final int m = (low + high) >> 1;
final int v = values [m];
if (v == x)
return m;
else if (v < x)
low = m + 1;
else // v > x
high = m - 1;
}
return high;
| public void | process(ClassDef cls, boolean ignoreAlreadyInstrumented, boolean instrument, boolean metadata, com.vladium.emma.instr.InstrVisitor$InstrResult out)Analyzes 'cls' and/or instruments it for coverage:
- if 'instrument' is true, the class definition is instrumented for
coverage if that is feasible
- if 'metadata' is true, the class definition is analysed
to create a {@link ClassDescriptor} for the original class definition
This method returns null if 'metadata' is 'false' *or* if 'cls' is an
interface [the latter precludes coverage of interface static
initializers and may be removed in the future].
NOTE: if 'instrument' is 'true', the caller should always assume that 'cls'
has been mutated by this method even if it returned null. The caller should
then revert to the original class definition that was created as a
cls.clone() or by retaining the original definition bytes.
This part of contract is for efficienty and also simplifies the implementation.
out.m_instrumented = false;
out.m_descriptor = null;
if (! (instrument || metadata)) return; // nothing to do
if (cls.isInterface ())
return; // skip interfaces [may change in the future]
else
{
reset ();
m_cls = cls;
// TODO: handle classes that cannot be instrumented due to bytecode/JVM limitations
m_instrument = instrument;
m_metadata = metadata;
m_ignoreAlreadyInstrumented = ignoreAlreadyInstrumented;
// TODO: create 'no instrumentation' execution path here
visit ((ClassDef) null, null); // potentially changes m_instrument and m_metadata
if (m_metadata)
{
setClassName (cls.getName ());
out.m_descriptor = new ClassDescriptor (m_classPackageName, m_className, m_classSignature, m_classSrcFileName, m_classMethodDescriptors);
}
out.m_instrumented = m_instrument;
}
| private void | reset()
// TODO: check that all state is reset
m_instrument = false;
m_metadata = false;
m_ignoreAlreadyInstrumented = false;
m_cls = null;
m_classPackageName = null;
m_className = null;
m_classSrcFileName = null;
m_classBlockMetadata = null;
m_classMethodDescriptors = null;
m_syntheticStringIndex = -1;
m_coverageFieldrefIndex = -1;
m_registerMethodrefIndex = -1;
m_preclinitMethodrefIndex = -1;
m_classNameConstantIndex = -1;
m_clinitID = -1;
m_clinitStatus = 0;
m_classInstrMethodCount = -1;
m_classBlockCounts = null;
m_classSignature = 0;
m_methodID = -1;
m_methodName = null;
m_methodFirstLine = 0;
m_methodBlockOffsets = null;
m_methodJumpAdjOffsets = null;
m_methodJumpAdjValues = null;
| private void | setClassName(java.lang.String fullName)
if ($assert.ENABLED) $assert.ASSERT (fullName != null && fullName.length () > 0,
"null or empty input: fullName");
final int lastSlash = fullName.lastIndexOf ('/");
if (lastSlash < 0)
{
m_classPackageName = "";
m_className = fullName;
}
else
{
if ($assert.ENABLED) $assert.ASSERT (lastSlash < fullName.length () - 1,
"malformed class name [" + fullName + "]");
m_classPackageName = fullName.substring (0, lastSlash);
m_className = fullName.substring (lastSlash + 1);
}
| public java.lang.Object | visit(SourceFileAttribute_info attribute, java.lang.Object ctx)
m_classSrcFileName = attribute.getSourceFile (m_cls).m_value;
return ctx;
| public java.lang.Object | visit(SyntheticAttribute_info attribute, java.lang.Object ctx)
return ctx;
| public java.lang.Object | visit(BridgeAttribute_info attribute, java.lang.Object ctx)
return ctx;
| public java.lang.Object | visit(InnerClassesAttribute_info attribute, java.lang.Object ctx)
return ctx;
| public java.lang.Object | visit(GenericAttribute_info attribute, java.lang.Object ctx)
return ctx;
| public java.lang.Object | visit(ClassDef ignore, java.lang.Object ctx)
final ClassDef cls = m_cls;
final String clsVMName = cls.getName ();
final String clsName = Types.vmNameToJavaName (clsVMName);
final boolean trace1 = m_log.atTRACE1 ();
if (trace1) m_log.trace1 ("visit", "class: [" + clsVMName + "]");
// skip synthetic classes if enabled:
if (SKIP_SYNTHETIC_CLASSES && cls.isSynthetic ())
{
m_instrument = false;
m_metadata = false;
if (trace1) m_log.trace1 ("visit", "skipping synthetic class");
return ctx;
}
// TODO: ideally, this check should be done in outer scope somewhere
if (! m_warningIssued && clsName.startsWith (IAppConstants.APP_PACKAGE))
{
m_warningIssued = true;
m_log.warning (IAppConstants.APP_NAME + " classes appear to be included on the instrumentation");
m_log.warning ("path: this is not a correct way to use " + IAppConstants.APP_NAME);
}
// field uniqueness check done to detect double instrumentation:
{
final int [] existing = cls.getFields (COVERAGE_FIELD_NAME);
if (existing.length > 0)
{
m_instrument = false;
m_metadata = false;
if (m_ignoreAlreadyInstrumented)
{
if (trace1) m_log.trace1 ("visit", "skipping instrumented class");
return ctx;
}
else
{
// TODO: use a app coded exception
throw new IllegalStateException ("class [" + clsName + "] appears to be instrumented already");
}
}
}
final IConstantCollection constants = cls.getConstants ();
SyntheticAttribute_info syntheticMarker = null;
// cache the location of "Synthetic" string:
{
if (MARK_ADDED_ELEMENTS_SYNTHETIC)
m_syntheticStringIndex = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_SYNTHETIC, true);
}
// add a Fieldref for the runtime coverage collector field:
{
// note: this is a bit premature if the class has no methods that need
// instrumentation
// TODO: the mutated version is easily discardable; however, this case
// needs attention at metadata/report generation level
final int coverageFieldOffset;
final String fieldDescriptor = "[[Z";
// note that post-4019 builds can modify this field outside of <clinit> (although
// it can only happen as part of initializing a set of classes); however, it is legal
// to declare this field final:
final int fieldModifiers = IAccessFlags.ACC_PRIVATE | IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL;
// add declared field:
if (MARK_ADDED_ELEMENTS_SYNTHETIC)
{
final IAttributeCollection fieldAttributes = ElementFactory.newAttributeCollection (1);
syntheticMarker = new SyntheticAttribute_info (m_syntheticStringIndex);
fieldAttributes.add (syntheticMarker);
coverageFieldOffset = cls.addField (COVERAGE_FIELD_NAME, fieldDescriptor,
fieldModifiers, fieldAttributes);
}
else
{
coverageFieldOffset = cls.addField (COVERAGE_FIELD_NAME, fieldDescriptor,
fieldModifiers);
}
//add fieldref:
m_coverageFieldrefIndex = cls.addFieldref (coverageFieldOffset);
}
// add a Methodref for Runtime.r():
{
// TODO: compute this without loading Runtime Class?
final String classJVMName = "com/vladium/emma/rt/RT";
final int class_index = cls.addClassref (classJVMName);
// NOTE: keep this descriptor in sync with the actual signature
final String methodDescriptor = "([[ZLjava/lang/String;J)V";
final int nametype_index = cls.addNameType ("r", methodDescriptor);
m_registerMethodrefIndex = constants.add (new CONSTANT_Methodref_info (class_index, nametype_index));
}
// SF FR 971186: split the init logic into a separate method so it could
// be called from regular method headers if necessary:
// add a Methodref for pre-<clinit> method:
{
// NOTE: keep this descriptor in sync with the actual signature
final String methodDescriptor = "()[[Z";
final int nametype_index = cls.addNameType (PRECLINIT_METHOD_NAME, methodDescriptor);
m_preclinitMethodrefIndex = constants.add (new CONSTANT_Methodref_info (cls.getThisClassIndex (), nametype_index));
}
// add a CONSTANT_String that corresponds to the class name [in JVM format]:
{
m_classNameConstantIndex = constants.add (new CONSTANT_String_info (cls.getThisClass ().m_name_index));
}
// visit method collection:
visit (cls.getMethods (), ctx);
// if necessary, do SUID compensation [need to be done after method
// visits when it is known whether a <clinit> was added]:
if (m_doSUIDCompensation)
{
// compensation not necessary if the original clsdef already defined <clinit>:
boolean compensate = ((m_clinitStatus & IMetadataConstants.METHOD_ADDED) != 0);
int existingSUIDFieldCount = 0;
if (compensate)
{
// compensation not necessary if the original clsdef already controlled it via 'serialVersionUID':
{
final int [] existing = cls.getFields (SUID_FIELD_NAME);
existingSUIDFieldCount = existing.length;
if (existingSUIDFieldCount > 0)
{
final IFieldCollection fields = cls.getFields ();
for (int f = 0; f < existingSUIDFieldCount; ++ f)
{
final Field_info field = fields.get (existing [f]);
if ((field.getAccessFlags () & (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL))
== (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL))
{
// TODO: should also check for presence of a non-zero initializer
compensate = false;
break;
}
}
}
}
// compensation not necessary if we can determine that this class
// does not implement java.io.Serializable/Externalizable:
if (compensate && (cls.getThisClassIndex () == 0)) // no superclasses [this tool can't traverse inheritance chains]
{
boolean serializable = false;
final IInterfaceCollection interfaces = cls.getInterfaces ();
for (int i = 0, iLimit = interfaces.size (); i < iLimit; ++ i)
{
final CONSTANT_Class_info ifc = (CONSTANT_Class_info) constants.get (interfaces.get (i));
final String ifcName = ifc.getName (cls);
if (JAVA_IO_SERIALIZABLE_NAME.equals (ifcName) || JAVA_IO_EXTERNALIZABLE_NAME.equals (ifcName))
{
serializable = true;
break;
}
}
if (! serializable) compensate = false;
}
}
if (compensate)
{
if (existingSUIDFieldCount > 0)
{
// if we get here, the class declares a 'serialVersionUID' field
// that is not both static and final and/or is not initialized
// statically: warn that SUID compensation may not work
m_log.warning ("class [" + clsName + "] declares a 'serialVersionUID'");
m_log.warning ("field that is not static and final: this is likely an implementation mistake");
m_log.warning ("and can interfere with " + IAppConstants.APP_NAME + "'s SUID compensation");
}
final String fieldDescriptor = "J";
final int fieldModifiers = IAccessFlags.ACC_PRIVATE | IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL;
final IAttributeCollection fieldAttributes = ElementFactory.newAttributeCollection (MARK_ADDED_ELEMENTS_SYNTHETIC ? 2 : 1);
final int nameIndex = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CONSTANT_VALUE, true);
final int valueIndex = constants.add (new CONSTANT_Long_info (cls.computeSUID (true))); // ignore the added <clinit>
final ConstantValueAttribute_info initializer = new ConstantValueAttribute_info (nameIndex, valueIndex);
fieldAttributes.add (initializer);
if (MARK_ADDED_ELEMENTS_SYNTHETIC)
{
if (syntheticMarker == null) syntheticMarker = new SyntheticAttribute_info (m_syntheticStringIndex);
fieldAttributes.add (syntheticMarker);
}
cls.addField (SUID_FIELD_NAME, fieldDescriptor, fieldModifiers, fieldAttributes);
}
} // if (m_doSUIDCompensation)
// visit class attributes [to get src file name, etc]:
visit (cls.getAttributes (), ctx);
return ctx;
| public java.lang.Object | visit(IMethodCollection methods, java.lang.Object ctx)
final ClassDef cls = m_cls;
final boolean trace2 = m_log.atTRACE2 ();
final int originalMethodCount = methods.size ();
final boolean constructMetadata = m_metadata;
// create block count map: TODO: is the extra slot really needed?
// - create [potentially unused] slot for added <clinit>
m_classBlockCounts = new int [originalMethodCount + 1];
if (constructMetadata)
{
// prepare to collect metadata:
m_classBlockMetadata = new int [originalMethodCount + 1] [] []; // same comments as above
m_classMethodDescriptors = new MethodDescriptor [originalMethodCount];
}
// visit each original method:
for (int m = 0; m < originalMethodCount; ++ m)
{
final Method_info method = methods.get (m);
m_methodName = method.getName (cls);
if (trace2) m_log.trace2 ("visit", (method.isSynthetic () ? "synthetic " : "") + "method #" + m + ": [" + m_methodName + "]");
final boolean isClinit = IClassDefConstants.CLINIT_NAME.equals (m_methodName);
// TODO: research whether synthetic methods add nontrivially to line coverage or not
boolean excluded = false;
if (! isClinit)
{
if (m_excludeSyntheticMethods && method.isSynthetic ())
{
excluded = true;
if (trace2) m_log.trace2 ("visit", "skipped synthetic method");
}
else if (m_excludeBridgeMethods && method.isBridge ())
{
excluded = true;
if (trace2) m_log.trace2 ("visit", "skipped bridge method");
}
}
if (excluded)
{
if (constructMetadata)
{
m_classMethodDescriptors [m] = new MethodDescriptor (m_methodName, method.getDescriptor (cls), IMetadataConstants.METHOD_EXCLUDED, m_methodBlockSizes, null, 0);
}
}
else
{
if ((method.getAccessFlags () & (IAccessFlags.ACC_ABSTRACT | IAccessFlags.ACC_NATIVE)) != 0)
{
if (constructMetadata)
{
m_classMethodDescriptors [m] = new MethodDescriptor (m_methodName, method.getDescriptor (cls), IMetadataConstants.METHOD_ABSTRACT_OR_NATIVE, m_methodBlockSizes, null, 0);
}
if (trace2) m_log.trace2 ("visit", "skipped " + (method.isAbstract () ? "abstract" : "native") + " method");
}
else // this is a regular, non-<clinit> method that has bytecode:
{
// reset first line:
m_methodFirstLine = 0;
// set current method ID:
m_methodID = m;
if (isClinit)
{
// if <clinit> found: note the ID but delay processing until the very end
m_clinitID = m;
if (trace2) m_log.trace2 ("visit", "<clinit> method delayed");
}
else
{
// visit attributes [skip visit (IAttributeCollection) method]:
final IAttributeCollection attributes = method.getAttributes ();
final int attributeCount = attributes.size ();
for (int a = 0; a < attributeCount; ++ a)
{
final Attribute_info attribute = attributes.get (a);
attribute.accept (this, ctx);
}
if (constructMetadata)
{
if ($assert.ENABLED) $assert.ASSERT (m_classBlockCounts [m_methodID] > 0, "invalid block count for method " + m_methodID + ": " + m_classBlockCounts [m_methodID]);
if ($assert.ENABLED) $assert.ASSERT (m_methodBlockSizes != null && m_methodBlockSizes.length == m_classBlockCounts [m_methodID], "invalid block sizes map for method " + m_methodID);
final int [][] methodBlockMetadata = m_classBlockMetadata [m_methodID];
final int status = (methodBlockMetadata == null ? IMetadataConstants.METHOD_NO_LINE_NUMBER_TABLE : 0);
m_classMethodDescriptors [m] = new MethodDescriptor (m_methodName, method.getDescriptor (cls), status, m_methodBlockSizes, methodBlockMetadata, m_methodFirstLine);
}
}
}
}
}
// add <clinit> (and instrument if needed) [a <clinit> is always needed
// even if there are no other instrumented method to act as a load hook]:
final boolean instrumentClinit = false; // TODO: make use of this [to limit instrumentation to clinitHeader only], take into account whether we added and whether it is synthetic
final Method_info clinit;
if (m_clinitID >= 0)
{
// <clinit> existed in the original class: needs to be covered
// m_clinitStatus = 0;
clinit = methods.get (m_clinitID);
m_classInstrMethodCount = originalMethodCount;
}
else
{
// there is no <clinit> defined by the original class: add one [and mark it synthetic]
m_clinitStatus = IMetadataConstants.METHOD_ADDED; // mark as added by us
final int attribute_name_index = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CODE, true);
final int name_index = cls.addCONSTANT_Utf8 (IClassDefConstants.CLINIT_NAME, true);
final int descriptor_index = cls.addCONSTANT_Utf8 ("()V", true);
final IAttributeCollection attributes;
if (MARK_ADDED_ELEMENTS_SYNTHETIC)
attributes = ElementFactory.newAttributeCollection (2);
else
attributes = ElementFactory.newAttributeCollection (1);
final CodeAttribute_info code = new CodeAttribute_info (attribute_name_index,
0, 0,
new byte [] {(byte) _return},
AttributeElementFactory.newExceptionHandlerTable (0),
ElementFactory.newAttributeCollection (0));
attributes.add (code);
if (MARK_ADDED_ELEMENTS_SYNTHETIC)
{
attributes.add (new SyntheticAttribute_info (m_syntheticStringIndex));
}
clinit = new Method_info (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_PRIVATE, name_index, descriptor_index, attributes);
m_clinitID = cls.addMethod (clinit);
if (trace2) m_log.trace2 ("visit", "added synthetic <clinit> method");
// TODO: this should exclude <clinit> if it were added by us
m_classInstrMethodCount = originalMethodCount + 1;
}
if ($assert.ENABLED) $assert.ASSERT (m_classInstrMethodCount >= 0,
"m_classInstrMethodCount not set");
// visit <clinit>:
{
m_methodFirstLine = 0;
m_methodID = m_clinitID;
if (trace2) m_log.trace2 ("visit", (clinit.isSynthetic () ? "synthetic " : "") + "method #" + m_methodID + ": [<clinit>]");
final IAttributeCollection attributes = clinit.getAttributes ();
final int attributeCount = attributes.size ();
for (int a = 0; a < attributeCount; ++ a)
{
final Attribute_info attribute = attributes.get (a);
attribute.accept (this, ctx);
}
}
// add pre-<clinit> method:
{
final int attribute_name_index = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CODE, true);
final int name_index = cls.addCONSTANT_Utf8 (PRECLINIT_METHOD_NAME, false);
final int descriptor_index = cls.addCONSTANT_Utf8 ("()[[Z", false);
final IAttributeCollection attributes;
if (MARK_ADDED_ELEMENTS_SYNTHETIC)
attributes = ElementFactory.newAttributeCollection (2);
else
attributes = ElementFactory.newAttributeCollection (1);
final ByteArrayOStream buf = new ByteArrayOStream (PRECLINIT_INIT_CAPACITY);
{
final int [] blockCounts = m_classBlockCounts;
final int instrMethodCount = m_classInstrMethodCount; // actual number of methods to instrument may be less than the size of the block map
if ($assert.ENABLED) $assert.ASSERT (blockCounts != null && blockCounts.length >= instrMethodCount,
"invalid block count map");
// new and set COVERAGE_FIELD:
// push first dimension:
CodeGen.push_int_value (buf, cls, instrMethodCount);
// [stack +1]
// new boolean [][]:
final int type_index = cls.addClassref ("[[Z");
buf.write4 (_multianewarray,
type_index >>> 8, // indexbyte1
type_index, // indexbyte2
1); // only one dimension created here
// [stack +1]
// clone array ref:
buf.write4 (_dup,
// [stack +2]
// store in the static field
_putstatic,
m_coverageFieldrefIndex >>> 8, // indexbyte1
m_coverageFieldrefIndex); // indexbyte2
// [stack +1]
for (int m = 0; m < instrMethodCount; ++ m)
{
final int blockCount = blockCounts [m];
if (blockCount > 0)
{
// clone array ref:
buf.write (_dup);
// [stack +2]
// push outer dim index:
CodeGen.push_int_value (buf, cls, m);
// [stack +3]
// push dim:
CodeGen.push_int_value (buf, cls, blockCount);
// [stack +4]
// newarray boolean []:
buf.write3 (_newarray,
4, // "T_BOOLEAN"
// add subarray to the outer array:
_aastore);
// [stack +1]
}
}
// [stack +1]
{
// clone array ref
buf.write (_dup);
// [stack +2]
CodeGen.push_constant_index (buf, m_classNameConstantIndex);
// [stack +3]
buf.write3 (_ldc2_w,
m_stampIndex >>> 8, // indexbyte1
m_stampIndex); // indexbyte2
// [stack +5]
buf.write3 (_invokestatic,
m_registerMethodrefIndex >>> 8, // indexbyte1
m_registerMethodrefIndex); // indexbyte2
// [stack +1]
}
// pop and return extra array ref:
buf.write (_areturn);
// [stack +0]
}
final CodeAttribute_info code = new CodeAttribute_info (attribute_name_index,
5, 0, // adjust constants if the bytecode emitted above changes
EMPTY_BYTE_ARRAY,
AttributeElementFactory.newExceptionHandlerTable (0),
ElementFactory.newAttributeCollection (0));
code.setCode (buf.getByteArray (), buf.size ());
attributes.add (code);
if (MARK_ADDED_ELEMENTS_SYNTHETIC)
{
attributes.add (new SyntheticAttribute_info (m_syntheticStringIndex));
}
final Method_info preclinit = new Method_info (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_PRIVATE, name_index, descriptor_index, attributes);
cls.addMethod (preclinit);
if (trace2) m_log.trace2 ("visit", "added synthetic pre-<clinit> method");
}
if (constructMetadata)
{
if ($assert.ENABLED) $assert.ASSERT (m_classBlockCounts [m_methodID] > 0, "invalid block count for method " + m_methodID + " (" + IClassDefConstants.CLINIT_NAME + "): " + m_classBlockCounts [m_methodID]);
if ($assert.ENABLED) $assert.ASSERT (m_methodBlockSizes != null && m_methodBlockSizes.length == m_classBlockCounts [m_methodID], "invalid block sizes map for method " + m_methodID);
final int [][] methodBlockMetadata = m_classBlockMetadata [m_methodID];
m_clinitStatus |= (methodBlockMetadata == null ? IMetadataConstants.METHOD_NO_LINE_NUMBER_TABLE : 0);
// TODO: this still does not process not added/synthetic case
if ((m_clinitStatus & IMetadataConstants.METHOD_ADDED) == 0)
m_classMethodDescriptors [m_methodID] = new MethodDescriptor (IClassDefConstants.CLINIT_NAME, clinit.getDescriptor (cls), m_clinitStatus, m_methodBlockSizes, methodBlockMetadata, m_methodFirstLine);
}
return ctx;
| public java.lang.Object | visit(IAttributeCollection attributes, java.lang.Object ctx)
for (int a = 0, aCount = attributes.size (); a < aCount; ++ a)
{
// TODO: define a global way to set the mask set of attrs to be visited
attributes.get (a).accept (this, ctx);
}
return ctx;
| public java.lang.Object | visit(CodeAttribute_info attribute, java.lang.Object ctx)
final boolean trace2 = m_log.atTRACE2 ();
final boolean trace3 = m_log.atTRACE3 ();
final byte [] code = attribute.getCode ();
final int codeSize = attribute.getCodeSize ();
if (trace2) m_log.trace2 ("visit", "code attribute for method #" + m_methodID + ": size = " + codeSize);
final IntSet leaders = new IntSet ();
// instructionMap.get(ip) is the number of instructions in code[0-ip)
// [this map will include a mapping for code length as well]
final IntIntMap /* int(ip)->instr count */ instructionMap = new IntIntMap ();
// add first instruction and all exc handler start pcs:
leaders.add (0);
final IExceptionHandlerTable exceptions = attribute.getExceptionTable ();
final int exceptionCount = exceptions.size ();
for (int e = 0; e < exceptionCount; ++ e)
{
final Exception_info exception = exceptions.get (e);
leaders.add (exception.m_handler_pc);
}
final IntObjectMap branches = new IntObjectMap ();
// determine block leaders [an O(code length) loop]:
boolean branch = false;
boolean wide = false;
int instructionCount = 0;
instructionMap.put (0, 0);
for (int ip = 0; ip < codeSize; )
{
final int opcode = 0xFF & code [ip];
int size = 0; // will be set to -<real size> for special cases in the switch below
//if (trace3) m_log.trace3 ("parse", MNEMONICS [opcode]);
// "visitor.visit (opcode, wide, ip, null)":
{ // "opcode visit" logic:
int iv, ov;
if (branch)
{
// previous instruction was a branch: this one is a leader
leaders.add (ip);
branch = false;
}
switch (opcode)
{
case _ifeq:
case _iflt:
case _ifle:
case _ifne:
case _ifgt:
case _ifge:
case _ifnull:
case _ifnonnull:
case _if_icmpeq:
case _if_icmpne:
case _if_icmplt:
case _if_icmpgt:
case _if_icmple:
case _if_icmpge:
case _if_acmpeq:
case _if_acmpne:
{
//ov = getI2 (code, ip + 1);
int scan = ip + 1;
ov = (code [scan] << 8) | (0xFF & code [++ scan]);
final int target = ip + ov;
leaders.add (target);
branches.put (ip, new IFJUMP2 (opcode, target));
branch = true;
}
break;
case _goto:
case _jsr:
{
//ov = getI2 (code, ip + 1);
int scan = ip + 1;
ov = (code [scan] << 8) | (0xFF & code [++ scan]);
final int target = ip + ov;
leaders.add (target);
branches.put (ip, new JUMP2 (opcode, target));
branch = true;
}
break;
case _lookupswitch:
{
int scan = ip + 4 - (ip & 3); // eat padding
ov = (code [scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
leaders.add (ip + ov);
//final int npairs = getU4 (code, scan);
//scan += 4;
final int npairs = ((0xFF & code [++ scan]) << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
final int [] keys = new int [npairs];
final int [] targets = new int [npairs + 1];
targets [0] = ip + ov;
for (int p = 0; p < npairs; ++ p)
{
//iv = getI4 (code, scan);
//scan += 4;
iv = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
keys [p] = iv;
//ov = getI4 (code, scan);
//scan += 4;
ov = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
targets [p + 1] = ip + ov;
leaders.add (ip + ov);
}
branches.put (ip, new LOOKUPSWITCH (keys, targets));
branch = true;
size = ip - scan - 1; // special case
}
break;
case _tableswitch:
{
int scan = ip + 4 - (ip & 3); // eat padding
ov = (code [scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
leaders.add (ip + ov);
//final int low = getI4 (code, scan + 4);
final int low = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
//final int high = getI4 (code, scan + 8);
//scan += 12;
final int high = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
final int [] targets = new int [high - low + 2];
targets [0] = ip + ov;
for (int index = low; index <= high; ++ index)
{
//ov = getI4 (code, scan);
ov = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
targets [index - low + 1] = ip + ov;
leaders.add (ip + ov);
//scan += 4;
}
branches.put (ip, new TABLESWITCH (low, high, targets));
branch = true;
size = ip - scan - 1; // special case
}
break;
case _goto_w:
case _jsr_w:
{
int scan = ip + 1;
//ov = getI4 (code, ip + 1);
ov = (code [scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
final int target = ip + ov;
leaders.add (target);
branches.put (ip, new JUMP4 (opcode, target));
branch = true;
}
break;
case _ret:
{
int scan = ip + 1;
iv = wide ? (((0xFF & code [scan]) << 8) | (0xFF & code [++ scan])) : (0xFF & code [scan]);
branches.put (ip, new RET (opcode, iv));
branch = true;
}
break;
case _athrow:
case _ireturn:
case _lreturn:
case _freturn:
case _dreturn:
case _areturn:
case _return:
{
branches.put (ip, new TERMINATE (opcode));
branch = true;
}
break;
} // end of switch
} // end of processing the current opcode
// shift to the next instruction [this is the only block that adjusts 'ip']:
if (size == 0)
size = (wide ? WIDE_SIZE : NARROW_SIZE) [opcode];
else
size = -size;
ip += size;
wide = (opcode == _wide);
instructionMap.put (ip, ++ instructionCount);
} // end of for
// split 'code' into an ordered list of basic blocks [O(block count) loops]:
final int blockCount = leaders.size ();
if (trace2) m_log.trace2 ("visit", "method contains " + blockCount + " basic blocks");
final BlockList blocks = new BlockList (blockCount);
final int [] _leaders = new int [blockCount + 1]; // room for end-of-code leader at the end
leaders.values (_leaders, 0);
_leaders [blockCount] = codeSize;
Arrays.sort (_leaders);
final int [] _branch_locations = branches.keys ();
Arrays.sort (_branch_locations);
final IntIntMap leaderToBlockID = new IntIntMap (_leaders.length);
if (m_metadata)
{
// help construct a MethodDescriptor for the current method:
m_methodBlockSizes = new int [blockCount];
m_methodBlockOffsets = _leaders;
}
// compute signature even if metadata is not needed (because the instrumented
// classdef uses it):
consumeSignatureData (m_methodID, _leaders);
// pass 1:
final int [] intHolder = new int [1];
int instr_count = 0, prev_instr_count;
for (int bl = 0, br = 0; bl < blockCount; ++ bl)
{
final Block block = new Block ();
blocks.m_blocks.add (block);
final int leader = _leaders [bl];
block.m_first = leader; // m_first set
leaderToBlockID.put (leader, bl);
final int next_leader = _leaders [bl + 1];
boolean branchDelimited = false;
prev_instr_count = instr_count;
if (_branch_locations.length > br)
{
final int next_branch_location = _branch_locations [br];
if (next_branch_location < next_leader)
{
branchDelimited = true;
block.m_length = next_branch_location - leader; // m_length set
if ($assert.ENABLED)
$assert.ASSERT (instructionMap.get (next_branch_location, intHolder), "no mapping for " + next_branch_location);
else
instructionMap.get (next_branch_location, intHolder);
instr_count = intHolder [0] + 1; // [+ 1 for the branch]
block.m_branch = (Branch) branches.get (next_branch_location);
block.m_branch.m_parentBlockID = bl; // m_branch set
++ br;
}
}
if (! branchDelimited)
{
block.m_length = next_leader - leader; // m_length set
if ($assert.ENABLED)
$assert.ASSERT (instructionMap.get (next_leader, intHolder), "no mapping for " + next_leader);
else
instructionMap.get (next_leader, intHolder);
instr_count = intHolder [0];
}
block.m_instrCount = instr_count - prev_instr_count; // m_instrCount set
if ($assert.ENABLED) $assert.ASSERT (block.m_length == 0 || block.m_instrCount > 0, "invalid instr count for block " + bl + ": " + block.m_instrCount);
if (m_metadata) m_methodBlockSizes [bl] = block.m_instrCount;
}
// pass 2:
final Block [] _blocks = (Block []) blocks.m_blocks.toArray (new Block [blockCount]);
for (int l = 0; l < blockCount; ++ l)
{
final Block block = _blocks [l];
if (block.m_branch != null)
{
final int [] targets = block.m_branch.m_targets;
if (targets != null)
{
for (int t = 0, targetCount = targets.length; t < targetCount; ++ t)
{
// TODO: HACK ! convert block absolute offsets to block IDs:
if ($assert.ENABLED)
$assert.ASSERT (leaderToBlockID.get (targets [t], intHolder), "no mapping for " + targets [t]);
else
leaderToBlockID.get (targets [t], intHolder);
targets [t] = intHolder [0];
}
}
}
}
// update block count map [used later by <clinit> visit]:
m_classBlockCounts [m_methodID] = blockCount;
// actual basic block instrumentation:
{
if (trace2) m_log.trace2 ("visit", "instrumenting... ");
// determine the local var index for the var that will alias COVERAGE_FIELD:
final int localVarIndex = attribute.m_max_locals ++;
if (m_methodID == m_clinitID) // note: m_clinitID can be -1 if <clinit> has not been visited yet
{
// add a long stamp constant after all the original methods have been visited:
m_stampIndex = m_cls.getConstants ().add (new CONSTANT_Long_info (m_classSignature));
blocks.m_header = new clinitHeader (this, localVarIndex);
}
else
blocks.m_header = new methodHeader (this, localVarIndex);
int headerMaxStack = blocks.m_header.maxstack ();
int methodMaxStack = 0;
for (int l = 0; l < blockCount; ++ l)
{
final Block block = _blocks [l];
final CodeSegment insertion = new BlockSegment (this, localVarIndex, l);
block.m_insertion = insertion;
final int insertionMaxStack = insertion.maxstack ();
if (insertionMaxStack > methodMaxStack)
methodMaxStack = insertionMaxStack;
}
// update maxstack as needed [it can only grow]:
{
final int oldMaxStack = attribute.m_max_stack;
attribute.m_max_stack += methodMaxStack; // this is not precise, but still need to add because the insertion may be happening at the old maxstack point
if (headerMaxStack > attribute.m_max_stack)
attribute.m_max_stack = headerMaxStack;
if (trace3) m_log.trace3 ("visit", "increasing maxstack by " + (attribute.m_max_stack - oldMaxStack));
}
if ($assert.ENABLED) $assert.ASSERT (blocks.m_header != null, "header not set");
}
// assemble all blocks into an instrumented code block:
if (trace2) m_log.trace2 ("visit", "assembling... ");
int newcodeCapacity = codeSize << 1;
if (newcodeCapacity < EMIT_CTX_MIN_INIT_CAPACITY) newcodeCapacity = EMIT_CTX_MIN_INIT_CAPACITY;
final ByteArrayOStream newcode = new ByteArrayOStream (newcodeCapacity); // TODO: empirical capacity
final EmitCtx emitctx = new EmitCtx (blocks, newcode);
// create a jump adjustment map:
final int [] jumpAdjOffsets = new int [blockCount]; // room for initial 0 + (blockCount - 1)
final int [] jumpAdjMap = new int [jumpAdjOffsets.length]; // room for initial 0 + (blockCount - 1)
if ($assert.ENABLED) $assert.ASSERT (jumpAdjOffsets.length == jumpAdjMap.length,
"jumpAdjOffsets and jumpAdjMap length mismatch");
// header:
blocks.m_header.emit (emitctx);
// jumpAdjOffsets [0] = 0: redundant
jumpAdjMap [0] = emitctx.m_out.size ();
// rest of blocks:
for (int l = 0; l < blockCount; ++ l)
{
final Block block = _blocks [l];
if (l + 1 < blockCount)
{
jumpAdjOffsets [l + 1] = _blocks [l].m_first + _blocks [l].m_length; // implies the insertion goes just before the branch
}
block.emit (emitctx, code);
// TODO: this breaks if code can shrink:
if (l + 1 < blockCount)
{
jumpAdjMap [l + 1] = emitctx.m_out.size () - _blocks [l + 1].m_first;
}
}
m_methodJumpAdjOffsets = jumpAdjOffsets;
m_methodJumpAdjValues = jumpAdjMap;
if (trace3)
{
final StringBuffer s = new StringBuffer ("jump adjustment map:" + EOL);
for (int a = 0; a < jumpAdjOffsets.length; ++ a)
{
s.append (" " + jumpAdjOffsets [a] + ": +" + jumpAdjMap [a]);
if (a < jumpAdjOffsets.length - 1) s.append (EOL);
}
m_log.trace3 ("visit", s.toString ());
}
final byte [] _newcode = newcode.getByteArray (); // note: not cloned
final int _newcodeSize = newcode.size ();
// [all blocks have had their m_first adjusted]
// backpatching pass:
if (trace3) m_log.trace3 ("visit", "backpatching " + emitctx.m_backpatchQueue.size () + " ip(s)");
for (Iterator i = emitctx.m_backpatchQueue.iterator (); i.hasNext (); )
{
final int [] patchData = (int []) i.next ();
int ip = patchData [1];
if ($assert.ENABLED) $assert.ASSERT (patchData != null, "null patch data for ip " + ip);
final int jump = _blocks [patchData [3]].m_first - patchData [2];
if ($assert.ENABLED) $assert.ASSERT (jump > 0, "negative backpatch jump offset " + jump + " for ip " + ip);
switch (patchData [0])
{
case 4:
{
_newcode [ip ++] = (byte) (jump >>> 24);
_newcode [ip ++] = (byte) (jump >>> 16);
} // *FALL THROUGH*
case 2:
{
_newcode [ip ++] = (byte) (jump >>> 8);
_newcode [ip] = (byte) jump;
}
}
}
attribute.setCode (_newcode, _newcodeSize);
if (trace2) m_log.trace2 ("visit", "method assembled into " + _newcodeSize + " code bytes");
// adjust bytecode offsets in the exception table:
final IExceptionHandlerTable exceptionTable = attribute.getExceptionTable ();
for (int e = 0; e < exceptionTable.size (); ++ e)
{
final Exception_info exception = exceptionTable.get (e);
int adjSegment = lowbound (jumpAdjOffsets, exception.m_start_pc);
exception.m_start_pc += jumpAdjMap [adjSegment];
adjSegment = lowbound (jumpAdjOffsets, exception.m_end_pc);
exception.m_end_pc += jumpAdjMap [adjSegment];
adjSegment = lowbound (jumpAdjOffsets, exception.m_handler_pc);
exception.m_handler_pc += jumpAdjMap [adjSegment];
}
// visit other nested attributes [LineNumberAttribute, etc]:
final IAttributeCollection attributes = attribute.getAttributes ();
final int attributeCount = attributes.size ();
for (int a = 0; a < attributeCount; ++ a)
{
final Attribute_info nested = attributes.get (a);
nested.accept (this, ctx);
}
return ctx;
| public java.lang.Object | visit(LineNumberTableAttribute_info attribute, java.lang.Object ctx)
final boolean trace2 = m_log.atTRACE2 ();
final boolean trace3 = m_log.atTRACE3 ();
if (trace2) m_log.trace2 ("visit", "attribute: [" + attribute.getName (m_cls) + "]");
final int lineCount = attribute.size ();
if (m_metadata)
{
if (trace2) m_log.trace2 ("visit", "processing line number table for metadata...");
final int blockCount = m_classBlockCounts [m_methodID];
if ($assert.ENABLED) $assert.ASSERT (blockCount > 0, "invalid method block count for method " + m_methodID);
final int [][] blockLineMap = new int [blockCount][];
if ($assert.ENABLED) $assert.ASSERT (blockCount + 1 == m_methodBlockOffsets.length,
"invalid m_methodBlockOffsets");
if (lineCount == 0)
{
for (int bl = 0; bl < blockCount; ++ bl)
blockLineMap [bl] = EMPTY_INT_ARRAY;
}
else
{
// TODO: this code does not work if there are multiple LineNumberTableAttribute attributes for the method
final LineNumber_info [] sortedLines = new LineNumber_info [attribute.size ()];
for (int l = 0; l < lineCount; ++ l)
{
final LineNumber_info line = attribute.get (l);
sortedLines [l] = line;
}
Arrays.sort (sortedLines, LINE_NUMBER_COMPARATOR);
// construct block->line mapping: TODO: is the loop below the fastest it can be done?
final int [] methodBlockOffsets = m_methodBlockOffsets;
LineNumber_info line = sortedLines [0]; // never null
LineNumber_info prev_line = null;
// remember the first line:
m_methodFirstLine = line.m_line_number;
for (int bl = 0, l = 0; bl < blockCount; ++ bl)
{
final IntSet blockLines = new IntSet ();
if ((prev_line != null) && (line.m_start_pc > methodBlockOffsets [bl]))
{
blockLines.add (prev_line.m_line_number);
}
while (line.m_start_pc < methodBlockOffsets [bl + 1])
{
blockLines.add (line.m_line_number);
if (l == lineCount - 1)
break;
else
{
prev_line = line;
line = sortedLines [++ l]; // advance to the next line
}
}
blockLineMap [bl] = blockLines.values ();
}
}
m_classBlockMetadata [m_methodID] = blockLineMap;
if (trace3)
{
StringBuffer s = new StringBuffer ("block-line map for method #" + m_methodID + ":");
for (int bl = 0; bl < blockCount; ++ bl)
{
s.append (EOL);
s.append (" block " + bl + ": ");
final int [] lines = blockLineMap [bl];
for (int l = 0; l < lines.length; ++ l)
{
if (l != 0) s.append (", ");
s.append (lines [l]);
}
}
m_log.trace3 ("visit", s.toString ());
}
}
for (int l = 0; l < lineCount; ++ l)
{
final LineNumber_info line = attribute.get (l);
// TODO: make this faster using either table assist or the sorted array in 'sortedLines'
// adjust bytecode offset for line number mapping:
int adjSegment = lowbound (m_methodJumpAdjOffsets, line.m_start_pc);
line.m_start_pc += m_methodJumpAdjValues [adjSegment];
}
return ctx;
| public java.lang.Object | visit(ExceptionsAttribute_info attribute, java.lang.Object ctx)
return ctx;
| public java.lang.Object | visit(ConstantValueAttribute_info attribute, java.lang.Object ctx)
return ctx;
|
|