1/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. 2 * 3 * This program and the accompanying materials are made available under 4 * the terms of the Common Public License v1.0 which accompanies this distribution, 5 * and is available at http://www.eclipse.org/legal/cpl-v10.html 6 * 7 * $Id: InstrVisitor.java,v 1.1.1.1.2.4 2004/07/16 23:32:28 vlad_r Exp $ 8 */ 9package com.vladium.emma.instr; 10 11import java.io.IOException; 12import java.util.ArrayList; 13import java.util.Arrays; 14import java.util.Comparator; 15import java.util.Iterator; 16import java.util.List; 17 18import com.vladium.jcd.cls.*; 19import com.vladium.jcd.cls.attribute.*; 20import com.vladium.jcd.cls.constant.CONSTANT_Class_info; 21import com.vladium.jcd.cls.constant.CONSTANT_Long_info; 22import com.vladium.jcd.cls.constant.CONSTANT_Methodref_info; 23import com.vladium.jcd.cls.constant.CONSTANT_String_info; 24import com.vladium.jcd.compiler.CodeGen; 25import com.vladium.jcd.lib.Types; 26import com.vladium.jcd.opcodes.IOpcodes; 27import com.vladium.logging.Logger; 28import com.vladium.util.ByteArrayOStream; 29import com.vladium.util.IConstants; 30import com.vladium.util.IntIntMap; 31import com.vladium.util.IntObjectMap; 32import com.vladium.util.IntSet; 33import com.vladium.util.asserts.$assert; 34import com.vladium.emma.IAppConstants; 35import com.vladium.emma.data.ClassDescriptor; 36import com.vladium.emma.data.CoverageOptions; 37import com.vladium.emma.data.IMetadataConstants; 38import com.vladium.emma.data.MethodDescriptor; 39 40// ---------------------------------------------------------------------------- 41/** 42 * @author Vlad Roubtsov, (C) 2003 43 */ 44public 45final class InstrVisitor extends AbstractClassDefVisitor 46 implements IClassDefVisitor, IAttributeVisitor, IOpcodes, IConstants 47{ 48 // public: ................................................................ 49 50 // TODO: m_instrument is unused 51 52 public static final class InstrResult 53 { 54 public boolean m_instrumented; 55 public ClassDescriptor m_descriptor; 56 57 } // end of nested class 58 59 public InstrVisitor (final CoverageOptions options) 60 { 61 m_excludeSyntheticMethods = options.excludeSyntheticMethods (); 62 m_excludeBridgeMethods = options.excludeBridgeMethods (); 63 m_doSUIDCompensation = options.doSUIDCompensation (); 64 65 m_log = Logger.getLogger (); 66 } 67 68 /** 69 * Analyzes 'cls' and/or instruments it for coverage: 70 * <ul> 71 * <li> if 'instrument' is true, the class definition is instrumented for 72 * coverage if that is feasible 73 * <li> if 'metadata' is true, the class definition is analysed 74 * to create a {@link ClassDescriptor} for the original class definition 75 * </ul> 76 * This method returns null if 'metadata' is 'false' *or* if 'cls' is an 77 * interface [the latter precludes coverage of interface static 78 * initializers and may be removed in the future].<P> 79 * 80 * NOTE: if 'instrument' is 'true', the caller should always assume that 'cls' 81 * has been mutated by this method even if it returned null. The caller should 82 * then revert to the original class definition that was created as a 83 * <code>cls.clone()</code> or by retaining the original definition bytes. 84 * This part of contract is for efficienty and also simplifies the implementation. 85 */ 86 public void process (final ClassDef cls, 87 final boolean ignoreAlreadyInstrumented, 88 final boolean instrument, final boolean metadata, 89 final InstrResult out) 90 { 91 out.m_instrumented = false; 92 out.m_descriptor = null; 93 94 if (! (instrument || metadata)) return; // nothing to do 95 96 if (cls.isInterface ()) 97 return; // skip interfaces [may change in the future] 98 else 99 { 100 reset (); 101 102 m_cls = cls; 103 104 // TODO: handle classes that cannot be instrumented due to bytecode/JVM limitations 105 m_instrument = instrument; 106 m_metadata = metadata; 107 m_ignoreAlreadyInstrumented = ignoreAlreadyInstrumented; 108 109 // TODO: create 'no instrumentation' execution path here 110 111 visit ((ClassDef) null, null); // potentially changes m_instrument and m_metadata 112 113 if (m_metadata) 114 { 115 setClassName (cls.getName ()); 116 117 out.m_descriptor = new ClassDescriptor (m_classPackageName, m_className, m_classSignature, m_classSrcFileName, m_classMethodDescriptors); 118 } 119 120 out.m_instrumented = m_instrument; 121 } 122 } 123 124 125 // IClassDefVisitor: 126 127 public Object visit (final ClassDef ignore, final Object ctx) 128 { 129 final ClassDef cls = m_cls; 130 final String clsVMName = cls.getName (); 131 final String clsName = Types.vmNameToJavaName (clsVMName); 132 133 final boolean trace1 = m_log.atTRACE1 (); 134 if (trace1) m_log.trace1 ("visit", "class: [" + clsVMName + "]"); 135 136 137 // skip synthetic classes if enabled: 138 if (SKIP_SYNTHETIC_CLASSES && cls.isSynthetic ()) 139 { 140 m_instrument = false; 141 m_metadata = false; 142 143 if (trace1) m_log.trace1 ("visit", "skipping synthetic class"); 144 return ctx; 145 } 146 147 // TODO: ideally, this check should be done in outer scope somewhere 148 if (! m_warningIssued && clsName.startsWith (IAppConstants.APP_PACKAGE)) 149 { 150 m_warningIssued = true; 151 152 m_log.warning (IAppConstants.APP_NAME + " classes appear to be included on the instrumentation"); 153 m_log.warning ("path: this is not a correct way to use " + IAppConstants.APP_NAME); 154 } 155 156 // field uniqueness check done to detect double instrumentation: 157 { 158 final int [] existing = cls.getFields (COVERAGE_FIELD_NAME); 159 if (existing.length > 0) 160 { 161 m_instrument = false; 162 m_metadata = false; 163 164 if (m_ignoreAlreadyInstrumented) 165 { 166 if (trace1) m_log.trace1 ("visit", "skipping instrumented class"); 167 return ctx; 168 } 169 else 170 { 171 // TODO: use a app coded exception 172 throw new IllegalStateException ("class [" + clsName + "] appears to be instrumented already"); 173 } 174 } 175 } 176 177 final IConstantCollection constants = cls.getConstants (); 178 179 SyntheticAttribute_info syntheticMarker = null; 180 181 // cache the location of "Synthetic" string: 182 { 183 if (MARK_ADDED_ELEMENTS_SYNTHETIC) 184 m_syntheticStringIndex = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_SYNTHETIC, true); 185 } 186 187 // add a Fieldref for the runtime coverage collector field: 188 { 189 // note: this is a bit premature if the class has no methods that need 190 // instrumentation 191 // TODO: the mutated version is easily discardable; however, this case 192 // needs attention at metadata/report generation level 193 194 final int coverageFieldOffset; 195 final String fieldDescriptor = "[[Z"; 196 197 // note that post-4019 builds can modify this field outside of <clinit> (although 198 // it can only happen as part of initializing a set of classes); however, it is legal 199 // to declare this field final: 200 201 final int fieldModifiers = IAccessFlags.ACC_PRIVATE | IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL; 202 203 // add declared field: 204 if (MARK_ADDED_ELEMENTS_SYNTHETIC) 205 { 206 final IAttributeCollection fieldAttributes = ElementFactory.newAttributeCollection (1); 207 208 syntheticMarker = new SyntheticAttribute_info (m_syntheticStringIndex); 209 fieldAttributes.add (syntheticMarker); 210 211 coverageFieldOffset = cls.addField (COVERAGE_FIELD_NAME, fieldDescriptor, 212 fieldModifiers, fieldAttributes); 213 } 214 else 215 { 216 coverageFieldOffset = cls.addField (COVERAGE_FIELD_NAME, fieldDescriptor, 217 fieldModifiers); 218 } 219 220 //add fieldref: 221 m_coverageFieldrefIndex = cls.addFieldref (coverageFieldOffset); 222 } 223 224 // add a Methodref for Runtime.r(): 225 { 226 // TODO: compute this without loading Runtime Class? 227 final String classJVMName = "com/vladium/emma/rt/RT"; 228 final int class_index = cls.addClassref (classJVMName); 229 230 // NOTE: keep this descriptor in sync with the actual signature 231 final String methodDescriptor = "([[ZLjava/lang/String;J)V"; 232 final int nametype_index = cls.addNameType ("r", methodDescriptor); 233 234 m_registerMethodrefIndex = constants.add (new CONSTANT_Methodref_info (class_index, nametype_index)); 235 } 236 237 // SF FR 971186: split the init logic into a separate method so it could 238 // be called from regular method headers if necessary: 239 240 // add a Methodref for pre-<clinit> method: 241 { 242 // NOTE: keep this descriptor in sync with the actual signature 243 final String methodDescriptor = "()[[Z"; 244 final int nametype_index = cls.addNameType (PRECLINIT_METHOD_NAME, methodDescriptor); 245 246 m_preclinitMethodrefIndex = constants.add (new CONSTANT_Methodref_info (cls.getThisClassIndex (), nametype_index)); 247 } 248 249 // add a CONSTANT_String that corresponds to the class name [in JVM format]: 250 { 251 m_classNameConstantIndex = constants.add (new CONSTANT_String_info (cls.getThisClass ().m_name_index)); 252 } 253 254 // visit method collection: 255 visit (cls.getMethods (), ctx); 256 257 // if necessary, do SUID compensation [need to be done after method 258 // visits when it is known whether a <clinit> was added]: 259 if (m_doSUIDCompensation) 260 { 261 // compensation not necessary if the original clsdef already defined <clinit>: 262 boolean compensate = ((m_clinitStatus & IMetadataConstants.METHOD_ADDED) != 0); 263 264 int existingSUIDFieldCount = 0; 265 266 if (compensate) 267 { 268 // compensation not necessary if the original clsdef already controlled it via 'serialVersionUID': 269 { 270 final int [] existing = cls.getFields (SUID_FIELD_NAME); 271 existingSUIDFieldCount = existing.length; 272 273 if (existingSUIDFieldCount > 0) 274 { 275 final IFieldCollection fields = cls.getFields (); 276 277 for (int f = 0; f < existingSUIDFieldCount; ++ f) 278 { 279 final Field_info field = fields.get (existing [f]); 280 if ((field.getAccessFlags () & (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL)) 281 == (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL)) 282 { 283 // TODO: should also check for presence of a non-zero initializer 284 285 compensate = false; 286 break; 287 } 288 } 289 } 290 } 291 292 // compensation not necessary if we can determine that this class 293 // does not implement java.io.Serializable/Externalizable: 294 295 if (compensate && (cls.getThisClassIndex () == 0)) // no superclasses [this tool can't traverse inheritance chains] 296 { 297 boolean serializable = false; 298 299 final IInterfaceCollection interfaces = cls.getInterfaces (); 300 for (int i = 0, iLimit = interfaces.size (); i < iLimit; ++ i) 301 { 302 final CONSTANT_Class_info ifc = (CONSTANT_Class_info) constants.get (interfaces.get (i)); 303 final String ifcName = ifc.getName (cls); 304 if (JAVA_IO_SERIALIZABLE_NAME.equals (ifcName) || JAVA_IO_EXTERNALIZABLE_NAME.equals (ifcName)) 305 { 306 serializable = true; 307 break; 308 } 309 } 310 311 if (! serializable) compensate = false; 312 } 313 } 314 315 if (compensate) 316 { 317 if (existingSUIDFieldCount > 0) 318 { 319 // if we get here, the class declares a 'serialVersionUID' field 320 // that is not both static and final and/or is not initialized 321 // statically: warn that SUID compensation may not work 322 323 m_log.warning ("class [" + clsName + "] declares a 'serialVersionUID'"); 324 m_log.warning ("field that is not static and final: this is likely an implementation mistake"); 325 m_log.warning ("and can interfere with " + IAppConstants.APP_NAME + "'s SUID compensation"); 326 } 327 328 final String fieldDescriptor = "J"; 329 final int fieldModifiers = IAccessFlags.ACC_PRIVATE | IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL; 330 final IAttributeCollection fieldAttributes = ElementFactory.newAttributeCollection (MARK_ADDED_ELEMENTS_SYNTHETIC ? 2 : 1); 331 332 final int nameIndex = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CONSTANT_VALUE, true); 333 final int valueIndex = constants.add (new CONSTANT_Long_info (cls.computeSUID (true))); // ignore the added <clinit> 334 335 final ConstantValueAttribute_info initializer = new ConstantValueAttribute_info (nameIndex, valueIndex); 336 fieldAttributes.add (initializer); 337 338 if (MARK_ADDED_ELEMENTS_SYNTHETIC) 339 { 340 if (syntheticMarker == null) syntheticMarker = new SyntheticAttribute_info (m_syntheticStringIndex); 341 fieldAttributes.add (syntheticMarker); 342 } 343 344 cls.addField (SUID_FIELD_NAME, fieldDescriptor, fieldModifiers, fieldAttributes); 345 } 346 347 } // if (m_doSUIDCompensation) 348 349 // visit class attributes [to get src file name, etc]: 350 visit (cls.getAttributes (), ctx); 351 352 return ctx; 353 } 354 355 356 public Object visit (final IMethodCollection methods, final Object ctx) 357 { 358 final ClassDef cls = m_cls; 359 360 final boolean trace2 = m_log.atTRACE2 (); 361 362 final int originalMethodCount = methods.size (); 363 final boolean constructMetadata = m_metadata; 364 365 // create block count map: TODO: is the extra slot really needed? 366 // - create [potentially unused] slot for added <clinit> 367 m_classBlockCounts = new int [originalMethodCount + 1]; 368 369 if (constructMetadata) 370 { 371 // prepare to collect metadata: 372 m_classBlockMetadata = new int [originalMethodCount + 1] [] []; // same comments as above 373 374 m_classMethodDescriptors = new MethodDescriptor [originalMethodCount]; 375 } 376 377 378 // visit each original method: 379 380 for (int m = 0; m < originalMethodCount; ++ m) 381 { 382 final Method_info method = methods.get (m); 383 m_methodName = method.getName (cls); 384 if (trace2) m_log.trace2 ("visit", (method.isSynthetic () ? "synthetic " : "") + "method #" + m + ": [" + m_methodName + "]"); 385 386 final boolean isClinit = IClassDefConstants.CLINIT_NAME.equals (m_methodName); 387 388 // TODO: research whether synthetic methods add nontrivially to line coverage or not 389 390 boolean excluded = false; 391 392 if (! isClinit) 393 { 394 if (m_excludeSyntheticMethods && method.isSynthetic ()) 395 { 396 excluded = true; 397 if (trace2) m_log.trace2 ("visit", "skipped synthetic method"); 398 } 399 else if (m_excludeBridgeMethods && method.isBridge ()) 400 { 401 excluded = true; 402 if (trace2) m_log.trace2 ("visit", "skipped bridge method"); 403 } 404 } 405 406 if (excluded) 407 { 408 if (constructMetadata) 409 { 410 m_classMethodDescriptors [m] = new MethodDescriptor (m_methodName, method.getDescriptor (cls), IMetadataConstants.METHOD_EXCLUDED, m_methodBlockSizes, null, 0); 411 } 412 } 413 else 414 { 415 if ((method.getAccessFlags () & (IAccessFlags.ACC_ABSTRACT | IAccessFlags.ACC_NATIVE)) != 0) 416 { 417 if (constructMetadata) 418 { 419 m_classMethodDescriptors [m] = new MethodDescriptor (m_methodName, method.getDescriptor (cls), IMetadataConstants.METHOD_ABSTRACT_OR_NATIVE, m_methodBlockSizes, null, 0); 420 } 421 422 if (trace2) m_log.trace2 ("visit", "skipped " + (method.isAbstract () ? "abstract" : "native") + " method"); 423 } 424 else // this is a regular, non-<clinit> method that has bytecode: 425 { 426 // reset first line: 427 m_methodFirstLine = 0; 428 429 // set current method ID: 430 m_methodID = m; 431 432 if (isClinit) 433 { 434 // if <clinit> found: note the ID but delay processing until the very end 435 m_clinitID = m; 436 if (trace2) m_log.trace2 ("visit", "<clinit> method delayed"); 437 } 438 else 439 { 440 // visit attributes [skip visit (IAttributeCollection) method]: 441 final IAttributeCollection attributes = method.getAttributes (); 442 final int attributeCount = attributes.size (); 443 for (int a = 0; a < attributeCount; ++ a) 444 { 445 final Attribute_info attribute = attributes.get (a); 446 attribute.accept (this, ctx); 447 } 448 449 if (constructMetadata) 450 { 451 if ($assert.ENABLED) $assert.ASSERT (m_classBlockCounts [m_methodID] > 0, "invalid block count for method " + m_methodID + ": " + m_classBlockCounts [m_methodID]); 452 if ($assert.ENABLED) $assert.ASSERT (m_methodBlockSizes != null && m_methodBlockSizes.length == m_classBlockCounts [m_methodID], "invalid block sizes map for method " + m_methodID); 453 454 final int [][] methodBlockMetadata = m_classBlockMetadata [m_methodID]; 455 final int status = (methodBlockMetadata == null ? IMetadataConstants.METHOD_NO_LINE_NUMBER_TABLE : 0); 456 457 m_classMethodDescriptors [m] = new MethodDescriptor (m_methodName, method.getDescriptor (cls), status, m_methodBlockSizes, methodBlockMetadata, m_methodFirstLine); 458 } 459 } 460 } 461 } 462 } 463 464 // add <clinit> (and instrument if needed) [a <clinit> is always needed 465 // even if there are no other instrumented method to act as a load hook]: 466 467 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 468 final Method_info clinit; 469 470 if (m_clinitID >= 0) 471 { 472 // <clinit> existed in the original class: needs to be covered 473 474 // m_clinitStatus = 0; 475 clinit = methods.get (m_clinitID); 476 477 m_classInstrMethodCount = originalMethodCount; 478 } 479 else 480 { 481 // there is no <clinit> defined by the original class: add one [and mark it synthetic] 482 483 m_clinitStatus = IMetadataConstants.METHOD_ADDED; // mark as added by us 484 485 final int attribute_name_index = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CODE, true); 486 final int name_index = cls.addCONSTANT_Utf8 (IClassDefConstants.CLINIT_NAME, true); 487 final int descriptor_index = cls.addCONSTANT_Utf8 ("()V", true); 488 489 final IAttributeCollection attributes; 490 491 if (MARK_ADDED_ELEMENTS_SYNTHETIC) 492 attributes = ElementFactory.newAttributeCollection (2); 493 else 494 attributes = ElementFactory.newAttributeCollection (1); 495 496 final CodeAttribute_info code = new CodeAttribute_info (attribute_name_index, 497 0, 0, 498 new byte [] {(byte) _return}, 499 AttributeElementFactory.newExceptionHandlerTable (0), 500 ElementFactory.newAttributeCollection (0)); 501 502 attributes.add (code); 503 504 if (MARK_ADDED_ELEMENTS_SYNTHETIC) 505 { 506 attributes.add (new SyntheticAttribute_info (m_syntheticStringIndex)); 507 } 508 509 clinit = new Method_info (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_PRIVATE, name_index, descriptor_index, attributes); 510 511 m_clinitID = cls.addMethod (clinit); 512 513 if (trace2) m_log.trace2 ("visit", "added synthetic <clinit> method"); 514 515 // TODO: this should exclude <clinit> if it were added by us 516 m_classInstrMethodCount = originalMethodCount + 1; 517 } 518 519 if ($assert.ENABLED) $assert.ASSERT (m_classInstrMethodCount >= 0, 520 "m_classInstrMethodCount not set"); 521 522 523 // visit <clinit>: 524 { 525 m_methodFirstLine = 0; 526 m_methodID = m_clinitID; 527 528 if (trace2) m_log.trace2 ("visit", (clinit.isSynthetic () ? "synthetic " : "") + "method #" + m_methodID + ": [<clinit>]"); 529 530 final IAttributeCollection attributes = clinit.getAttributes (); 531 final int attributeCount = attributes.size (); 532 for (int a = 0; a < attributeCount; ++ a) 533 { 534 final Attribute_info attribute = attributes.get (a); 535 attribute.accept (this, ctx); 536 } 537 } 538 539 // add pre-<clinit> method: 540 541 { 542 final int attribute_name_index = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CODE, true); 543 final int name_index = cls.addCONSTANT_Utf8 (PRECLINIT_METHOD_NAME, false); 544 final int descriptor_index = cls.addCONSTANT_Utf8 ("()[[Z", false); 545 546 final IAttributeCollection attributes; 547 548 if (MARK_ADDED_ELEMENTS_SYNTHETIC) 549 attributes = ElementFactory.newAttributeCollection (2); 550 else 551 attributes = ElementFactory.newAttributeCollection (1); 552 553 final ByteArrayOStream buf = new ByteArrayOStream (PRECLINIT_INIT_CAPACITY); 554 { 555 final int [] blockCounts = m_classBlockCounts; 556 final int instrMethodCount = m_classInstrMethodCount; // actual number of methods to instrument may be less than the size of the block map 557 558 if ($assert.ENABLED) $assert.ASSERT (blockCounts != null && blockCounts.length >= instrMethodCount, 559 "invalid block count map"); 560 561 // new and set COVERAGE_FIELD: 562 563 // push first dimension: 564 CodeGen.push_int_value (buf, cls, instrMethodCount); 565 566 // [stack +1] 567 568 // new boolean [][]: 569 final int type_index = cls.addClassref ("[[Z"); 570 buf.write4 (_multianewarray, 571 type_index >>> 8, // indexbyte1 572 type_index, // indexbyte2 573 1); // only one dimension created here 574 575 // [stack +1] 576 577 // clone array ref: 578 buf.write4 (_dup, 579 580 // [stack +2] 581 582 // store in the static field 583 _putstatic, 584 m_coverageFieldrefIndex >>> 8, // indexbyte1 585 m_coverageFieldrefIndex); // indexbyte2 586 587 // [stack +1] 588 589 for (int m = 0; m < instrMethodCount; ++ m) 590 { 591 final int blockCount = blockCounts [m]; 592 if (blockCount > 0) 593 { 594 // clone array ref: 595 buf.write (_dup); 596 597 // [stack +2] 598 599 // push outer dim index: 600 CodeGen.push_int_value (buf, cls, m); 601 602 // [stack +3] 603 604 // push dim: 605 CodeGen.push_int_value (buf, cls, blockCount); 606 607 // [stack +4] 608 609 // newarray boolean []: 610 buf.write3 (_newarray, 611 4, // "T_BOOLEAN" 612 613 // add subarray to the outer array: 614 _aastore); 615 616 // [stack +1] 617 } 618 } 619 620 // [stack +1] 621 622 { 623 // clone array ref 624 buf.write (_dup); 625 626 // [stack +2] 627 628 CodeGen.push_constant_index (buf, m_classNameConstantIndex); 629 630 // [stack +3] 631 632 buf.write3 (_ldc2_w, 633 m_stampIndex >>> 8, // indexbyte1 634 m_stampIndex); // indexbyte2 635 636 // [stack +5] 637 638 buf.write3 (_invokestatic, 639 m_registerMethodrefIndex >>> 8, // indexbyte1 640 m_registerMethodrefIndex); // indexbyte2 641 642 // [stack +1] 643 } 644 645 // pop and return extra array ref: 646 buf.write (_areturn); 647 648 // [stack +0] 649 } 650 651 final CodeAttribute_info code = new CodeAttribute_info (attribute_name_index, 652 5, 0, // adjust constants if the bytecode emitted above changes 653 EMPTY_BYTE_ARRAY, 654 AttributeElementFactory.newExceptionHandlerTable (0), 655 ElementFactory.newAttributeCollection (0)); 656 657 code.setCode (buf.getByteArray (), buf.size ()); 658 659 attributes.add (code); 660 661 if (MARK_ADDED_ELEMENTS_SYNTHETIC) 662 { 663 attributes.add (new SyntheticAttribute_info (m_syntheticStringIndex)); 664 } 665 666 final Method_info preclinit = new Method_info (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_PRIVATE, name_index, descriptor_index, attributes); 667 cls.addMethod (preclinit); 668 669 if (trace2) m_log.trace2 ("visit", "added synthetic pre-<clinit> method"); 670 } 671 672 673 if (constructMetadata) 674 { 675 if ($assert.ENABLED) $assert.ASSERT (m_classBlockCounts [m_methodID] > 0, "invalid block count for method " + m_methodID + " (" + IClassDefConstants.CLINIT_NAME + "): " + m_classBlockCounts [m_methodID]); 676 if ($assert.ENABLED) $assert.ASSERT (m_methodBlockSizes != null && m_methodBlockSizes.length == m_classBlockCounts [m_methodID], "invalid block sizes map for method " + m_methodID); 677 678 final int [][] methodBlockMetadata = m_classBlockMetadata [m_methodID]; 679 m_clinitStatus |= (methodBlockMetadata == null ? IMetadataConstants.METHOD_NO_LINE_NUMBER_TABLE : 0); 680 681 // TODO: this still does not process not added/synthetic case 682 683 if ((m_clinitStatus & IMetadataConstants.METHOD_ADDED) == 0) 684 m_classMethodDescriptors [m_methodID] = new MethodDescriptor (IClassDefConstants.CLINIT_NAME, clinit.getDescriptor (cls), m_clinitStatus, m_methodBlockSizes, methodBlockMetadata, m_methodFirstLine); 685 } 686 687 return ctx; 688 } 689 690 691 public Object visit (final IAttributeCollection attributes, Object ctx) 692 { 693 for (int a = 0, aCount = attributes.size (); a < aCount; ++ a) 694 { 695 // TODO: define a global way to set the mask set of attrs to be visited 696 attributes.get (a).accept (this, ctx); 697 } 698 699 return ctx; 700 } 701 702 703 // IAttributeVisitor: 704 705 public Object visit (final CodeAttribute_info attribute, final Object ctx) 706 { 707 final boolean trace2 = m_log.atTRACE2 (); 708 final boolean trace3 = m_log.atTRACE3 (); 709 710 final byte [] code = attribute.getCode (); 711 final int codeSize = attribute.getCodeSize (); 712 713 if (trace2) m_log.trace2 ("visit", "code attribute for method #" + m_methodID + ": size = " + codeSize); 714 715 final IntSet leaders = new IntSet (); 716 717 // instructionMap.get(ip) is the number of instructions in code[0-ip) 718 // [this map will include a mapping for code length as well] 719 final IntIntMap /* int(ip)->instr count */ instructionMap = new IntIntMap (); 720 721 // add first instruction and all exc handler start pcs: 722 leaders.add (0); 723 724 final IExceptionHandlerTable exceptions = attribute.getExceptionTable (); 725 final int exceptionCount = exceptions.size (); 726 for (int e = 0; e < exceptionCount; ++ e) 727 { 728 final Exception_info exception = exceptions.get (e); 729 leaders.add (exception.m_handler_pc); 730 } 731 732 733 final IntObjectMap branches = new IntObjectMap (); 734 735 // determine block leaders [an O(code length) loop]: 736 737 boolean branch = false; 738 boolean wide = false; 739 740 int instructionCount = 0; 741 instructionMap.put (0, 0); 742 743 for (int ip = 0; ip < codeSize; ) 744 { 745 final int opcode = 0xFF & code [ip]; 746 int size = 0; // will be set to -<real size> for special cases in the switch below 747 748 //if (trace3) m_log.trace3 ("parse", MNEMONICS [opcode]); 749 // "visitor.visit (opcode, wide, ip, null)": 750 751 { // "opcode visit" logic: 752 753 int iv, ov; 754 755 if (branch) 756 { 757 // previous instruction was a branch: this one is a leader 758 leaders.add (ip); 759 branch = false; 760 } 761 762 switch (opcode) 763 { 764 case _ifeq: 765 case _iflt: 766 case _ifle: 767 case _ifne: 768 case _ifgt: 769 case _ifge: 770 case _ifnull: 771 case _ifnonnull: 772 case _if_icmpeq: 773 case _if_icmpne: 774 case _if_icmplt: 775 case _if_icmpgt: 776 case _if_icmple: 777 case _if_icmpge: 778 case _if_acmpeq: 779 case _if_acmpne: 780 { 781 //ov = getI2 (code, ip + 1); 782 int scan = ip + 1; 783 ov = (code [scan] << 8) | (0xFF & code [++ scan]); 784 785 final int target = ip + ov; 786 leaders.add (target); 787 788 branches.put (ip, new IFJUMP2 (opcode, target)); 789 branch = true; 790 } 791 break; 792 793 794 case _goto: 795 case _jsr: 796 { 797 //ov = getI2 (code, ip + 1); 798 int scan = ip + 1; 799 ov = (code [scan] << 8) | (0xFF & code [++ scan]); 800 801 final int target = ip + ov; 802 leaders.add (target); 803 804 branches.put (ip, new JUMP2 (opcode, target)); 805 branch = true; 806 } 807 break; 808 809 810 case _lookupswitch: 811 { 812 int scan = ip + 4 - (ip & 3); // eat padding 813 814 ov = (code [scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]); 815 leaders.add (ip + ov); 816 817 //final int npairs = getU4 (code, scan); 818 //scan += 4; 819 final int npairs = ((0xFF & code [++ scan]) << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]); 820 821 final int [] keys = new int [npairs]; 822 final int [] targets = new int [npairs + 1]; 823 targets [0] = ip + ov; 824 825 for (int p = 0; p < npairs; ++ p) 826 { 827 //iv = getI4 (code, scan); 828 //scan += 4; 829 iv = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]); 830 keys [p] = iv; 831 832 833 //ov = getI4 (code, scan); 834 //scan += 4; 835 ov = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]); 836 targets [p + 1] = ip + ov; 837 leaders.add (ip + ov); 838 } 839 840 branches.put (ip, new LOOKUPSWITCH (keys, targets)); 841 branch = true; 842 843 size = ip - scan - 1; // special case 844 } 845 break; 846 847 848 case _tableswitch: 849 { 850 int scan = ip + 4 - (ip & 3); // eat padding 851 852 ov = (code [scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]); 853 leaders.add (ip + ov); 854 855 //final int low = getI4 (code, scan + 4); 856 final int low = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]); 857 //final int high = getI4 (code, scan + 8); 858 //scan += 12; 859 final int high = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]); 860 861 final int [] targets = new int [high - low + 2]; 862 targets [0] = ip + ov; 863 864 for (int index = low; index <= high; ++ index) 865 { 866 //ov = getI4 (code, scan); 867 ov = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]); 868 targets [index - low + 1] = ip + ov; 869 leaders.add (ip + ov); 870 //scan += 4; 871 } 872 873 branches.put (ip, new TABLESWITCH (low, high, targets)); 874 branch = true; 875 876 size = ip - scan - 1; // special case 877 } 878 break; 879 880 881 case _goto_w: 882 case _jsr_w: 883 { 884 int scan = ip + 1; 885 //ov = getI4 (code, ip + 1); 886 ov = (code [scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]); 887 final int target = ip + ov; 888 889 leaders.add (target); 890 891 branches.put (ip, new JUMP4 (opcode, target)); 892 branch = true; 893 } 894 break; 895 896 897 case _ret: 898 { 899 int scan = ip + 1; 900 iv = wide ? (((0xFF & code [scan]) << 8) | (0xFF & code [++ scan])) : (0xFF & code [scan]); 901 902 branches.put (ip, new RET (opcode, iv)); 903 branch = true; 904 } 905 break; 906 907 908 case _athrow: 909 case _ireturn: 910 case _lreturn: 911 case _freturn: 912 case _dreturn: 913 case _areturn: 914 case _return: 915 { 916 branches.put (ip, new TERMINATE (opcode)); 917 branch = true; 918 } 919 break; 920 921 } // end of switch 922 923 } // end of processing the current opcode 924 925 926 // shift to the next instruction [this is the only block that adjusts 'ip']: 927 928 if (size == 0) 929 size = (wide ? WIDE_SIZE : NARROW_SIZE) [opcode]; 930 else 931 size = -size; 932 933 ip += size; 934 wide = (opcode == _wide); 935 936 instructionMap.put (ip, ++ instructionCount); 937 938 } // end of for 939 940 941 // split 'code' into an ordered list of basic blocks [O(block count) loops]: 942 943 final int blockCount = leaders.size (); 944 if (trace2) m_log.trace2 ("visit", "method contains " + blockCount + " basic blocks"); 945 946 final BlockList blocks = new BlockList (blockCount); 947 948 final int [] _leaders = new int [blockCount + 1]; // room for end-of-code leader at the end 949 leaders.values (_leaders, 0); 950 _leaders [blockCount] = codeSize; 951 952 Arrays.sort (_leaders); 953 954 final int [] _branch_locations = branches.keys (); 955 Arrays.sort (_branch_locations); 956 957 final IntIntMap leaderToBlockID = new IntIntMap (_leaders.length); 958 959 if (m_metadata) 960 { 961 // help construct a MethodDescriptor for the current method: 962 963 m_methodBlockSizes = new int [blockCount]; 964 m_methodBlockOffsets = _leaders; 965 } 966 967 // compute signature even if metadata is not needed (because the instrumented 968 // classdef uses it): 969 consumeSignatureData (m_methodID, _leaders); 970 971 // pass 1: 972 973 final int [] intHolder = new int [1]; 974 int instr_count = 0, prev_instr_count; 975 976 for (int bl = 0, br = 0; bl < blockCount; ++ bl) 977 { 978 final Block block = new Block (); 979 blocks.m_blocks.add (block); 980 981 final int leader = _leaders [bl]; 982 983 block.m_first = leader; // m_first set 984 leaderToBlockID.put (leader, bl); 985 986 final int next_leader = _leaders [bl + 1]; 987 boolean branchDelimited = false; 988 989 prev_instr_count = instr_count; 990 991 if (_branch_locations.length > br) 992 { 993 final int next_branch_location = _branch_locations [br]; 994 if (next_branch_location < next_leader) 995 { 996 branchDelimited = true; 997 998 block.m_length = next_branch_location - leader; // m_length set 999 1000 if ($assert.ENABLED) 1001 $assert.ASSERT (instructionMap.get (next_branch_location, intHolder), "no mapping for " + next_branch_location); 1002 else 1003 instructionMap.get (next_branch_location, intHolder); 1004 1005 instr_count = intHolder [0] + 1; // [+ 1 for the branch] 1006 1007 block.m_branch = (Branch) branches.get (next_branch_location); 1008 block.m_branch.m_parentBlockID = bl; // m_branch set 1009 1010 ++ br; 1011 } 1012 } 1013 1014 if (! branchDelimited) 1015 { 1016 block.m_length = next_leader - leader; // m_length set 1017 1018 if ($assert.ENABLED) 1019 $assert.ASSERT (instructionMap.get (next_leader, intHolder), "no mapping for " + next_leader); 1020 else 1021 instructionMap.get (next_leader, intHolder); 1022 1023 instr_count = intHolder [0]; 1024 } 1025 1026 block.m_instrCount = instr_count - prev_instr_count; // m_instrCount set 1027 1028 if ($assert.ENABLED) $assert.ASSERT (block.m_length == 0 || block.m_instrCount > 0, "invalid instr count for block " + bl + ": " + block.m_instrCount); 1029 if (m_metadata) m_methodBlockSizes [bl] = block.m_instrCount; 1030 } 1031 1032 // pass 2: 1033 1034 final Block [] _blocks = (Block []) blocks.m_blocks.toArray (new Block [blockCount]); 1035 1036 for (int l = 0; l < blockCount; ++ l) 1037 { 1038 final Block block = _blocks [l]; 1039 1040 if (block.m_branch != null) 1041 { 1042 final int [] targets = block.m_branch.m_targets; 1043 if (targets != null) 1044 { 1045 for (int t = 0, targetCount = targets.length; t < targetCount; ++ t) 1046 { 1047 // TODO: HACK ! convert block absolute offsets to block IDs: 1048 1049 if ($assert.ENABLED) 1050 $assert.ASSERT (leaderToBlockID.get (targets [t], intHolder), "no mapping for " + targets [t]); 1051 else 1052 leaderToBlockID.get (targets [t], intHolder); 1053 1054 targets [t] = intHolder [0]; 1055 } 1056 } 1057 } 1058 } 1059 1060 1061 // update block count map [used later by <clinit> visit]: 1062 m_classBlockCounts [m_methodID] = blockCount; 1063 1064 // actual basic block instrumentation: 1065 { 1066 if (trace2) m_log.trace2 ("visit", "instrumenting... "); 1067 1068 // determine the local var index for the var that will alias COVERAGE_FIELD: 1069 final int localVarIndex = attribute.m_max_locals ++; 1070 1071 if (m_methodID == m_clinitID) // note: m_clinitID can be -1 if <clinit> has not been visited yet 1072 { 1073 // add a long stamp constant after all the original methods have been visited: 1074 1075 m_stampIndex = m_cls.getConstants ().add (new CONSTANT_Long_info (m_classSignature)); 1076 1077 blocks.m_header = new clinitHeader (this, localVarIndex); 1078 } 1079 else 1080 blocks.m_header = new methodHeader (this, localVarIndex); 1081 1082 int headerMaxStack = blocks.m_header.maxstack (); 1083 int methodMaxStack = 0; 1084 1085 for (int l = 0; l < blockCount; ++ l) 1086 { 1087 final Block block = _blocks [l]; 1088 1089 final CodeSegment insertion = new BlockSegment (this, localVarIndex, l); 1090 block.m_insertion = insertion; 1091 1092 final int insertionMaxStack = insertion.maxstack (); 1093 if (insertionMaxStack > methodMaxStack) 1094 methodMaxStack = insertionMaxStack; 1095 } 1096 1097 // update maxstack as needed [it can only grow]: 1098 { 1099 final int oldMaxStack = attribute.m_max_stack; 1100 1101 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 1102 1103 if (headerMaxStack > attribute.m_max_stack) 1104 attribute.m_max_stack = headerMaxStack; 1105 1106 if (trace3) m_log.trace3 ("visit", "increasing maxstack by " + (attribute.m_max_stack - oldMaxStack)); 1107 } 1108 1109 if ($assert.ENABLED) $assert.ASSERT (blocks.m_header != null, "header not set"); 1110 } 1111 1112 1113 // assemble all blocks into an instrumented code block: 1114 if (trace2) m_log.trace2 ("visit", "assembling... "); 1115 1116 int newcodeCapacity = codeSize << 1; 1117 if (newcodeCapacity < EMIT_CTX_MIN_INIT_CAPACITY) newcodeCapacity = EMIT_CTX_MIN_INIT_CAPACITY; 1118 1119 final ByteArrayOStream newcode = new ByteArrayOStream (newcodeCapacity); // TODO: empirical capacity 1120 final EmitCtx emitctx = new EmitCtx (blocks, newcode); 1121 1122 // create a jump adjustment map: 1123 final int [] jumpAdjOffsets = new int [blockCount]; // room for initial 0 + (blockCount - 1) 1124 final int [] jumpAdjMap = new int [jumpAdjOffsets.length]; // room for initial 0 + (blockCount - 1) 1125 1126 if ($assert.ENABLED) $assert.ASSERT (jumpAdjOffsets.length == jumpAdjMap.length, 1127 "jumpAdjOffsets and jumpAdjMap length mismatch"); 1128 1129 // header: 1130 blocks.m_header.emit (emitctx); 1131 // jumpAdjOffsets [0] = 0: redundant 1132 jumpAdjMap [0] = emitctx.m_out.size (); 1133 1134 // rest of blocks: 1135 for (int l = 0; l < blockCount; ++ l) 1136 { 1137 final Block block = _blocks [l]; 1138 1139 if (l + 1 < blockCount) 1140 { 1141 jumpAdjOffsets [l + 1] = _blocks [l].m_first + _blocks [l].m_length; // implies the insertion goes just before the branch 1142 } 1143 1144 block.emit (emitctx, code); 1145 1146 // TODO: this breaks if code can shrink: 1147 if (l + 1 < blockCount) 1148 { 1149 jumpAdjMap [l + 1] = emitctx.m_out.size () - _blocks [l + 1].m_first; 1150 } 1151 } 1152 1153 m_methodJumpAdjOffsets = jumpAdjOffsets; 1154 m_methodJumpAdjValues = jumpAdjMap; 1155 1156 if (trace3) 1157 { 1158 final StringBuffer s = new StringBuffer ("jump adjustment map:" + EOL); 1159 for (int a = 0; a < jumpAdjOffsets.length; ++ a) 1160 { 1161 s.append (" " + jumpAdjOffsets [a] + ": +" + jumpAdjMap [a]); 1162 if (a < jumpAdjOffsets.length - 1) s.append (EOL); 1163 } 1164 1165 m_log.trace3 ("visit", s.toString ()); 1166 } 1167 1168 final byte [] _newcode = newcode.getByteArray (); // note: not cloned 1169 final int _newcodeSize = newcode.size (); 1170 1171 // [all blocks have had their m_first adjusted] 1172 1173 // backpatching pass: 1174 if (trace3) m_log.trace3 ("visit", "backpatching " + emitctx.m_backpatchQueue.size () + " ip(s)"); 1175 1176 for (Iterator i = emitctx.m_backpatchQueue.iterator (); i.hasNext (); ) 1177 { 1178 final int [] patchData = (int []) i.next (); 1179 int ip = patchData [1]; 1180 1181 if ($assert.ENABLED) $assert.ASSERT (patchData != null, "null patch data for ip " + ip); 1182 1183 final int jump = _blocks [patchData [3]].m_first - patchData [2]; 1184 if ($assert.ENABLED) $assert.ASSERT (jump > 0, "negative backpatch jump offset " + jump + " for ip " + ip); 1185 1186 switch (patchData [0]) 1187 { 1188 case 4: 1189 { 1190 _newcode [ip ++] = (byte) (jump >>> 24); 1191 _newcode [ip ++] = (byte) (jump >>> 16); 1192 1193 } // *FALL THROUGH* 1194 1195 case 2: 1196 { 1197 _newcode [ip ++] = (byte) (jump >>> 8); 1198 _newcode [ip] = (byte) jump; 1199 } 1200 } 1201 } 1202 1203 attribute.setCode (_newcode, _newcodeSize); 1204 if (trace2) m_log.trace2 ("visit", "method assembled into " + _newcodeSize + " code bytes"); 1205 1206 1207 // adjust bytecode offsets in the exception table: 1208 final IExceptionHandlerTable exceptionTable = attribute.getExceptionTable (); 1209 for (int e = 0; e < exceptionTable.size (); ++ e) 1210 { 1211 final Exception_info exception = exceptionTable.get (e); 1212 1213 int adjSegment = lowbound (jumpAdjOffsets, exception.m_start_pc); 1214 exception.m_start_pc += jumpAdjMap [adjSegment]; 1215 1216 adjSegment = lowbound (jumpAdjOffsets, exception.m_end_pc); 1217 exception.m_end_pc += jumpAdjMap [adjSegment]; 1218 1219 adjSegment = lowbound (jumpAdjOffsets, exception.m_handler_pc); 1220 exception.m_handler_pc += jumpAdjMap [adjSegment]; 1221 } 1222 1223 1224 // visit other nested attributes [LineNumberAttribute, etc]: 1225 final IAttributeCollection attributes = attribute.getAttributes (); 1226 final int attributeCount = attributes.size (); 1227 for (int a = 0; a < attributeCount; ++ a) 1228 { 1229 final Attribute_info nested = attributes.get (a); 1230 nested.accept (this, ctx); 1231 } 1232 1233 return ctx; 1234 } 1235 1236 1237 public Object visit (final LineNumberTableAttribute_info attribute, final Object ctx) 1238 { 1239 final boolean trace2 = m_log.atTRACE2 (); 1240 final boolean trace3 = m_log.atTRACE3 (); 1241 if (trace2) m_log.trace2 ("visit", "attribute: [" + attribute.getName (m_cls) + "]"); 1242 1243 final int lineCount = attribute.size (); 1244 1245 if (m_metadata) 1246 { 1247 if (trace2) m_log.trace2 ("visit", "processing line number table for metadata..."); 1248 1249 final int blockCount = m_classBlockCounts [m_methodID]; 1250 if ($assert.ENABLED) $assert.ASSERT (blockCount > 0, "invalid method block count for method " + m_methodID); 1251 1252 final int [][] blockLineMap = new int [blockCount][]; 1253 1254 if ($assert.ENABLED) $assert.ASSERT (blockCount + 1 == m_methodBlockOffsets.length, 1255 "invalid m_methodBlockOffsets"); 1256 1257 if (lineCount == 0) 1258 { 1259 for (int bl = 0; bl < blockCount; ++ bl) 1260 blockLineMap [bl] = EMPTY_INT_ARRAY; 1261 } 1262 else 1263 { 1264 // TODO: this code does not work if there are multiple LineNumberTableAttribute attributes for the method 1265 1266 final LineNumber_info [] sortedLines = new LineNumber_info [attribute.size ()]; 1267 1268 for (int l = 0; l < lineCount; ++ l) 1269 { 1270 final LineNumber_info line = attribute.get (l); 1271 sortedLines [l] = line; 1272 } 1273 1274 Arrays.sort (sortedLines, LINE_NUMBER_COMPARATOR); 1275 1276 // construct block->line mapping: TODO: is the loop below the fastest it can be done? 1277 1278 final int [] methodBlockOffsets = m_methodBlockOffsets; 1279 1280 LineNumber_info line = sortedLines [0]; // never null 1281 LineNumber_info prev_line = null; 1282 1283 // remember the first line: 1284 m_methodFirstLine = line.m_line_number; 1285 1286 for (int bl = 0, l = 0; bl < blockCount; ++ bl) 1287 { 1288 final IntSet blockLines = new IntSet (); 1289 1290 if ((prev_line != null) && (line.m_start_pc > methodBlockOffsets [bl])) 1291 { 1292 blockLines.add (prev_line.m_line_number); 1293 } 1294 1295 while (line.m_start_pc < methodBlockOffsets [bl + 1]) 1296 { 1297 blockLines.add (line.m_line_number); 1298 1299 if (l == lineCount - 1) 1300 break; 1301 else 1302 { 1303 prev_line = line; 1304 line = sortedLines [++ l]; // advance to the next line 1305 } 1306 } 1307 1308 blockLineMap [bl] = blockLines.values (); 1309 } 1310 } 1311 1312 m_classBlockMetadata [m_methodID] = blockLineMap; 1313 1314 if (trace3) 1315 { 1316 StringBuffer s = new StringBuffer ("block-line map for method #" + m_methodID + ":"); 1317 for (int bl = 0; bl < blockCount; ++ bl) 1318 { 1319 s.append (EOL); 1320 s.append (" block " + bl + ": "); 1321 1322 final int [] lines = blockLineMap [bl]; 1323 for (int l = 0; l < lines.length; ++ l) 1324 { 1325 if (l != 0) s.append (", "); 1326 s.append (lines [l]); 1327 } 1328 } 1329 1330 m_log.trace3 ("visit", s.toString ()); 1331 } 1332 } 1333 1334 for (int l = 0; l < lineCount; ++ l) 1335 { 1336 final LineNumber_info line = attribute.get (l); 1337 1338 // TODO: make this faster using either table assist or the sorted array in 'sortedLines' 1339 1340 // adjust bytecode offset for line number mapping: 1341 int adjSegment = lowbound (m_methodJumpAdjOffsets, line.m_start_pc); 1342 line.m_start_pc += m_methodJumpAdjValues [adjSegment]; 1343 } 1344 1345 return ctx; 1346 } 1347 1348 // TODO: line var table as well 1349 1350 1351 // no-op visits: 1352 1353 public Object visit (final ExceptionsAttribute_info attribute, final Object ctx) 1354 { 1355 return ctx; 1356 } 1357 1358 public Object visit (final ConstantValueAttribute_info attribute, final Object ctx) 1359 { 1360 return ctx; 1361 } 1362 1363 public Object visit (final SourceFileAttribute_info attribute, final Object ctx) 1364 { 1365 m_classSrcFileName = attribute.getSourceFile (m_cls).m_value; 1366 1367 return ctx; 1368 } 1369 1370 public Object visit (final SyntheticAttribute_info attribute, final Object ctx) 1371 { 1372 return ctx; 1373 } 1374 1375 public Object visit (final BridgeAttribute_info attribute, final Object ctx) 1376 { 1377 return ctx; 1378 } 1379 1380 public Object visit (final InnerClassesAttribute_info attribute, final Object ctx) 1381 { 1382 return ctx; 1383 } 1384 1385 public Object visit (final GenericAttribute_info attribute, final Object ctx) 1386 { 1387 return ctx; 1388 } 1389 1390 // protected: ............................................................. 1391 1392 // package: ............................................................... 1393 1394 // private: ............................................................... 1395 1396 1397 private static final class BlockList 1398 { 1399 BlockList () 1400 { 1401 m_blocks = new ArrayList (); 1402 } 1403 1404 BlockList (final int capacity) 1405 { 1406 m_blocks = new ArrayList (capacity); 1407 } 1408 1409 final List /* Block */ m_blocks; // TODO: might as well use an array here? 1410 CodeSegment m_header; 1411 1412 } // end of nested class 1413 1414 1415 private static final class Block 1416 { 1417 int m_first; // inclusive offset of the leader instruction [first instr in the block] 1418 //int m_last; // exclusive offset of the last non-branch instruction [excludes possible control transfer at the end] 1419 int m_length; // excluding the branch statement [can be 0] 1420 int m_instrCount; // size in instructions, including the [optional] original branch; [m_insertion is not counted] 1421 1422 // NOTE: it is possible that m_first == m_last [the block is empty except for a possible control transfer instr] 1423 1424// public int maxlength () 1425// { 1426// // TODO: cache 1427// return m_length 1428//// + (m_insertion != null ? m_insertion.maxlength () : 0) 1429// + (m_branch != null ? m_branch.maxlength () : 0); 1430// } 1431 1432 /** 1433 * When this is called, all previous blocks have been written out and 1434 * their m_first have been updated. 1435 */ 1436 void emit (final EmitCtx ctx, final byte [] code) // TODO: move 'code' into 'ctx' 1437 { 1438 final ByteArrayOStream out = ctx.m_out; 1439 final int first = m_first; 1440 1441 m_first = out.size (); // update position to be within new code array 1442 1443 for (int i = 0, length = m_length; i < length; ++ i) 1444 { 1445 out.write (code [first + i]); 1446 } 1447 1448 if (m_insertion != null) 1449 m_insertion.emit (ctx); 1450 1451 if (m_branch != null) 1452 m_branch.emit (ctx); 1453 } 1454 1455 public CodeSegment m_insertion; 1456 public Branch m_branch; // falling through is implied by this being null 1457 1458 } // end of nested class 1459 1460 1461 static final class EmitCtx 1462 { 1463 // TODO: profile to check that ByteArrayOStream.write() is not the bottleneck 1464 1465 EmitCtx (final BlockList blocks, final ByteArrayOStream out) 1466 { 1467 m_blocks = blocks; 1468 m_out = out; 1469 1470 m_backpatchQueue = new ArrayList (); 1471 } 1472 1473 final BlockList m_blocks; 1474 final ByteArrayOStream m_out; 1475 final List /* int[4] */ m_backpatchQueue; 1476 1477 } // end of nested class 1478 1479 1480 /** 1481 * A Branch does not add any maxlocals/maxstack requirements. 1482 */ 1483 static abstract class Branch 1484 { 1485 protected Branch (final int opcode, final int [] targets) 1486 { 1487 m_opcode = (byte) opcode; 1488 m_targets = targets; 1489 } 1490 1491 /* 1492 * Called when targets are block IDs, before emitting. 1493 */ 1494 int maxlength () { return 1; } 1495 1496 abstract void emit (EmitCtx ctx); 1497 1498 // TODO: this method must signal when it is necessary to switch to long jump form 1499 protected final void emitJumpOffset2 (final EmitCtx ctx, final int ip, final int targetBlockID) 1500 { 1501 final ByteArrayOStream out = ctx.m_out; 1502 1503 if (targetBlockID <= m_parentBlockID) 1504 { 1505 // backwards branch: 1506 final int jumpOffset = ((Block) ctx.m_blocks.m_blocks.get (targetBlockID)).m_first - ip; 1507 1508 out.write2 (jumpOffset >>> 8, // targetbyte1 1509 jumpOffset); // targetbyte2 1510 } 1511 else 1512 { 1513 final int jumpOffsetLocation = out.size (); 1514 1515 // else write out zeros and submit for backpatching: 1516 out.write2 (0, 1517 0); 1518 1519 ctx.m_backpatchQueue.add (new int [] {2, jumpOffsetLocation, ip, targetBlockID}); 1520 } 1521 } 1522 1523 protected final void emitJumpOffset4 (final EmitCtx ctx, final int ip, final int targetBlockID) 1524 { 1525 final ByteArrayOStream out = ctx.m_out; 1526 1527 if (targetBlockID <= m_parentBlockID) 1528 { 1529 // backwards branch: 1530 final int jumpOffset = ((Block) ctx.m_blocks.m_blocks.get (targetBlockID)).m_first - ip; 1531 1532 out.write4 (jumpOffset >>> 24, // targetbyte1 1533 jumpOffset >>> 16, // targetbyte2 1534 jumpOffset >>> 8, // targetbyte3 1535 jumpOffset); // targetbyte4 1536 } 1537 else 1538 { 1539 final int jumpOffsetLocation = out.size (); 1540 1541 // else write out zeros and submit for backpatching: 1542 out.write4 (0, 1543 0, 1544 0, 1545 0); 1546 1547 ctx.m_backpatchQueue.add (new int [] {4, jumpOffsetLocation, ip, targetBlockID}); 1548 } 1549 } 1550 1551 final byte m_opcode; 1552 final int [] m_targets; // could be code offsets or block IDs 1553 1554 int m_parentBlockID; 1555 1556 } // end of nested class 1557 1558 1559 // TODO: these could be static instance-pooled 1560 static final class TERMINATE extends Branch // _[x]return, _athrow 1561 { 1562 TERMINATE (final int opcode) 1563 { 1564 super (opcode, null); 1565 } 1566 1567 int length () { return 1; } 1568 1569 void emit (final EmitCtx ctx) 1570 { 1571 ctx.m_out.write (m_opcode); 1572 } 1573 1574 } // end of nested class 1575 1576 1577 static final class RET extends Branch // [wide] ret 1578 { 1579 RET (final int opcode, final int varindex) 1580 { 1581 super (opcode, null); 1582 m_varindex = varindex; 1583 } 1584 1585 int length () { return (m_varindex <= 0xFF) ? 2 : 3; } 1586 1587 void emit (final EmitCtx ctx) 1588 { 1589 final ByteArrayOStream out = ctx.m_out; 1590 1591 if (m_varindex <= 0xFF) 1592 { 1593 out.write2 (m_opcode, 1594 m_varindex); // indexbyte 1595 } 1596 else 1597 { 1598 out.write4 (_wide, 1599 m_opcode, 1600 m_varindex >>> 8, // indexbyte1 1601 m_varindex); // indexbyte2 1602 } 1603 } 1604 1605 final int m_varindex; 1606 1607 } // end of nested class 1608 1609 1610 static final class JUMP2 extends Branch // _goto, _jsr 1611 { 1612 JUMP2 (final int opcode, final int target) 1613 { 1614 super (opcode, new int [] {target}); 1615 } 1616 1617 int maxlength () { return 5; } 1618 1619 void emit (final EmitCtx ctx) 1620 { 1621 final ByteArrayOStream out = ctx.m_out; 1622 final int targetBlockID = m_targets [0]; 1623 final int ip = out.size (); 1624 1625 // TODO: switch to 4-byte long form if jump > 32k 1626 1627 out.write (m_opcode); 1628 emitJumpOffset2 (ctx, ip, targetBlockID); 1629 } 1630 1631 } // end of nested class 1632 1633 1634 static final class JUMP4 extends Branch // _goto_w, _jsr_w 1635 { 1636 JUMP4 (final int opcode, final int target) 1637 { 1638 super (opcode, new int [] {target}); 1639 } 1640 1641 int maxlength () { return 5; } 1642 1643 void emit (final EmitCtx ctx) 1644 { 1645 final ByteArrayOStream out = ctx.m_out; 1646 final int targetBlockID = m_targets [0]; 1647 final int ip = out.size (); 1648 1649 out.write (m_opcode); 1650 emitJumpOffset4 (ctx, ip, targetBlockID); 1651 } 1652 1653 } // end of nested class 1654 1655 1656 static final class IFJUMP2 extends Branch // _ifxxx 1657 { 1658 IFJUMP2 (final int opcode, final int target) 1659 { 1660 super (opcode, new int [] {target}); 1661 } 1662 1663 int maxlength () { return 8; } 1664 1665 void emit (final EmitCtx ctx) 1666 { 1667 final ByteArrayOStream out = ctx.m_out; 1668 final int targetBlockID = m_targets [0]; 1669 final int ip = out.size (); 1670 1671 // TODO: switch to 8-byte long form if jump > 32k 1672 1673 out.write (m_opcode); 1674 emitJumpOffset2 (ctx, ip, targetBlockID); 1675 } 1676 1677 } // end of nested class 1678 1679 1680 static final class LOOKUPSWITCH extends Branch 1681 { 1682 LOOKUPSWITCH (final int [] keys, final int [] targets /* first one is default */) 1683 { 1684 super (_lookupswitch, targets); 1685 m_keys = keys; 1686 } 1687 1688 int maxlength () { return 12 + (m_keys.length << 3); } 1689 1690 void emit (final EmitCtx ctx) 1691 { 1692 final ByteArrayOStream out = ctx.m_out; 1693 final int ip = out.size (); 1694 1695 out.write (m_opcode); 1696 1697 // padding bytes: 1698 for (int p = 0, padCount = 3 - (ip & 3); p < padCount; ++ p) out.write (0); 1699 1700 // default: 1701 emitJumpOffset4 (ctx, ip, m_targets [0]); 1702 1703 // npairs count: 1704 final int npairs = m_keys.length; 1705 out.write4 (npairs >>> 24, // byte1 1706 npairs >>> 16, // byte2 1707 npairs >>> 8, // byte3 1708 npairs); // byte4 1709 1710 // keyed targets: 1711 for (int t = 1; t < m_targets.length; ++ t) 1712 { 1713 final int key = m_keys [t - 1]; 1714 out.write4 (key >>> 24, // byte1 1715 key >>> 16, // byte2 1716 key >>> 8, // byte3 1717 key); // byte4 1718 1719 // key target: 1720 emitJumpOffset4 (ctx, ip, m_targets [t]); 1721 } 1722 } 1723 1724 final int [] m_keys; 1725 1726 } // end of nested class 1727 1728 1729 static final class TABLESWITCH extends Branch 1730 { 1731 TABLESWITCH (final int low, final int high, final int [] targets /* first one is default */) 1732 { 1733 super (_tableswitch, targets); 1734 m_low = low; 1735 m_high = high; 1736 } 1737 1738 int maxlength () { return 12 + (m_targets.length << 2); } 1739 1740 void emit (final EmitCtx ctx) 1741 { 1742 final ByteArrayOStream out = ctx.m_out; 1743 final int ip = out.size (); 1744 1745 // TODO: switch to long form for any jump > 32k 1746 1747 out.write (m_opcode); 1748 1749 // padding bytes: 1750 for (int p = 0, padCount = 3 - (ip & 3); p < padCount; ++ p) out.write (0); 1751 1752 // default: 1753 emitJumpOffset4 (ctx, ip, m_targets [0]); 1754 1755 // low, high: 1756 final int low = m_low; 1757 out.write4 (low >>> 24, // byte1 1758 low >>> 16, // byte2 1759 low >>> 8, // byte3 1760 low); // byte4 1761 1762 final int high = m_high; 1763 out.write4 (high >>> 24, // byte1 1764 high >>> 16, // byte2 1765 high >>> 8, // byte3 1766 high); // byte4 1767 1768 // targets: 1769 for (int t = 1; t < m_targets.length; ++ t) 1770 { 1771 // key target: 1772 emitJumpOffset4 (ctx, ip, m_targets [t]); 1773 } 1774 } 1775 1776 final int m_low, m_high; 1777 1778 } // end of nested class 1779 1780 1781 /** 1782 * TODO: CodeSegment right now must be 100% position-independent code; 1783 * otherwise it must follow maxlengtt() Branch pattern... 1784 */ 1785 static abstract class CodeSegment 1786 { 1787 CodeSegment (final InstrVisitor visitor) 1788 { 1789 m_visitor = visitor; // TODO: will this field be used? 1790 } 1791 1792 abstract int length (); 1793 abstract int maxstack (); 1794 abstract void emit (EmitCtx ctx); 1795 1796 1797 final InstrVisitor m_visitor; 1798 1799 } // end of nested class 1800 1801 1802 static final class clinitHeader extends CodeSegment 1803 { 1804 clinitHeader (final InstrVisitor visitor, final int localVarIndex) 1805 { 1806 super (visitor); 1807 final ByteArrayOStream buf = new ByteArrayOStream (CLINIT_HEADER_INIT_CAPACITY); 1808 m_buf = buf; 1809 1810 final ClassDef cls = visitor.m_cls; 1811 1812 final int [] blockCounts = visitor.m_classBlockCounts; 1813 final int instrMethodCount = visitor.m_classInstrMethodCount; // actual number of methods to instrument may be less than the size of the block map 1814 if ($assert.ENABLED) $assert.ASSERT (blockCounts != null && blockCounts.length >= instrMethodCount, 1815 "invalid block count map"); 1816 1817 final int coverageFieldrefIndex = visitor.m_coverageFieldrefIndex; 1818 final int preclinitMethodrefIndex = visitor.m_preclinitMethodrefIndex; 1819 final int classNameConstantIndex = visitor.m_classNameConstantIndex; 1820 1821 if ($assert.ENABLED) 1822 { 1823 $assert.ASSERT (coverageFieldrefIndex > 0, "invalid coverageFieldrefIndex"); 1824 $assert.ASSERT (preclinitMethodrefIndex > 0, "invalid registerMethodrefIndex"); 1825 $assert.ASSERT (classNameConstantIndex > 0, "invalid classNameConstantIndex"); 1826 } 1827 1828 // init and load COVERAGE_FIELD: 1829 buf.write3 (_invokestatic, 1830 preclinitMethodrefIndex >>> 8, // indexbyte1 1831 preclinitMethodrefIndex); // indexbyte2 1832 1833 // [stack +1] 1834 1835 // TODO: disable this when there are no real blocks following? 1836 // [in general, use a different template when this method contains a single block] 1837 1838 // TODO: if this method has been added by us, do not instrument its blocks 1839 1840 // push int literal equal to 'methodID' [for the parent method]: 1841 CodeGen.push_int_value (buf, cls, visitor.m_methodID); 1842 1843 // [stack +2] 1844 1845 // push subarray reference: 1846 buf.write (_aaload); 1847 1848 // [stack +1] 1849 1850 // store it in alias var: 1851 CodeGen.store_local_object_var (buf, localVarIndex); 1852 1853 // [stack +0] 1854 } 1855 1856 int length () { return m_buf.size (); } 1857 int maxstack () { return 2; } // note: needs to be updated each time emitted code changes 1858 1859 void emit (final EmitCtx ctx) 1860 { 1861 // TODO: better error handling here? 1862 try 1863 { 1864 m_buf.writeTo (ctx.m_out); 1865 } 1866 catch (IOException ioe) 1867 { 1868 if ($assert.ENABLED) $assert.ASSERT (false, ioe.toString ()); 1869 } 1870 } 1871 1872 1873 private final ByteArrayOStream m_buf; 1874 1875 private static final int CLINIT_HEADER_INIT_CAPACITY = 32; // covers about 80% of classes (no reallocation) 1876 1877 } // end of nested class 1878 1879 1880 static final class methodHeader extends CodeSegment 1881 { 1882 methodHeader (final InstrVisitor visitor, final int localVarIndex) 1883 { 1884 super (visitor); 1885 final ByteArrayOStream buf = new ByteArrayOStream (HEADER_INIT_CAPACITY); 1886 m_buf = buf; 1887 1888 final ClassDef cls = visitor.m_cls; 1889 final int coverageFieldrefIndex = visitor.m_coverageFieldrefIndex; 1890 final int preclinitMethodrefIndex = visitor.m_preclinitMethodrefIndex; 1891 1892 // TODO: disable this when there are no real blocks following? 1893 // [in general, use a different template when this method contains a single block] 1894 1895 // push ref to the static field and dup it: 1896 buf.write4 (_getstatic, 1897 coverageFieldrefIndex >>> 8, // indexbyte1 1898 coverageFieldrefIndex, // indexbyte2 1899 _dup); 1900 1901 // [stack +2] 1902 1903 // SF FR 971186: check if it is null and if so run the field 1904 // init and class RT register code (only relevant for 1905 // methods that can be executed ahead of <clinit>) [rare] 1906 1907 buf.write3 (_ifnonnull, // skip over pre-<clinit> method call 1908 0, 1909 3 + /* size of the block below */ 4); 1910 1911 // [stack +1] 1912 1913 // block: call pre-<clinit> method 1914 { 1915 buf.write4 (_pop, 1916 _invokestatic, 1917 preclinitMethodrefIndex >>> 8, // indexbyte1 1918 preclinitMethodrefIndex); // indexbyte2 1919 1920 // [stack +1] 1921 } 1922 1923 // push int literal equal to 'methodID': 1924 CodeGen.push_int_value (buf, cls, visitor.m_methodID); 1925 1926 // [stack +2] 1927 1928 // push subarray reference: 1929 buf.write (_aaload); 1930 1931 // [stack +1] 1932 1933 // store it in alias var: 1934 CodeGen.store_local_object_var (buf, localVarIndex); 1935 1936 // [stack +0] 1937 } 1938 1939 int length () { return m_buf.size (); } 1940 int maxstack () { return 2; } // note: needs to be updated each time emitted code changes 1941 1942 void emit (final EmitCtx ctx) 1943 { 1944 // TODO: better error handling here? 1945 try 1946 { 1947 m_buf.writeTo (ctx.m_out); 1948 } 1949 catch (IOException ioe) 1950 { 1951 if ($assert.ENABLED) $assert.ASSERT (false, ioe.toString ()); 1952 } 1953 } 1954 1955 1956 private final ByteArrayOStream m_buf; 1957 1958 private static final int HEADER_INIT_CAPACITY = 16; 1959 1960 } // end of nested class 1961 1962 1963 static final class BlockSegment extends CodeSegment 1964 { 1965 public BlockSegment (final InstrVisitor visitor, final int localVarIndex, final int blockID) 1966 { 1967 super (visitor); 1968 final ByteArrayOStream buf = new ByteArrayOStream (BLOCK_INIT_CAPACITY); 1969 m_buf = buf; 1970 1971 final ClassDef cls = visitor.m_cls; 1972 1973 // push alias var: 1974 CodeGen.load_local_object_var (buf, localVarIndex); 1975 1976 // [stack +1] 1977 1978 // push int value equal to 'blockID': 1979 CodeGen.push_int_value (buf, cls, blockID); 1980 1981 // [stack +2] 1982 1983 // push boolean 'true': 1984 buf.write2 (_iconst_1, 1985 1986 // [stack +3] 1987 1988 // store it in the array: 1989 _bastore); 1990 1991 // [stack +0] 1992 } 1993 1994 int length () { return m_buf.size (); } 1995 int maxstack () { return 3; } // note: needs to be updated each time emitted code changes 1996 1997 void emit (final EmitCtx ctx) 1998 { 1999 // TODO: better error handling here? 2000 try 2001 { 2002 m_buf.writeTo (ctx.m_out); 2003 } 2004 catch (IOException ioe) 2005 { 2006 if ($assert.ENABLED) $assert.ASSERT (false, ioe.toString ()); 2007 } 2008 } 2009 2010 2011 private final ByteArrayOStream m_buf; 2012 2013 private static final int BLOCK_INIT_CAPACITY = 16; 2014 2015 } // end of nested class 2016 2017 2018 private static final class LineNumberComparator implements Comparator 2019 { 2020 public final int compare (final Object o1, final Object o2) 2021 { 2022 return ((LineNumber_info) o1).m_start_pc - ((LineNumber_info) o2).m_start_pc; 2023 } 2024 2025 } // end of nested class 2026 2027 2028 2029 private void setClassName (final String fullName) 2030 { 2031 if ($assert.ENABLED) $assert.ASSERT (fullName != null && fullName.length () > 0, 2032 "null or empty input: fullName"); 2033 2034 final int lastSlash = fullName.lastIndexOf ('/'); 2035 if (lastSlash < 0) 2036 { 2037 m_classPackageName = ""; 2038 m_className = fullName; 2039 } 2040 else 2041 { 2042 if ($assert.ENABLED) $assert.ASSERT (lastSlash < fullName.length () - 1, 2043 "malformed class name [" + fullName + "]"); 2044 2045 m_classPackageName = fullName.substring (0, lastSlash); 2046 m_className = fullName.substring (lastSlash + 1); 2047 } 2048 } 2049 2050 private void consumeSignatureData (final int methodID, final int [] basicBlockOffsets) 2051 { 2052 // note: by itself, this is not a very good checksum for a class def; 2053 // however, it is fast to compute and since it will be used along with 2054 // a class name it should be good at detecting structural changes that 2055 // matter to us (method and basic block ordering/sizes) 2056 2057 final int temp1 = basicBlockOffsets.length; 2058 long temp2 = NBEAST * m_classSignature + (methodID + 1) * temp1; 2059 2060 for (int i = 1; i < temp1; ++ i) // skip the initial 0 offset 2061 { 2062 temp2 = NBEAST * temp2 + basicBlockOffsets [i]; 2063 } 2064 2065 m_classSignature = temp2; 2066 } 2067 2068 // TODO: use a compilation flag to use table assist here instead of binary search 2069 // BETTER YET: use binsearch for online mode and table assist for offline [when memory is not an issue] 2070 2071 /** 2072 * Returns the maximum index 'i' such that (values[i] <= x). values[] 2073 * contains distinct non-negative integers in increasing order. values[0] is 0, 2074 * 'x' is non-negative. 2075 * 2076 * Edge case: 2077 * returns values.length-1 if values [values.length - 1] < x 2078 */ 2079 private static int lowbound (final int [] values, final int x) 2080 { 2081 int low = 0, high = values.length - 1; 2082 2083 // assertion: lb is in [low, high] 2084 2085 while (low <= high) 2086 { 2087 final int m = (low + high) >> 1; 2088 final int v = values [m]; 2089 2090 if (v == x) 2091 return m; 2092 else if (v < x) 2093 low = m + 1; 2094 else // v > x 2095 high = m - 1; 2096 } 2097 2098 return high; 2099 } 2100 2101 private void reset () 2102 { 2103 // TODO: check that all state is reset 2104 2105 m_instrument = false; 2106 m_metadata = false; 2107 m_ignoreAlreadyInstrumented = false; 2108 2109 m_cls = null; 2110 m_classPackageName = null; 2111 m_className = null; 2112 m_classSrcFileName = null; 2113 m_classBlockMetadata = null; 2114 m_classMethodDescriptors = null; 2115 2116 m_syntheticStringIndex = -1; 2117 m_coverageFieldrefIndex = -1; 2118 m_registerMethodrefIndex = -1; 2119 m_preclinitMethodrefIndex = -1; 2120 m_classNameConstantIndex = -1; 2121 m_clinitID = -1; 2122 m_clinitStatus = 0; 2123 m_classInstrMethodCount = -1; 2124 m_classBlockCounts = null; 2125 m_classSignature = 0; 2126 2127 m_methodID = -1; 2128 m_methodName = null; 2129 m_methodFirstLine = 0; 2130 m_methodBlockOffsets = null; 2131 m_methodJumpAdjOffsets = null; 2132 m_methodJumpAdjValues = null; 2133 } 2134 2135 2136 private final boolean m_excludeSyntheticMethods; 2137 private final boolean m_excludeBridgeMethods; 2138 private final boolean m_doSUIDCompensation; 2139 2140 private final Logger m_log; // instr visitor logging context is latched at construction time 2141 2142 // non-resettable state: 2143 2144 private boolean m_warningIssued; 2145 2146 2147 // resettable state: 2148 2149 private boolean m_instrument; 2150 private boolean m_metadata; 2151 private boolean m_ignoreAlreadyInstrumented; 2152 2153 /*private*/ ClassDef m_cls; 2154 private String m_classPackageName; // in JVM format [com/vladium/...]; empty string for default package 2155 private String m_className; // in JVM format [<init>, <clinit>, etc], relative to 'm_classPackageName' 2156 private String m_classSrcFileName; 2157 private int [][][] m_classBlockMetadata; // methodID->(blockID->line) map [valid only if 'm_constructMetadata' is true; null if the method has not line number table] 2158 private MethodDescriptor [] m_classMethodDescriptors; 2159 2160 // current class scope: 2161 private int m_syntheticStringIndex; // index of CONSTANT_Utf8 String that reads "Synthetic" 2162 /*private*/ int m_coverageFieldrefIndex; // index of the Fieldref for COVERAGE_FIELD 2163 private int m_registerMethodrefIndex; // index of Methodref for RT.r() 2164 /*private*/ int m_preclinitMethodrefIndex; // index of Methodref for pre-<clinit> method 2165 /*private*/ int m_classNameConstantIndex; // index of CONSTANT_String that is the class name [in JVM format] 2166 private int m_stampIndex; // index of CONSTANT_Long that is the class instr stamp 2167 private int m_clinitID; // offset of <clinit> method [-1 if not determined yet] 2168 private int m_clinitStatus; 2169 /*private*/ int m_classInstrMethodCount; // the number of slots in 'm_classBlockCounts' corresponding to methods to be instrumented for coverage 2170 /*private*/ int [] m_classBlockCounts; // basic block counts for all methods [only valid just before <clinit> is processed] 2171 private long m_classSignature; 2172 2173 // current method scope: 2174 /*private*/ int m_methodID; // offset of current method being instrumented 2175 private String m_methodName; 2176 private int m_methodFirstLine; 2177 private int [] m_methodBlockOffsets; // [unadjusted] basic block boundaries [length = m_classBlockCounts[m_methodID]+1; the last slot is method bytecode length] 2178 private int [] m_methodBlockSizes; 2179 private int [] m_methodJumpAdjOffsets; // TODO: length ? 2180 private int [] m_methodJumpAdjValues; // TODO: length ? 2181 2182 2183 private static final long NBEAST = 16661; // prime 2184 2185 private static final String COVERAGE_FIELD_NAME = "$VR" + "c"; 2186 private static final String SUID_FIELD_NAME = "serialVersionUID"; 2187 private static final String PRECLINIT_METHOD_NAME = "$VR" + "i"; 2188 2189 private static final String JAVA_IO_SERIALIZABLE_NAME = "java/io/Serializable"; 2190 private static final String JAVA_IO_EXTERNALIZABLE_NAME = "java/io/Externalizable"; 2191 2192 private static final int EMIT_CTX_MIN_INIT_CAPACITY = 64; // good value determined empirically 2193 private static final int PRECLINIT_INIT_CAPACITY = 128; // covers about 80% of classes (no reallocation) 2194 private static final boolean MARK_ADDED_ELEMENTS_SYNTHETIC = true; 2195 2196 /* It appears that nested classes and interfaces ought to be marked 2197 * as Synthetic; however, neither Sun nor IBM compilers seem to do this. 2198 * 2199 * (As a side note, implied no-arg constructors ought to be marked as 2200 * synthetic as well, but Sun's javac is not consistent about that either) 2201 */ 2202 private static final boolean SKIP_SYNTHETIC_CLASSES = false; 2203 2204 private static final LineNumberComparator LINE_NUMBER_COMPARATOR = new LineNumberComparator (); 2205 2206 private static final byte [] EMPTY_BYTE_ARRAY = new byte [0]; 2207 2208} // end of class 2209// ----------------------------------------------------------------------------