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