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