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 */ 9 package com.vladium.emma.instr; 10 11 import java.io.IOException; 12 import java.util.ArrayList; 13 import java.util.Arrays; 14 import java.util.Comparator; 15 import java.util.Iterator; 16 import java.util.List; 17 18 import com.vladium.jcd.cls.*; 19 import com.vladium.jcd.cls.attribute.*; 20 import com.vladium.jcd.cls.constant.CONSTANT_Class_info; 21 import com.vladium.jcd.cls.constant.CONSTANT_Long_info; 22 import com.vladium.jcd.cls.constant.CONSTANT_Methodref_info; 23 import com.vladium.jcd.cls.constant.CONSTANT_String_info; 24 import com.vladium.jcd.compiler.CodeGen; 25 import com.vladium.jcd.lib.Types; 26 import com.vladium.jcd.opcodes.IOpcodes; 27 import com.vladium.logging.Logger; 28 import com.vladium.util.ByteArrayOStream; 29 import com.vladium.util.IConstants; 30 import com.vladium.util.IntIntMap; 31 import com.vladium.util.IntObjectMap; 32 import com.vladium.util.IntSet; 33 import com.vladium.util.asserts.$assert; 34 import com.vladium.emma.IAppConstants; 35 import com.vladium.emma.data.ClassDescriptor; 36 import com.vladium.emma.data.CoverageOptions; 37 import com.vladium.emma.data.IMetadataConstants; 38 import com.vladium.emma.data.MethodDescriptor; 39 40 // ---------------------------------------------------------------------------- 41 /** 42 * @author Vlad Roubtsov, (C) 2003 43 */ 44 public 45 final 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 InstrVisitor(final CoverageOptions options)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 */ process(final ClassDef cls, final boolean ignoreAlreadyInstrumented, final boolean instrument, final boolean metadata, final InstrResult out)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 visit(final ClassDef ignore, final Object ctx)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 visit(final IMethodCollection methods, final Object ctx)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 visit(final IAttributeCollection attributes, Object ctx)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 visit(final CodeAttribute_info attribute, final Object ctx)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 visit(final LineNumberTableAttribute_info attribute, final Object ctx)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 visit(final ExceptionsAttribute_info attribute, final Object ctx)1353 public Object visit (final ExceptionsAttribute_info attribute, final Object ctx) 1354 { 1355 return ctx; 1356 } 1357 visit(final ConstantValueAttribute_info attribute, final Object ctx)1358 public Object visit (final ConstantValueAttribute_info attribute, final Object ctx) 1359 { 1360 return ctx; 1361 } 1362 visit(final SourceFileAttribute_info attribute, final Object ctx)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 visit(final SyntheticAttribute_info attribute, final Object ctx)1370 public Object visit (final SyntheticAttribute_info attribute, final Object ctx) 1371 { 1372 return ctx; 1373 } 1374 visit(final BridgeAttribute_info attribute, final Object ctx)1375 public Object visit (final BridgeAttribute_info attribute, final Object ctx) 1376 { 1377 return ctx; 1378 } 1379 visit(final InnerClassesAttribute_info attribute, final Object ctx)1380 public Object visit (final InnerClassesAttribute_info attribute, final Object ctx) 1381 { 1382 return ctx; 1383 } 1384 visit(final GenericAttribute_info attribute, final Object ctx)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 { BlockList()1399 BlockList () 1400 { 1401 m_blocks = new ArrayList (); 1402 } 1403 BlockList(final int capacity)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 */ emit(final EmitCtx ctx, final byte [] code)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 EmitCtx(final BlockList blocks, final ByteArrayOStream out)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 { Branch(final int opcode, final int [] targets)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 */ maxlength()1494 int maxlength () { return 1; } 1495 emit(EmitCtx ctx)1496 abstract void emit (EmitCtx ctx); 1497 1498 // TODO: this method must signal when it is necessary to switch to long jump form emitJumpOffset2(final EmitCtx ctx, final int ip, final int targetBlockID)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 emitJumpOffset4(final EmitCtx ctx, final int ip, final int targetBlockID)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 { TERMINATE(final int opcode)1562 TERMINATE (final int opcode) 1563 { 1564 super (opcode, null); 1565 } 1566 length()1567 int length () { return 1; } 1568 emit(final EmitCtx ctx)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 { RET(final int opcode, final int varindex)1579 RET (final int opcode, final int varindex) 1580 { 1581 super (opcode, null); 1582 m_varindex = varindex; 1583 } 1584 length()1585 int length () { return (m_varindex <= 0xFF) ? 2 : 3; } 1586 emit(final EmitCtx ctx)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 { JUMP2(final int opcode, final int target)1612 JUMP2 (final int opcode, final int target) 1613 { 1614 super (opcode, new int [] {target}); 1615 } 1616 maxlength()1617 int maxlength () { return 5; } 1618 emit(final EmitCtx ctx)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 { JUMP4(final int opcode, final int target)1636 JUMP4 (final int opcode, final int target) 1637 { 1638 super (opcode, new int [] {target}); 1639 } 1640 maxlength()1641 int maxlength () { return 5; } 1642 emit(final EmitCtx ctx)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 { IFJUMP2(final int opcode, final int target)1658 IFJUMP2 (final int opcode, final int target) 1659 { 1660 super (opcode, new int [] {target}); 1661 } 1662 maxlength()1663 int maxlength () { return 8; } 1664 emit(final EmitCtx ctx)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 { LOOKUPSWITCH(final int [] keys, final int [] targets )1682 LOOKUPSWITCH (final int [] keys, final int [] targets /* first one is default */) 1683 { 1684 super (_lookupswitch, targets); 1685 m_keys = keys; 1686 } 1687 maxlength()1688 int maxlength () { return 12 + (m_keys.length << 3); } 1689 emit(final EmitCtx ctx)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 { TABLESWITCH(final int low, final int high, final int [] targets )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 maxlength()1738 int maxlength () { return 12 + (m_targets.length << 2); } 1739 emit(final EmitCtx ctx)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 { CodeSegment(final InstrVisitor visitor)1787 CodeSegment (final InstrVisitor visitor) 1788 { 1789 m_visitor = visitor; // TODO: will this field be used? 1790 } 1791 length()1792 abstract int length (); maxstack()1793 abstract int maxstack (); emit(EmitCtx ctx)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 { clinitHeader(final InstrVisitor visitor, final int localVarIndex)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 length()1856 int length () { return m_buf.size (); } maxstack()1857 int maxstack () { return 2; } // note: needs to be updated each time emitted code changes 1858 emit(final EmitCtx ctx)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 { methodHeader(final InstrVisitor visitor, final int localVarIndex)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 length()1939 int length () { return m_buf.size (); } maxstack()1940 int maxstack () { return 2; } // note: needs to be updated each time emitted code changes 1941 emit(final EmitCtx ctx)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 { BlockSegment(final InstrVisitor visitor, final int localVarIndex, final int blockID)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 length()1994 int length () { return m_buf.size (); } maxstack()1995 int maxstack () { return 3; } // note: needs to be updated each time emitted code changes 1996 emit(final EmitCtx ctx)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 { compare(final Object o1, final Object o2)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 setClassName(final String fullName)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 // ----------------------------------------------------------------------------