1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package com.google.common.truth; 15 16 import static com.google.common.base.MoreObjects.firstNonNull; 17 import static com.google.common.base.Preconditions.checkArgument; 18 import static com.google.common.base.Preconditions.checkState; 19 import static com.google.common.collect.Iterables.getOnlyElement; 20 import static java.lang.Thread.currentThread; 21 22 import com.google.auto.value.AutoValue; 23 import com.google.auto.value.AutoValue.CopyAnnotations; 24 import com.google.common.annotations.GwtIncompatible; 25 import com.google.common.collect.ImmutableList; 26 import com.google.common.collect.ImmutableSet; 27 import com.google.common.collect.ImmutableSetMultimap; 28 import com.google.errorprone.annotations.CanIgnoreReturnValue; 29 import com.google.errorprone.annotations.CheckReturnValue; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.util.ArrayList; 33 import java.util.Map.Entry; 34 import org.objectweb.asm.ClassReader; 35 import org.objectweb.asm.ClassVisitor; 36 import org.objectweb.asm.Handle; 37 import org.objectweb.asm.Label; 38 import org.objectweb.asm.MethodVisitor; 39 import org.objectweb.asm.Opcodes; 40 import org.objectweb.asm.Type; 41 42 /** 43 * Given the stack frame of a failing assertion, tries to describe what the user passed to {@code 44 * assertThat}. 45 * 46 * <p>For example, suppose that the test contains: 47 * 48 * <pre>{@code 49 * assertThat(logService.fetchLogMessages(startDate, endDate)) 50 * .containsExactly(message1, message2) 51 * .inOrder(); 52 * }</pre> 53 * 54 * If either {@code containsExactly} or {@code inOrder} fails, {@code ActualValueInference} reports 55 * (if the rest of the test method is simple enough to analyze easily) that the user passed {@code 56 * fetchLogMessages(...)}. This allows us to produce a failure message like: 57 * 58 * <pre> 59 * value of : fetchLogMessages(...) 60 * missing (1): message1 61 * ... 62 * </pre> 63 * 64 * {@code ActualValueInference} accomplishes this by examining the bytecode of the test. Naturally, 65 * this is all best-effort. 66 */ 67 @GwtIncompatible 68 final class ActualValueInference { 69 /** <b>Call {@link Platform#inferDescription} rather than calling this directly.</b> */ describeActualValue(String className, String methodName, int lineNumber)70 static String describeActualValue(String className, String methodName, int lineNumber) { 71 InferenceClassVisitor visitor; 72 try { 73 // TODO(cpovirk): Verify that methodName is correct for constructors and static initializers. 74 visitor = new InferenceClassVisitor(methodName); 75 } catch (IllegalArgumentException theVersionOfAsmIsOlderThanWeRequire) { 76 // TODO(cpovirk): Consider what minimum version the class and method visitors really need. 77 // TODO(cpovirk): Log a warning? 78 return null; 79 } 80 81 ClassLoader loader = 82 firstNonNull( 83 currentThread().getContextClassLoader(), ActualValueInference.class.getClassLoader()); 84 /* 85 * We're assuming that classes were loaded in a simple way. In principle, we could do better 86 * with java.lang.instrument. 87 */ 88 InputStream stream = null; 89 try { 90 stream = loader.getResourceAsStream(className.replace('.', '/') + ".class"); 91 // TODO(cpovirk): Disable inference if the bytecode version is newer than we've tested on? 92 new ClassReader(stream).accept(visitor, /*parsingOptions=*/ 0); 93 ImmutableSet<StackEntry> actualsAtLine = visitor.actualValueAtLine.build().get(lineNumber); 94 /* 95 * It's very unlikely that more than one assertion would happen on the same line _but with 96 * different root actual values_. 97 * 98 * That is, it's common to have: 99 * assertThat(list).containsExactly(...).inOrder(); 100 * 101 * But it's not common to have, all on one line: 102 * assertThat(list).isEmpty(); assertThat(list2).containsExactly(...); 103 * 104 * In principle, we could try to distinguish further by looking at what assertion method 105 * failed (which our caller could pass us by looking higher on the stack). But it's hard to 106 * imagine that it would be worthwhile. 107 */ 108 return actualsAtLine.size() == 1 ? getOnlyElement(actualsAtLine).description() : null; 109 } catch (IOException e) { 110 /* 111 * Likely "Class not found," perhaps from generated bytecode (or from StackTraceCleaner's 112 * pseudo-frames, which ideally ActualValueInference would tell it not to create). 113 */ 114 // TODO(cpovirk): Log a warning? 115 return null; 116 } catch (SecurityException e) { 117 // Inside Google, some tests run under a security manager that forbids filesystem access. 118 // TODO(cpovirk): Log a warning? 119 return null; 120 } finally { 121 closeQuietly(stream); 122 } 123 } 124 125 /** 126 * An entry on the stack (or the local-variable table) with a {@linkplain InferredType type} and 127 * sometimes a description of {@linkplain DescribedEntry how the value was produced} or, as a 128 * special case, whether {@linkplain SubjectEntry the value is a Truth subject}. 129 */ 130 abstract static class StackEntry { type()131 abstract InferredType type(); 132 133 // Each of these is overridden by a subclass: 134 isSubject()135 boolean isSubject() { 136 return false; 137 } 138 actualValue()139 StackEntry actualValue() { 140 throw new ClassCastException(getClass().getName()); 141 } 142 description()143 String description() { 144 return null; 145 } 146 } 147 148 /** An entry that we know nothing about except for its type. */ 149 @AutoValue 150 @CopyAnnotations 151 @GwtIncompatible 152 abstract static class OpaqueEntry extends StackEntry { 153 @Override toString()154 public final String toString() { 155 return "unknown"; 156 } 157 } 158 opaque(InferredType type)159 private static StackEntry opaque(InferredType type) { 160 return new AutoValue_ActualValueInference_OpaqueEntry(type); 161 } 162 163 /** 164 * An entry that contains a description of how it was created. Currently, the only case in which 165 * we provide a description is when the value comes from a method call whose name looks 166 * "interesting." 167 */ 168 @AutoValue 169 @CopyAnnotations 170 @GwtIncompatible 171 abstract static class DescribedEntry extends StackEntry { 172 @Override description()173 abstract String description(); 174 175 @Override toString()176 public final String toString() { 177 return description(); 178 } 179 } 180 described(InferredType type, String description)181 private static StackEntry described(InferredType type, String description) { 182 return new AutoValue_ActualValueInference_DescribedEntry(type, description); 183 } 184 185 /** 186 * An entry for a {@link Subject} (or a similar object derived with a {@code Subject}, like {@link 187 * Ordered}). 188 * 189 * <p>The entry contains the "root actual value" of the assertion. In an assertion like {@code 190 * assertThat(e).hasMessageThat().contains("foo")}, the root actual value is the {@code Throwable} 191 * {@code e}, even though the {@code contains} assertion operates on a string message. 192 */ 193 @AutoValue 194 @CopyAnnotations 195 @GwtIncompatible 196 abstract static class SubjectEntry extends StackEntry { 197 @Override actualValue()198 abstract StackEntry actualValue(); 199 200 @Override isSubject()201 final boolean isSubject() { 202 return true; 203 } 204 205 @Override toString()206 public final String toString() { 207 return String.format("subjectFor(%s)", actualValue()); 208 } 209 } 210 subjectFor(InferredType type, StackEntry actual)211 private static StackEntry subjectFor(InferredType type, StackEntry actual) { 212 return new AutoValue_ActualValueInference_SubjectEntry(type, actual); 213 } 214 215 private static final class InferenceMethodVisitor extends MethodVisitor { 216 private boolean used = false; 217 private final ArrayList<StackEntry> localVariableSlots; 218 private final ArrayList<StackEntry> operandStack = new ArrayList<>(); 219 private FrameInfo previousFrame; 220 /** For debugging purpose. */ 221 private final String methodSignature; 222 223 /** 224 * The ASM labels that we've seen so far, which we use to look up the closest line number for 225 * each assertion. 226 */ 227 private final ImmutableList.Builder<Label> labelsSeen = ImmutableList.builder(); 228 229 /** 230 * The mapping from label to line number. 231 * 232 * <p>I had hoped that we didn't need this: In the {@code .class} files I looked at, {@code 233 * visitLineNumber} calls were interleaved with the actual instructions. (I even have evidence 234 * that the current implementation visits labels and line numbers together: See Label.accept.) 235 * If that were guaranteed, then we could identify the line number for each assertion just by 236 * looking at which {@code visitLineNumber} call we'd seen most recently. However, that 237 * <i>doesn't</i> appear to be guaranteed, so we store this mapping and then join it with the 238 * labels at the end. 239 * 240 * <p>I would expect to be able to use a map here. But I'm seeing multiple line numbers for the 241 * same label in some Kotlin code. 242 */ 243 private final ImmutableSetMultimap.Builder<Label, Integer> lineNumbersAtLabel = 244 ImmutableSetMultimap.builder(); 245 246 /** 247 * The mapping that indexes every root actual value by the full list of labels we'd visited 248 * before we visited it. 249 */ 250 private final ImmutableSetMultimap.Builder<ImmutableList<Label>, StackEntry> 251 actualValueAtLocation = ImmutableSetMultimap.builder(); 252 253 /** Set to {@code true} whenever a method permits multiple execution paths. */ 254 private boolean seenJump; 255 256 /** 257 * The output of this process: a mapping from line number to the root actual values with 258 * assertions on that line. This builder is potentially shared across multiple method visitors 259 * for the same class visitor. 260 */ 261 private final ImmutableSetMultimap.Builder<Integer, StackEntry> actualValueAtLine; 262 InferenceMethodVisitor( int access, String owner, String name, String methodDescriptor, ImmutableSetMultimap.Builder<Integer, StackEntry> actualValueAtLine)263 InferenceMethodVisitor( 264 int access, 265 String owner, 266 String name, 267 String methodDescriptor, 268 ImmutableSetMultimap.Builder<Integer, StackEntry> actualValueAtLine) { 269 super(Opcodes.ASM8); 270 localVariableSlots = createInitialLocalVariableSlots(access, owner, name, methodDescriptor); 271 previousFrame = 272 FrameInfo.create( 273 ImmutableList.copyOf(localVariableSlots), ImmutableList.<StackEntry>of()); 274 this.methodSignature = owner + "." + name + methodDescriptor; 275 this.actualValueAtLine = actualValueAtLine; 276 } 277 278 @Override visitCode()279 public void visitCode() { 280 checkState(!used, "Cannot reuse this method visitor."); 281 used = true; 282 super.visitCode(); 283 } 284 285 @Override visitEnd()286 public void visitEnd() { 287 if (seenJump) { 288 /* 289 * If there are multiple paths through a method, we'd have to examine them all and make sure 290 * that the values still match up. We could try someday, but it's hard. 291 */ 292 super.visitEnd(); 293 return; 294 } 295 ImmutableSetMultimap<Label, Integer> lineNumbersAtLabel = this.lineNumbersAtLabel.build(); 296 for (Entry<ImmutableList<Label>, StackEntry> e : actualValueAtLocation.build().entries()) { 297 for (int lineNumber : lineNumbers(e.getKey(), lineNumbersAtLabel)) { 298 actualValueAtLine.put(lineNumber, e.getValue()); 299 } 300 } 301 super.visitEnd(); 302 } 303 lineNumbers( ImmutableList<Label> labels, ImmutableSetMultimap<Label, Integer> lineNumbersAtLabel)304 private static ImmutableSet<Integer> lineNumbers( 305 ImmutableList<Label> labels, ImmutableSetMultimap<Label, Integer> lineNumbersAtLabel) { 306 for (Label label : labels.reverse()) { 307 if (lineNumbersAtLabel.containsKey(label)) { 308 return lineNumbersAtLabel.get(label); 309 } 310 } 311 return ImmutableSet.of(); 312 } 313 314 @Override visitLineNumber(int line, Label start)315 public void visitLineNumber(int line, Label start) { 316 lineNumbersAtLabel.put(start, line); 317 super.visitLineNumber(line, start); 318 } 319 320 @Override visitLabel(Label label)321 public void visitLabel(Label label) { 322 labelsSeen.add(label); 323 super.visitLabel(label); 324 } 325 326 /** Returns the entry for the operand at the specified offset. 0 means the top of the stack. */ getOperandFromTop(int offsetFromTop)327 private StackEntry getOperandFromTop(int offsetFromTop) { 328 int index = operandStack.size() - 1 - offsetFromTop; 329 checkState( 330 index >= 0, 331 "Invalid offset %s in the list of size %s. The current method is %s", 332 offsetFromTop, 333 operandStack.size(), 334 methodSignature); 335 return operandStack.get(index); 336 } 337 338 @Override visitInsn(int opcode)339 public void visitInsn(int opcode) { 340 switch (opcode) { 341 case Opcodes.NOP: 342 case Opcodes.INEG: 343 case Opcodes.LNEG: 344 case Opcodes.FNEG: 345 case Opcodes.DNEG: 346 case Opcodes.I2B: 347 case Opcodes.I2C: 348 case Opcodes.I2S: 349 case Opcodes.RETURN: 350 break; 351 case Opcodes.ACONST_NULL: 352 push(InferredType.NULL); 353 break; 354 case Opcodes.ICONST_M1: 355 case Opcodes.ICONST_0: 356 case Opcodes.ICONST_1: 357 case Opcodes.ICONST_2: 358 case Opcodes.ICONST_3: 359 case Opcodes.ICONST_4: 360 case Opcodes.ICONST_5: 361 push(InferredType.INT); 362 break; 363 case Opcodes.LCONST_0: 364 case Opcodes.LCONST_1: 365 push(InferredType.LONG); 366 push(InferredType.TOP); 367 break; 368 case Opcodes.FCONST_0: 369 case Opcodes.FCONST_1: 370 case Opcodes.FCONST_2: 371 push(InferredType.FLOAT); 372 break; 373 case Opcodes.DCONST_0: 374 case Opcodes.DCONST_1: 375 push(InferredType.DOUBLE); 376 push(InferredType.TOP); 377 break; 378 case Opcodes.IALOAD: 379 case Opcodes.BALOAD: 380 case Opcodes.CALOAD: 381 case Opcodes.SALOAD: 382 pop(2); 383 push(InferredType.INT); 384 break; 385 case Opcodes.LALOAD: 386 case Opcodes.D2L: 387 pop(2); 388 push(InferredType.LONG); 389 push(InferredType.TOP); 390 break; 391 case Opcodes.DALOAD: 392 case Opcodes.L2D: 393 pop(2); 394 push(InferredType.DOUBLE); 395 push(InferredType.TOP); 396 break; 397 case Opcodes.AALOAD: 398 InferredType arrayType = pop(2).type(); 399 InferredType elementType = arrayType.getElementTypeIfArrayOrThrow(); 400 push(elementType); 401 break; 402 case Opcodes.IASTORE: 403 case Opcodes.BASTORE: 404 case Opcodes.CASTORE: 405 case Opcodes.SASTORE: 406 case Opcodes.FASTORE: 407 case Opcodes.AASTORE: 408 pop(3); 409 break; 410 case Opcodes.LASTORE: 411 case Opcodes.DASTORE: 412 pop(4); 413 break; 414 case Opcodes.POP: 415 case Opcodes.IRETURN: 416 case Opcodes.FRETURN: 417 case Opcodes.ARETURN: 418 case Opcodes.ATHROW: 419 case Opcodes.MONITORENTER: 420 case Opcodes.MONITOREXIT: 421 pop(); 422 break; 423 case Opcodes.POP2: 424 case Opcodes.LRETURN: 425 case Opcodes.DRETURN: 426 pop(2); 427 break; 428 case Opcodes.DUP: 429 push(top()); 430 break; 431 case Opcodes.DUP_X1: 432 { 433 StackEntry top = pop(); 434 StackEntry next = pop(); 435 push(top); 436 push(next); 437 push(top); 438 break; 439 } 440 case Opcodes.DUP_X2: 441 { 442 StackEntry top = pop(); 443 StackEntry next = pop(); 444 StackEntry bottom = pop(); 445 push(top); 446 push(bottom); 447 push(next); 448 push(top); 449 break; 450 } 451 case Opcodes.DUP2: 452 { 453 StackEntry top = pop(); 454 StackEntry next = pop(); 455 push(next); 456 push(top); 457 push(next); 458 push(top); 459 break; 460 } 461 case Opcodes.DUP2_X1: 462 { 463 StackEntry top = pop(); 464 StackEntry next = pop(); 465 StackEntry bottom = pop(); 466 push(next); 467 push(top); 468 push(bottom); 469 push(next); 470 push(top); 471 break; 472 } 473 case Opcodes.DUP2_X2: 474 { 475 StackEntry t1 = pop(); 476 StackEntry t2 = pop(); 477 StackEntry t3 = pop(); 478 StackEntry t4 = pop(); 479 push(t2); 480 push(t1); 481 push(t4); 482 push(t3); 483 push(t2); 484 push(t1); 485 break; 486 } 487 case Opcodes.SWAP: 488 { 489 StackEntry top = pop(); 490 StackEntry next = pop(); 491 push(top); 492 push(next); 493 break; 494 } 495 case Opcodes.IADD: 496 case Opcodes.ISUB: 497 case Opcodes.IMUL: 498 case Opcodes.IDIV: 499 case Opcodes.IREM: 500 case Opcodes.ISHL: 501 case Opcodes.ISHR: 502 case Opcodes.IUSHR: 503 case Opcodes.IAND: 504 case Opcodes.IOR: 505 case Opcodes.IXOR: 506 case Opcodes.L2I: 507 case Opcodes.D2I: 508 case Opcodes.FCMPL: 509 case Opcodes.FCMPG: 510 pop(2); 511 push(InferredType.INT); 512 break; 513 514 case Opcodes.LADD: 515 case Opcodes.LSUB: 516 case Opcodes.LMUL: 517 case Opcodes.LDIV: 518 case Opcodes.LREM: 519 case Opcodes.LAND: 520 case Opcodes.LOR: 521 case Opcodes.LXOR: 522 pop(4); 523 push(InferredType.LONG); 524 push(InferredType.TOP); 525 break; 526 527 case Opcodes.LSHL: 528 case Opcodes.LSHR: 529 case Opcodes.LUSHR: 530 pop(3); 531 push(InferredType.LONG); 532 push(InferredType.TOP); 533 break; 534 case Opcodes.I2L: 535 case Opcodes.F2L: 536 pop(); 537 push(InferredType.LONG); 538 push(InferredType.TOP); 539 break; 540 case Opcodes.I2F: 541 pop(); 542 push(InferredType.FLOAT); 543 break; 544 545 case Opcodes.LCMP: 546 case Opcodes.DCMPG: 547 case Opcodes.DCMPL: 548 pop(4); 549 push(InferredType.INT); 550 break; 551 552 case Opcodes.I2D: 553 case Opcodes.F2D: 554 pop(); 555 push(InferredType.DOUBLE); 556 push(InferredType.TOP); 557 break; 558 case Opcodes.F2I: 559 case Opcodes.ARRAYLENGTH: 560 pop(); 561 push(InferredType.INT); 562 break; 563 case Opcodes.FALOAD: 564 case Opcodes.FADD: 565 case Opcodes.FSUB: 566 case Opcodes.FMUL: 567 case Opcodes.FDIV: 568 case Opcodes.FREM: 569 case Opcodes.L2F: 570 case Opcodes.D2F: 571 pop(2); 572 push(InferredType.FLOAT); 573 break; 574 575 case Opcodes.DADD: 576 case Opcodes.DSUB: 577 case Opcodes.DMUL: 578 case Opcodes.DDIV: 579 case Opcodes.DREM: 580 pop(4); 581 push(InferredType.DOUBLE); 582 push(InferredType.TOP); 583 break; 584 default: 585 throw new RuntimeException("Unhandled opcode " + opcode); 586 } 587 super.visitInsn(opcode); 588 } 589 590 @Override visitIntInsn(int opcode, int operand)591 public void visitIntInsn(int opcode, int operand) { 592 switch (opcode) { 593 case Opcodes.BIPUSH: 594 case Opcodes.SIPUSH: 595 push(InferredType.INT); 596 break; 597 case Opcodes.NEWARRAY: 598 pop(); 599 switch (operand) { 600 case Opcodes.T_BOOLEAN: 601 pushDescriptor("[Z"); 602 break; 603 case Opcodes.T_CHAR: 604 pushDescriptor("[C"); 605 break; 606 case Opcodes.T_FLOAT: 607 pushDescriptor("[F"); 608 break; 609 case Opcodes.T_DOUBLE: 610 pushDescriptor("[D"); 611 break; 612 case Opcodes.T_BYTE: 613 pushDescriptor("[B"); 614 break; 615 case Opcodes.T_SHORT: 616 pushDescriptor("[S"); 617 break; 618 case Opcodes.T_INT: 619 pushDescriptor("[I"); 620 break; 621 case Opcodes.T_LONG: 622 pushDescriptor("[J"); 623 break; 624 default: 625 throw new RuntimeException("Unhandled operand value: " + operand); 626 } 627 break; 628 default: 629 throw new RuntimeException("Unhandled opcode " + opcode); 630 } 631 super.visitIntInsn(opcode, operand); 632 } 633 634 @Override visitVarInsn(int opcode, int var)635 public void visitVarInsn(int opcode, int var) { 636 switch (opcode) { 637 case Opcodes.ILOAD: 638 push(InferredType.INT); 639 break; 640 case Opcodes.LLOAD: 641 push(InferredType.LONG); 642 push(InferredType.TOP); 643 break; 644 case Opcodes.FLOAD: 645 push(InferredType.FLOAT); 646 break; 647 case Opcodes.DLOAD: 648 push(InferredType.DOUBLE); 649 push(InferredType.TOP); 650 break; 651 case Opcodes.ALOAD: 652 push(getLocalVariable(var)); 653 break; 654 case Opcodes.ISTORE: 655 case Opcodes.FSTORE: 656 case Opcodes.ASTORE: 657 { 658 StackEntry entry = pop(); 659 setLocalVariable(var, entry); 660 break; 661 } 662 case Opcodes.LSTORE: 663 case Opcodes.DSTORE: 664 { 665 StackEntry entry = pop(2); 666 setLocalVariable(var, entry); 667 setLocalVariable(var + 1, opaque(InferredType.TOP)); 668 break; 669 } 670 case Opcodes.RET: 671 throw new RuntimeException("The instruction RET is not supported"); 672 default: 673 throw new RuntimeException("Unhandled opcode " + opcode); 674 } 675 super.visitVarInsn(opcode, var); 676 } 677 678 @Override visitTypeInsn(int opcode, String type)679 public void visitTypeInsn(int opcode, String type) { 680 String descriptor = convertToDescriptor(type); 681 switch (opcode) { 682 case Opcodes.NEW: 683 // This should be UNINITIALIZED(label). Okay for type inference. 684 pushDescriptor(descriptor); 685 break; 686 case Opcodes.ANEWARRAY: 687 pop(); 688 pushDescriptor('[' + descriptor); 689 break; 690 case Opcodes.CHECKCAST: 691 pop(); 692 pushDescriptor(descriptor); 693 break; 694 case Opcodes.INSTANCEOF: 695 pop(); 696 push(InferredType.INT); 697 break; 698 default: 699 throw new RuntimeException("Unhandled opcode " + opcode); 700 } 701 super.visitTypeInsn(opcode, type); 702 } 703 704 @Override visitFieldInsn(int opcode, String owner, String name, String desc)705 public void visitFieldInsn(int opcode, String owner, String name, String desc) { 706 switch (opcode) { 707 case Opcodes.GETSTATIC: 708 pushDescriptor(desc); 709 break; 710 case Opcodes.PUTSTATIC: 711 popDescriptor(desc); 712 break; 713 case Opcodes.GETFIELD: 714 pop(); 715 pushDescriptor(desc); 716 break; 717 case Opcodes.PUTFIELD: 718 popDescriptor(desc); 719 pop(); 720 break; 721 default: 722 throw new RuntimeException( 723 "Unhandled opcode " 724 + opcode 725 + ", owner=" 726 + owner 727 + ", name=" 728 + name 729 + ", desc" 730 + desc); 731 } 732 super.visitFieldInsn(opcode, owner, name, desc); 733 } 734 735 @Override visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)736 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 737 if (opcode == Opcodes.INVOKESPECIAL && "<init>".equals(name)) { 738 int argumentSize = (Type.getArgumentsAndReturnSizes(desc) >> 2); 739 InferredType receiverType = getOperandFromTop(argumentSize - 1).type(); 740 if (receiverType.isUninitialized()) { 741 InferredType realType = InferredType.create('L' + owner + ';'); 742 replaceUninitializedTypeInStack(receiverType, realType); 743 } 744 } 745 switch (opcode) { 746 case Opcodes.INVOKESPECIAL: 747 case Opcodes.INVOKEVIRTUAL: 748 case Opcodes.INVOKESTATIC: 749 case Opcodes.INVOKEINTERFACE: 750 Invocation.Builder invocation = Invocation.builder(name); 751 752 if (isThatOrAssertThat(owner, name)) { 753 invocation.setActualValue(getOperandFromTop(0)); 754 } else if (isBoxing(owner, name, desc)) { 755 invocation.setBoxingInput( 756 // double and long are represented by a TOP with the "real" value under it. 757 getOperandFromTop(0).type() == InferredType.TOP 758 ? getOperandFromTop(1) 759 : getOperandFromTop(0)); 760 } 761 762 popDescriptor(desc); 763 764 if (opcode != Opcodes.INVOKESTATIC) { 765 invocation.setReceiver(pop()); 766 } 767 768 pushDescriptorAndMaybeProcessMethodCall(desc, invocation.build()); 769 break; 770 default: 771 throw new RuntimeException( 772 String.format( 773 "Unhandled opcode %s, owner=%s, name=%s, desc=%s, itf=%s", 774 opcode, owner, name, desc, itf)); 775 } 776 super.visitMethodInsn(opcode, owner, name, desc, itf); 777 } 778 779 @Override visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs)780 public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { 781 popDescriptor(desc); 782 pushDescriptor(desc); 783 super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); 784 } 785 786 @Override visitJumpInsn(int opcode, Label label)787 public void visitJumpInsn(int opcode, Label label) { 788 seenJump = true; 789 switch (opcode) { 790 case Opcodes.IFEQ: 791 case Opcodes.IFNE: 792 case Opcodes.IFLT: 793 case Opcodes.IFGE: 794 case Opcodes.IFGT: 795 case Opcodes.IFLE: 796 pop(); 797 break; 798 case Opcodes.IF_ICMPEQ: 799 case Opcodes.IF_ICMPNE: 800 case Opcodes.IF_ICMPLT: 801 case Opcodes.IF_ICMPGE: 802 case Opcodes.IF_ICMPGT: 803 case Opcodes.IF_ICMPLE: 804 case Opcodes.IF_ACMPEQ: 805 case Opcodes.IF_ACMPNE: 806 pop(2); 807 break; 808 case Opcodes.GOTO: 809 break; 810 case Opcodes.JSR: 811 throw new RuntimeException("The JSR instruction is not supported."); 812 case Opcodes.IFNULL: 813 case Opcodes.IFNONNULL: 814 pop(1); 815 break; 816 default: 817 throw new RuntimeException("Unhandled opcode " + opcode); 818 } 819 super.visitJumpInsn(opcode, label); 820 } 821 822 @Override visitLdcInsn(Object cst)823 public void visitLdcInsn(Object cst) { 824 if (cst instanceof Integer) { 825 push(InferredType.INT); 826 } else if (cst instanceof Float) { 827 push(InferredType.FLOAT); 828 } else if (cst instanceof Long) { 829 push(InferredType.LONG); 830 push(InferredType.TOP); 831 } else if (cst instanceof Double) { 832 push(InferredType.DOUBLE); 833 push(InferredType.TOP); 834 } else if (cst instanceof String) { 835 pushDescriptor("Ljava/lang/String;"); 836 } else if (cst instanceof Type) { 837 pushDescriptor(((Type) cst).getDescriptor()); 838 } else if (cst instanceof Handle) { 839 pushDescriptor("Ljava/lang/invoke/MethodHandle;"); 840 } else { 841 throw new RuntimeException("Cannot handle constant " + cst + " for LDC instruction"); 842 } 843 super.visitLdcInsn(cst); 844 } 845 846 @Override visitIincInsn(int var, int increment)847 public void visitIincInsn(int var, int increment) { 848 setLocalVariable(var, opaque(InferredType.INT)); 849 super.visitIincInsn(var, increment); 850 } 851 852 @Override visitTableSwitchInsn(int min, int max, Label dflt, Label... labels)853 public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { 854 seenJump = true; 855 pop(); 856 super.visitTableSwitchInsn(min, max, dflt, labels); 857 } 858 859 @Override visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels)860 public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { 861 seenJump = true; 862 pop(); 863 super.visitLookupSwitchInsn(dflt, keys, labels); 864 } 865 866 @Override visitTryCatchBlock(Label start, Label end, Label handler, String type)867 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { 868 /* 869 * Inference already fails for at least some try-catch blocks, apparently because of the extra 870 * frames they create. Still, let's disable inference explicitly. 871 */ 872 seenJump = true; 873 super.visitTryCatchBlock(start, end, handler, type); 874 } 875 876 @Override visitMultiANewArrayInsn(String desc, int dims)877 public void visitMultiANewArrayInsn(String desc, int dims) { 878 pop(dims); 879 pushDescriptor(desc); 880 super.visitMultiANewArrayInsn(desc, dims); 881 } 882 883 @Override visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack)884 public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { 885 switch (type) { 886 case Opcodes.F_NEW: 887 // Expanded form. 888 previousFrame = 889 FrameInfo.create( 890 convertTypesInStackMapFrame(nLocal, local), 891 convertTypesInStackMapFrame(nStack, stack)); 892 break; 893 case Opcodes.F_SAME: 894 // This frame type indicates that the frame has exactly the same local variables as the 895 // previous frame and that the operand stack is empty. 896 previousFrame = FrameInfo.create(previousFrame.locals(), ImmutableList.<StackEntry>of()); 897 break; 898 case Opcodes.F_SAME1: 899 // This frame type indicates that the frame has exactly the same local variables as the 900 // previous frame and that the operand stack has one entry. 901 previousFrame = 902 FrameInfo.create(previousFrame.locals(), convertTypesInStackMapFrame(nStack, stack)); 903 break; 904 case Opcodes.F_APPEND: 905 // This frame type indicates that the frame has the same locals as the previous frame 906 // except that k additional locals are defined, and that the operand stack is empty. 907 previousFrame = 908 FrameInfo.create( 909 appendArrayToList(previousFrame.locals(), nLocal, local), 910 ImmutableList.<StackEntry>of()); 911 break; 912 case Opcodes.F_CHOP: 913 // This frame type indicates that the frame has the same local variables as the previous 914 // frame except that the last k local variables are absent, and that the operand stack is 915 // empty. 916 previousFrame = 917 FrameInfo.create( 918 removeBackFromList(previousFrame.locals(), nLocal), 919 ImmutableList.<StackEntry>of()); 920 break; 921 case Opcodes.F_FULL: 922 previousFrame = 923 FrameInfo.create( 924 convertTypesInStackMapFrame(nLocal, local), 925 convertTypesInStackMapFrame(nStack, stack)); 926 break; 927 default: 928 // continue below 929 } 930 // Update types for operand stack and local variables. 931 operandStack.clear(); 932 operandStack.addAll(previousFrame.stack()); 933 localVariableSlots.clear(); 934 localVariableSlots.addAll(previousFrame.locals()); 935 super.visitFrame(type, nLocal, local, nStack, stack); 936 } 937 convertToDescriptor(String type)938 private static String convertToDescriptor(String type) { 939 return (type.length() > 1 && type.charAt(0) != '[') ? 'L' + type + ';' : type; 940 } 941 push(InferredType type)942 private void push(InferredType type) { 943 push(opaque(type)); 944 } 945 push(StackEntry entry)946 private void push(StackEntry entry) { 947 operandStack.add(entry); 948 } 949 replaceUninitializedTypeInStack(InferredType oldType, InferredType newType)950 private void replaceUninitializedTypeInStack(InferredType oldType, InferredType newType) { 951 checkArgument(oldType.isUninitialized(), "The old type is NOT uninitialized. %s", oldType); 952 for (int i = 0, size = operandStack.size(); i < size; ++i) { 953 InferredType type = operandStack.get(i).type(); 954 if (type.equals(oldType)) { 955 operandStack.set(i, opaque(newType)); 956 } 957 } 958 } 959 pushDescriptor(String desc)960 private void pushDescriptor(String desc) { 961 pushDescriptorAndMaybeProcessMethodCall(desc, /*invocation=*/ null); 962 } 963 964 /** 965 * Pushes entries onto the stack for the given arguments, and, if the descriptor is for a method 966 * call, records the assertion made by that call (if any). 967 * 968 * <p>If the descriptor is for a call, this method not only records the assertion made by it (if 969 * any) but also examines its parameters to generate more detailed stack entries. 970 * 971 * @param desc the descriptor of the type to be added to the stack (or the descriptor of the 972 * method whose return value is to be added to the stack) 973 * @param invocation the method invocation being visited, or {@code null} if a non-method 974 * descriptor is being visited 975 */ pushDescriptorAndMaybeProcessMethodCall(String desc, Invocation invocation)976 private void pushDescriptorAndMaybeProcessMethodCall(String desc, Invocation invocation) { 977 if (invocation != null && invocation.isOnSubjectInstance()) { 978 actualValueAtLocation.put(labelsSeen.build(), invocation.receiver().actualValue()); 979 } 980 981 boolean hasParams = invocation != null && (Type.getArgumentsAndReturnSizes(desc) >> 2) > 1; 982 int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0; 983 switch (desc.charAt(index)) { 984 case 'V': 985 return; 986 case 'Z': 987 case 'C': 988 case 'B': 989 case 'S': 990 case 'I': 991 pushMaybeDescribed(InferredType.INT, invocation, hasParams); 992 break; 993 case 'F': 994 pushMaybeDescribed(InferredType.FLOAT, invocation, hasParams); 995 break; 996 case 'D': 997 pushMaybeDescribed(InferredType.DOUBLE, invocation, hasParams); 998 push(InferredType.TOP); 999 break; 1000 case 'J': 1001 pushMaybeDescribed(InferredType.LONG, invocation, hasParams); 1002 push(InferredType.TOP); 1003 break; 1004 case 'L': 1005 case '[': 1006 pushMaybeDescribed(InferredType.create(desc.substring(index)), invocation, hasParams); 1007 break; 1008 default: 1009 throw new RuntimeException("Unhandled type: " + desc); 1010 } 1011 } 1012 pushMaybeDescribed(InferredType type, Invocation invocation, boolean hasParams)1013 private void pushMaybeDescribed(InferredType type, Invocation invocation, boolean hasParams) { 1014 push(invocation == null ? opaque(type) : invocation.deriveEntry(type, hasParams)); 1015 } 1016 1017 @CanIgnoreReturnValue pop()1018 private StackEntry pop() { 1019 return pop(1); 1020 } 1021 1022 /** Pop elements from the end of the operand stack, and return the last popped element. */ 1023 @CanIgnoreReturnValue pop(int count)1024 private StackEntry pop(int count) { 1025 checkArgument( 1026 count >= 1, "The count should be at least one: %s (In %s)", count, methodSignature); 1027 checkState( 1028 operandStack.size() >= count, 1029 "There are no enough elements in the stack. count=%s, stack=%s (In %s)", 1030 count, 1031 operandStack, 1032 methodSignature); 1033 int expectedLastIndex = operandStack.size() - count - 1; 1034 StackEntry lastPopped = null; 1035 for (int i = operandStack.size() - 1; i > expectedLastIndex; --i) { 1036 lastPopped = operandStack.remove(i); 1037 } 1038 return lastPopped; 1039 } 1040 popDescriptor(String desc)1041 private void popDescriptor(String desc) { 1042 char c = desc.charAt(0); 1043 switch (c) { 1044 case '(': 1045 int argumentSize = (Type.getArgumentsAndReturnSizes(desc) >> 2) - 1; 1046 if (argumentSize > 0) { 1047 pop(argumentSize); 1048 } 1049 break; 1050 case 'J': 1051 case 'D': 1052 pop(2); 1053 break; 1054 default: 1055 pop(1); 1056 break; 1057 } 1058 } 1059 getLocalVariable(int index)1060 private StackEntry getLocalVariable(int index) { 1061 checkState( 1062 index < localVariableSlots.size(), 1063 "Cannot find type for var %s in method %s", 1064 index, 1065 methodSignature); 1066 return localVariableSlots.get(index); 1067 } 1068 1069 private void setLocalVariable(int index, StackEntry entry) { 1070 while (localVariableSlots.size() <= index) { 1071 localVariableSlots.add(opaque(InferredType.TOP)); 1072 } 1073 localVariableSlots.set(index, entry); 1074 } 1075 1076 private StackEntry top() { 1077 return operandStack.get(operandStack.size() - 1); 1078 } 1079 1080 /** 1081 * Create the slots for local variables at the very beginning of the method with the information 1082 * of the declaring class and the method descriptor. 1083 */ 1084 private static ArrayList<StackEntry> createInitialLocalVariableSlots( 1085 int access, String ownerClass, String methodName, String methodDescriptor) { 1086 ArrayList<StackEntry> entries = new ArrayList<>(); 1087 1088 if (!isStatic(access)) { 1089 // Instance method, and this is the receiver 1090 entries.add(opaque(InferredType.create(convertToDescriptor(ownerClass)))); 1091 } 1092 Type[] argumentTypes = Type.getArgumentTypes(methodDescriptor); 1093 for (Type argumentType : argumentTypes) { 1094 switch (argumentType.getSort()) { 1095 case Type.BOOLEAN: 1096 case Type.BYTE: 1097 case Type.CHAR: 1098 case Type.SHORT: 1099 case Type.INT: 1100 entries.add(opaque(InferredType.INT)); 1101 break; 1102 case Type.FLOAT: 1103 entries.add(opaque(InferredType.FLOAT)); 1104 break; 1105 case Type.LONG: 1106 entries.add(opaque(InferredType.LONG)); 1107 entries.add(opaque(InferredType.TOP)); 1108 break; 1109 case Type.DOUBLE: 1110 entries.add(opaque(InferredType.DOUBLE)); 1111 entries.add(opaque(InferredType.TOP)); 1112 break; 1113 case Type.ARRAY: 1114 case Type.OBJECT: 1115 entries.add(opaque(InferredType.create(argumentType.getDescriptor()))); 1116 break; 1117 default: 1118 throw new RuntimeException( 1119 "Unhandled argument type: " 1120 + argumentType 1121 + " in " 1122 + ownerClass 1123 + "." 1124 + methodName 1125 + methodDescriptor); 1126 } 1127 } 1128 return entries; 1129 } 1130 1131 private static ImmutableList<StackEntry> removeBackFromList( 1132 ImmutableList<StackEntry> list, int countToRemove) { 1133 int origSize = list.size(); 1134 int index = origSize - 1; 1135 1136 while (index >= 0 && countToRemove > 0) { 1137 InferredType type = list.get(index).type(); 1138 if (type.equals(InferredType.TOP) 1139 && index > 0 1140 && list.get(index - 1).type().isCategory2()) { 1141 --index; // A category 2 takes two slots. 1142 } 1143 --index; // Eat this local variable. 1144 --countToRemove; 1145 } 1146 checkState( 1147 countToRemove == 0, 1148 "countToRemove is %s but not 0. index=%s, list=%s", 1149 countToRemove, 1150 index, 1151 list); 1152 return list.subList(0, index + 1); 1153 } 1154 appendArrayToList( ImmutableList<StackEntry> list, int size, Object[] array)1155 private ImmutableList<StackEntry> appendArrayToList( 1156 ImmutableList<StackEntry> list, int size, Object[] array) { 1157 ImmutableList.Builder<StackEntry> builder = ImmutableList.builder(); 1158 builder.addAll(list); 1159 for (int i = 0; i < size; ++i) { 1160 InferredType type = convertTypeInStackMapFrame(array[i]); 1161 builder.add(opaque(type)); 1162 if (type.isCategory2()) { 1163 builder.add(opaque(InferredType.TOP)); 1164 } 1165 } 1166 return builder.build(); 1167 } 1168 1169 /** Convert the type in stack map frame to inference type. */ convertTypeInStackMapFrame(Object typeInStackMapFrame)1170 private InferredType convertTypeInStackMapFrame(Object typeInStackMapFrame) { 1171 if (typeInStackMapFrame == Opcodes.TOP) { 1172 return InferredType.TOP; 1173 } else if (typeInStackMapFrame == Opcodes.INTEGER) { 1174 return InferredType.INT; 1175 } else if (typeInStackMapFrame == Opcodes.FLOAT) { 1176 return InferredType.FLOAT; 1177 } else if (typeInStackMapFrame == Opcodes.DOUBLE) { 1178 return InferredType.DOUBLE; 1179 } else if (typeInStackMapFrame == Opcodes.LONG) { 1180 return InferredType.LONG; 1181 } else if (typeInStackMapFrame == Opcodes.NULL) { 1182 return InferredType.NULL; 1183 } else if (typeInStackMapFrame == Opcodes.UNINITIALIZED_THIS) { 1184 return InferredType.UNINITIALIZED_THIS; 1185 } else if (typeInStackMapFrame instanceof String) { 1186 String referenceTypeName = (String) typeInStackMapFrame; 1187 if (referenceTypeName.charAt(0) == '[') { 1188 return InferredType.create(referenceTypeName); 1189 } else { 1190 return InferredType.create('L' + referenceTypeName + ';'); 1191 } 1192 } else if (typeInStackMapFrame instanceof Label) { 1193 return InferredType.UNINITIALIZED; 1194 } else { 1195 throw new RuntimeException( 1196 "Cannot reach here. Unhandled element: value=" 1197 + typeInStackMapFrame 1198 + ", class=" 1199 + typeInStackMapFrame.getClass() 1200 + ". The current method being desugared is " 1201 + methodSignature); 1202 } 1203 } 1204 convertTypesInStackMapFrame(int size, Object[] array)1205 private ImmutableList<StackEntry> convertTypesInStackMapFrame(int size, Object[] array) { 1206 ImmutableList.Builder<StackEntry> builder = ImmutableList.builder(); 1207 for (int i = 0; i < size; ++i) { 1208 InferredType type = convertTypeInStackMapFrame(array[i]); 1209 builder.add(opaque(type)); 1210 if (type.isCategory2()) { 1211 builder.add(opaque(InferredType.TOP)); 1212 } 1213 } 1214 return builder.build(); 1215 } 1216 } 1217 1218 /** A value class to represent a frame. */ 1219 @AutoValue 1220 @CopyAnnotations 1221 @GwtIncompatible 1222 abstract static class FrameInfo { 1223 create(ImmutableList<StackEntry> locals, ImmutableList<StackEntry> stack)1224 static FrameInfo create(ImmutableList<StackEntry> locals, ImmutableList<StackEntry> stack) { 1225 return new AutoValue_ActualValueInference_FrameInfo(locals, stack); 1226 } 1227 locals()1228 abstract ImmutableList<StackEntry> locals(); 1229 stack()1230 abstract ImmutableList<StackEntry> stack(); 1231 } 1232 1233 /** A method invocation. */ 1234 @AutoValue 1235 @CopyAnnotations 1236 @GwtIncompatible 1237 abstract static class Invocation { builder(String name)1238 static Builder builder(String name) { 1239 return new AutoValue_ActualValueInference_Invocation.Builder().setName(name); 1240 } 1241 1242 /** The receiver of this call, if it is an instance call. */ 1243 @Nullable receiver()1244 abstract StackEntry receiver(); 1245 1246 /** The value being passed to this call if it is an {@code assertThat} or {@code that} call. */ 1247 @Nullable actualValue()1248 abstract StackEntry actualValue(); 1249 1250 /** 1251 * The value being passed to this call if it is a boxing call (e.g., {@code Integer.valueOf}). 1252 */ 1253 @Nullable boxingInput()1254 abstract StackEntry boxingInput(); 1255 name()1256 abstract String name(); 1257 deriveEntry(InferredType type, boolean hasParams)1258 final StackEntry deriveEntry(InferredType type, boolean hasParams) { 1259 if (boxingInput() != null && boxingInput().description() != null) { 1260 return described(type, boxingInput().description()); 1261 } else if (actualValue() != null) { 1262 return subjectFor(type, actualValue()); 1263 } else if (isOnSubjectInstance()) { 1264 return subjectFor(type, receiver().actualValue()); 1265 } else if (BORING_NAMES.contains(name())) { 1266 /* 1267 * TODO(cpovirk): For no-arg instance methods like get(), return "foo.get()," where "foo" is 1268 * the description we had for the receiver (if any). 1269 */ 1270 return opaque(type); 1271 } else { 1272 return described(type, name() + (hasParams ? "(...)" : "()")); 1273 } 1274 } 1275 isOnSubjectInstance()1276 final boolean isOnSubjectInstance() { 1277 return receiver() != null && receiver().isSubject(); 1278 } 1279 1280 @AutoValue.Builder 1281 @CanIgnoreReturnValue 1282 abstract static class Builder { setReceiver(StackEntry receiver)1283 abstract Builder setReceiver(StackEntry receiver); 1284 setActualValue(StackEntry actualValue)1285 abstract Builder setActualValue(StackEntry actualValue); 1286 setBoxingInput(StackEntry boxingInput)1287 abstract Builder setBoxingInput(StackEntry boxingInput); 1288 setName(String name)1289 abstract Builder setName(String name); 1290 1291 @CheckReturnValue build()1292 abstract Invocation build(); 1293 } 1294 } 1295 1296 /** This is the type used for type inference. */ 1297 @AutoValue 1298 @CopyAnnotations 1299 @GwtIncompatible 1300 abstract static class InferredType { 1301 1302 static final String UNINITIALIZED_PREFIX = "UNINIT@"; 1303 1304 static final InferredType BOOLEAN = new AutoValue_ActualValueInference_InferredType("Z"); 1305 static final InferredType BYTE = new AutoValue_ActualValueInference_InferredType("B"); 1306 static final InferredType INT = new AutoValue_ActualValueInference_InferredType("I"); 1307 static final InferredType FLOAT = new AutoValue_ActualValueInference_InferredType("F"); 1308 static final InferredType LONG = new AutoValue_ActualValueInference_InferredType("J"); 1309 static final InferredType DOUBLE = new AutoValue_ActualValueInference_InferredType("D"); 1310 /** Not a real value. */ 1311 static final InferredType TOP = new AutoValue_ActualValueInference_InferredType("TOP"); 1312 /** The value NULL */ 1313 static final InferredType NULL = new AutoValue_ActualValueInference_InferredType("NULL"); 1314 1315 static final InferredType UNINITIALIZED_THIS = 1316 new AutoValue_ActualValueInference_InferredType("UNINITIALIZED_THIS"); 1317 1318 static final InferredType UNINITIALIZED = 1319 new AutoValue_ActualValueInference_InferredType(UNINITIALIZED_PREFIX); 1320 1321 /** Create a type for a value. */ create(String descriptor)1322 static InferredType create(String descriptor) { 1323 if (UNINITIALIZED_PREFIX.equals(descriptor)) { 1324 return UNINITIALIZED; 1325 } 1326 char firstChar = descriptor.charAt(0); 1327 if (firstChar == 'L' || firstChar == '[') { 1328 // Reference, array. 1329 return new AutoValue_ActualValueInference_InferredType(descriptor); 1330 } 1331 switch (descriptor) { 1332 case "Z": 1333 return BOOLEAN; 1334 case "B": 1335 return BYTE; 1336 case "I": 1337 return INT; 1338 case "F": 1339 return FLOAT; 1340 case "J": 1341 return LONG; 1342 case "D": 1343 return DOUBLE; 1344 case "TOP": 1345 return TOP; 1346 case "NULL": 1347 return NULL; 1348 case "UNINITIALIZED_THIS": 1349 return UNINITIALIZED_THIS; 1350 default: 1351 throw new RuntimeException("Invalid descriptor: " + descriptor); 1352 } 1353 } 1354 descriptor()1355 abstract String descriptor(); 1356 1357 @Override toString()1358 public final String toString() { 1359 return descriptor(); 1360 } 1361 1362 /** Is a category 2 value? */ isCategory2()1363 boolean isCategory2() { 1364 String descriptor = descriptor(); 1365 return descriptor.equals("J") || descriptor.equals("D"); 1366 } 1367 1368 /** If the type is an array, return the element type. Otherwise, throw an exception. */ getElementTypeIfArrayOrThrow()1369 InferredType getElementTypeIfArrayOrThrow() { 1370 String descriptor = descriptor(); 1371 checkState(descriptor.charAt(0) == '[', "This type %s is not an array.", this); 1372 return create(descriptor.substring(1)); 1373 } 1374 1375 /** Is an uninitialized value? */ isUninitialized()1376 boolean isUninitialized() { 1377 return descriptor().startsWith(UNINITIALIZED_PREFIX); 1378 } 1379 } 1380 1381 private static final class InferenceClassVisitor extends ClassVisitor { 1382 /** 1383 * The method to visit. 1384 * 1385 * <p>We don't really <i>need</i> the method name: We could just visit the whole class, since we 1386 * look at data for only the relevant line. But it's nice not to process the whole class, 1387 * especially during debugging. (And it might also help avoid triggering any bugs in the 1388 * inference code.) 1389 */ 1390 private final String methodNameToVisit; 1391 1392 private final ImmutableSetMultimap.Builder<Integer, StackEntry> actualValueAtLine = 1393 ImmutableSetMultimap.builder(); 1394 // TODO(cpovirk): Can the class visitor pass the name in? 1395 private String className; 1396 InferenceClassVisitor(String methodNameToVisit)1397 InferenceClassVisitor(String methodNameToVisit) { 1398 super(Opcodes.ASM7); 1399 this.methodNameToVisit = methodNameToVisit; 1400 } 1401 1402 @Override visit( int version, int access, String name, String signature, String superName, String[] interfaces)1403 public void visit( 1404 int version, 1405 int access, 1406 String name, 1407 String signature, 1408 String superName, 1409 String[] interfaces) { 1410 className = name; 1411 } 1412 1413 @Override visitMethod( int access, String name, String desc, String signature, String[] exceptions)1414 public MethodVisitor visitMethod( 1415 int access, String name, String desc, String signature, String[] exceptions) { 1416 /* 1417 * Each InferenceMethodVisitor instance may be used only once. Still, it might seem like we 1418 * can get away with creating a single instance at construction time. However, we know only 1419 * the name of the method that we're visiting, not its full signature, so we may need to visit 1420 * multiple methods with that name, each with a fresh visitor. 1421 */ 1422 return methodNameToVisit.equals(name) 1423 ? new InferenceMethodVisitor(access, className, name, desc, actualValueAtLine) 1424 : null; 1425 } 1426 } 1427 1428 /* 1429 * TODO(cpovirk): Expand this, maybe based on data about the most common method calls passed to 1430 * assertThat(). 1431 */ 1432 private static final ImmutableSet<String> BORING_NAMES = 1433 ImmutableSet.of( 1434 "asList", 1435 "build", 1436 "collect", 1437 "copyOf", 1438 "create", 1439 "from", 1440 "get", 1441 "iterator", 1442 "of", 1443 "toArray", 1444 "toString", 1445 "valueOf"); 1446 isThatOrAssertThat(String owner, String name)1447 private static boolean isThatOrAssertThat(String owner, String name) { 1448 /* 1449 * TODO(cpovirk): Handle CustomSubjectBuilder. That requires looking at the type hierarchy, as 1450 * users always have an instance of a specific subtype. Also keep in mind that the that(...) 1451 * method might accept more than 1 parameter, like `that(className, methodName)` and/or that it 1452 * might have category-2 parameters. 1453 * 1454 * TODO(cpovirk): Handle custom assertThat methods. The challenges are similar. 1455 */ 1456 return (owner.equals("com/google/common/truth/Truth") && name.equals("assertThat")) 1457 || (owner.equals("com/google/common/truth/StandardSubjectBuilder") && name.equals("that")) 1458 || (owner.equals("com/google/common/truth/SimpleSubjectBuilder") && name.equals("that")); 1459 } 1460 isBoxing(String owner, String name, String desc)1461 private static boolean isBoxing(String owner, String name, String desc) { 1462 return name.equals("valueOf") 1463 && PRIMITIVE_WRAPPERS.contains(owner) 1464 /* 1465 * Don't handle valueOf(String s[, int radix]). The valueOf support is really here for 1466 * autoboxing, as in "assertThat(primitive)," not for 1467 * "assertThat(Integer.valueOf(...))." Not that there's anything really *wrong* with 1468 * handling manual boxing of primitives -- good thing, since we can't distinguish the two -- 1469 * but we're not interested in handling the valueOf methods that *parse*. That's mainly 1470 * because there's a type conversion, so some assertions might succeed on a string and fail 1471 * on the parsed number (or vice versa). 1472 */ 1473 && !Type.getArgumentTypes(desc)[0].equals(Type.getType(String.class)); 1474 } 1475 1476 private static final ImmutableSet<String> PRIMITIVE_WRAPPERS = 1477 ImmutableSet.of( 1478 "java/lang/Boolean", 1479 "java/lang/Byte", 1480 "java/lang/Character", 1481 "java/lang/Double", 1482 "java/lang/Float", 1483 "java/lang/Integer", 1484 "java/lang/Long", 1485 "java/lang/Short"); 1486 isStatic(int access)1487 private static boolean isStatic(int access) { 1488 return isSet(access, Opcodes.ACC_STATIC); 1489 } 1490 1491 /** 1492 * Returns {@code true} iff <b>all</b> bits in {@code bitmask} are set in {@code flags}. Trivially 1493 * returns {@code true} if {@code bitmask} is 0. 1494 */ isSet(int flags, int bitmask)1495 private static boolean isSet(int flags, int bitmask) { 1496 return (flags & bitmask) == bitmask; 1497 } 1498 closeQuietly(InputStream stream)1499 private static void closeQuietly(InputStream stream) { 1500 if (stream == null) { 1501 return; 1502 } 1503 try { 1504 stream.close(); 1505 } catch (IOException e) { 1506 // TODO(cpovirk): Log a warning? 1507 } 1508 } 1509 1510 /* 1511 * TODO(cpovirk): Switch to using Checker Framework @Nullable. The problem I see if I try to 1512 * switch now is that the AutoValue-generated code is `@Nullable ActualValueInference.StackEntry` 1513 * rather than `ActualValueInference.@Nullable StackEntry`. AutoValue normally gets this right 1514 * (b/29530042), so I think the failure here is because we use `-source 7`. That might still be 1515 * fine, except that j2objc compilation appears to then use `-source 8`. 1516 */ 1517 @interface Nullable {} 1518 ActualValueInference()1519 private ActualValueInference() {} 1520 } 1521