FileDocCategorySizeDatePackage
InstrVisitor.javaAPI DocAndroid 1.5 API85927Wed May 06 22:41:16 BST 2009com.vladium.emma.instr

InstrVisitor

public final class InstrVisitor extends AbstractClassDefVisitor implements IAttributeVisitor, IClassDefVisitor, com.vladium.util.IConstants, com.vladium.jcd.opcodes.IOpcodes
author
Vlad Roubtsov, (C) 2003

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 voidconsumeSignatureData(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 intlowbound(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 voidprocess(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 voidreset()

        // 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 voidsetClassName(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.Objectvisit(SourceFileAttribute_info attribute, java.lang.Object ctx)

        m_classSrcFileName = attribute.getSourceFile (m_cls).m_value;

        return ctx;
    
public java.lang.Objectvisit(SyntheticAttribute_info attribute, java.lang.Object ctx)

        return ctx;
    
public java.lang.Objectvisit(BridgeAttribute_info attribute, java.lang.Object ctx)

        return ctx;
    
public java.lang.Objectvisit(InnerClassesAttribute_info attribute, java.lang.Object ctx)

        return ctx;
    
public java.lang.Objectvisit(GenericAttribute_info attribute, java.lang.Object ctx)

        return ctx;
    
public java.lang.Objectvisit(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.Objectvisit(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.Objectvisit(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.Objectvisit(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.Objectvisit(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.Objectvisit(ExceptionsAttribute_info attribute, java.lang.Object ctx)

        return ctx;
    
public java.lang.Objectvisit(ConstantValueAttribute_info attribute, java.lang.Object ctx)

        return ctx;