• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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