forked from typetools/checker-framework
/
AnnotatedTypeFactory.java
5586 lines (5129 loc) · 236 KB
/
AnnotatedTypeFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package org.checkerframework.framework.type;
// The imports from com.sun are all @jdk.Exported and therefore somewhat safe to use.
// Try to avoid using non-@jdk.Exported classes.
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Options;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Target;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import org.checkerframework.checker.interning.qual.FindDistinct;
import org.checkerframework.checker.interning.qual.InternedDistinct;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.common.reflection.DefaultReflectionResolver;
import org.checkerframework.common.reflection.MethodValAnnotatedTypeFactory;
import org.checkerframework.common.reflection.MethodValChecker;
import org.checkerframework.common.reflection.ReflectionResolver;
import org.checkerframework.common.reflection.qual.MethodVal;
import org.checkerframework.common.wholeprograminference.WholeProgramInference;
import org.checkerframework.common.wholeprograminference.WholeProgramInferenceImplementation;
import org.checkerframework.common.wholeprograminference.WholeProgramInferenceJavaParserStorage;
import org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesStorage;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.framework.qual.AnnotatedFor;
import org.checkerframework.framework.qual.EnsuresQualifier;
import org.checkerframework.framework.qual.EnsuresQualifierIf;
import org.checkerframework.framework.qual.FieldInvariant;
import org.checkerframework.framework.qual.FromStubFile;
import org.checkerframework.framework.qual.HasQualifierParameter;
import org.checkerframework.framework.qual.InheritedAnnotation;
import org.checkerframework.framework.qual.NoQualifierParameter;
import org.checkerframework.framework.qual.RequiresQualifier;
import org.checkerframework.framework.source.SourceChecker;
import org.checkerframework.framework.stub.AnnotationFileElementTypes;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.visitor.AnnotatedTypeCombiner;
import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.AnnotationFormatter;
import org.checkerframework.framework.util.AnnotationMirrorSet;
import org.checkerframework.framework.util.CheckerMain;
import org.checkerframework.framework.util.DefaultAnnotationFormatter;
import org.checkerframework.framework.util.FieldInvariants;
import org.checkerframework.framework.util.TreePathCacher;
import org.checkerframework.framework.util.typeinference.DefaultTypeArgumentInference;
import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
import org.checkerframework.framework.util.typeinference.TypeArgumentInference;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.CollectionUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeAnnotationUtils;
import org.checkerframework.javacutil.TypeKindUtils;
import org.checkerframework.javacutil.TypeSystemError;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.UserError;
import org.checkerframework.javacutil.trees.DetachedVarSymbol;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.ImmutableTypes;
import org.plumelib.util.StringsPlume;
import scenelib.annotations.el.AMethod;
import scenelib.annotations.el.ATypeElement;
/**
* The methods of this class take an element or AST node, and return the annotated type as an {@link
* AnnotatedTypeMirror}. The methods are:
*
* <ul>
* <li>{@link #getAnnotatedType(ClassTree)}
* <li>{@link #getAnnotatedType(MethodTree)}
* <li>{@link #getAnnotatedType(Tree)}
* <li>{@link #getAnnotatedTypeFromTypeTree(Tree)}
* <li>{@link #getAnnotatedType(TypeElement)}
* <li>{@link #getAnnotatedType(ExecutableElement)}
* <li>{@link #getAnnotatedType(Element)}
* </ul>
*
* This implementation only adds qualifiers explicitly specified by the programmer. Subclasses
* override {@link #addComputedTypeAnnotations} to add defaults, flow-sensitive refinement, and
* type-system-specific rules.
*
* <p>Unless otherwise indicated, each public method in this class returns a "fully annotated" type,
* which is one that has an annotation in all positions.
*
* <p>Type system checker writers may need to subclass this class, to add default qualifiers
* according to the type system semantics. Subclasses should especially override {@link
* #addComputedTypeAnnotations(Element, AnnotatedTypeMirror)} and {@link
* #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)} to handle default annotations. (Also,
* {@link #addDefaultAnnotations(AnnotatedTypeMirror)} adds annotations, but that method is a
* workaround for <a href="https://github.com/typetools/checker-framework/issues/979">Issue
* 979</a>.)
*
* @checker_framework.manual #creating-a-checker How to write a checker plug-in
*/
public class AnnotatedTypeFactory implements AnnotationProvider {
/** Whether to print verbose debugging messages about stub files. */
private final boolean debugStubParser;
/** The {@link Trees} instance to use for tree node path finding. */
protected final Trees trees;
/** Optional! The AST of the source file being operated on. */
// TODO: when should root be null? What are the use cases?
// None of the existing test checkers has a null root.
// Should not be modified between calls to "visit".
protected @Nullable CompilationUnitTree root;
/** The processing environment to use for accessing compiler internals. */
protected final ProcessingEnvironment processingEnv;
/** Utility class for working with {@link Element}s. */
protected final Elements elements;
/** Utility class for working with {@link TypeMirror}s. */
public final Types types;
/**
* A TreePath to the current tree that an external "visitor" is visiting. The visitor is either a
* subclass of {@link BaseTypeVisitor} or {@link
* org.checkerframework.framework.flow.CFAbstractTransfer}.
*/
private @Nullable TreePath visitorTreePath;
/** The AnnotatedFor.value argument/element. */
private final ExecutableElement annotatedForValueElement;
/** The EnsuresQualifier.expression field/element. */
final ExecutableElement ensuresQualifierExpressionElement;
/** The EnsuresQualifier.List.value field/element. */
final ExecutableElement ensuresQualifierListValueElement;
/** The EnsuresQualifierIf.expression field/element. */
final ExecutableElement ensuresQualifierIfExpressionElement;
/** The EnsuresQualifierIf.result argument/element. */
final ExecutableElement ensuresQualifierIfResultElement;
/** The EnsuresQualifierIf.List.value field/element. */
final ExecutableElement ensuresQualifierIfListValueElement;
/** The FieldInvariant.field argument/element. */
private final ExecutableElement fieldInvariantFieldElement;
/** The FieldInvariant.qualifier argument/element. */
private final ExecutableElement fieldInvariantQualifierElement;
/** The HasQualifierParameter.value field/element. */
private final ExecutableElement hasQualifierParameterValueElement;
/** The MethodVal.className argument/element. */
public final ExecutableElement methodValClassNameElement;
/** The MethodVal.methodName argument/element. */
public final ExecutableElement methodValMethodNameElement;
/** The MethodVal.params argument/element. */
public final ExecutableElement methodValParamsElement;
/** The NoQualifierParameter.value field/element. */
private final ExecutableElement noQualifierParameterValueElement;
/** The RequiresQualifier.expression field/element. */
final ExecutableElement requiresQualifierExpressionElement;
/** The RequiresQualifier.List.value field/element. */
final ExecutableElement requiresQualifierListValueElement;
/** The RequiresQualifier type. */
TypeMirror requiresQualifierTM;
/** The RequiresQualifier.List type. */
TypeMirror requiresQualifierListTM;
/** The EnsuresQualifier type. */
TypeMirror ensuresQualifierTM;
/** The EnsuresQualifier.List type. */
TypeMirror ensuresQualifierListTM;
/** The EnsuresQualifierIf type. */
TypeMirror ensuresQualifierIfTM;
/** The EnsuresQualifierIf.List type. */
TypeMirror ensuresQualifierIfListTM;
/**
* ===== postInit initialized fields ==== Note: qualHierarchy and typeHierarchy are both
* initialized in the postInit.
*
* @see #postInit() This means, they cannot be final and cannot be referred to in any subclass
* constructor or method until after postInit is called
*/
/** Represent the annotation relations. */
protected QualifierHierarchy qualHierarchy;
/** Represent the type relations. */
protected TypeHierarchy typeHierarchy;
/** Performs whole-program inference. If null, whole-program inference is disabled. */
private final @Nullable WholeProgramInference wholeProgramInference;
/**
* This formatter is used for converting AnnotatedTypeMirrors to Strings. This formatter will be
* used by all AnnotatedTypeMirrors created by this factory in their toString methods.
*/
protected final AnnotatedTypeFormatter typeFormatter;
/**
* Annotation formatter is used to format AnnotationMirrors. It is primarily used by SourceChecker
* when generating error messages.
*/
private final AnnotationFormatter annotationFormatter;
/** Holds the qualifier upper bounds for type uses. */
protected QualifierUpperBounds qualifierUpperBounds;
/**
* Provides utility method to substitute arguments for their type variables. Field should be
* final, but can only be set in postInit, because subtypes might need other state to be
* initialized first.
*/
protected TypeVariableSubstitutor typeVarSubstitutor;
/** Provides utility method to infer type arguments. */
protected TypeArgumentInference typeArgumentInference;
/**
* Caches the supported type qualifier classes. Call {@link #getSupportedTypeQualifiers()} instead
* of using this field directly, as it may not have been initialized.
*/
private final Set<Class<? extends Annotation>> supportedQuals;
/**
* Caches the fully-qualified names of the classes in {@link #supportedQuals}. Call {@link
* #getSupportedTypeQualifierNames()} instead of using this field directly, as it may not have
* been initialized.
*/
private final Set<@CanonicalName String> supportedQualNames;
/** Parses stub files and stores annotations on public elements from stub files. */
public final AnnotationFileElementTypes stubTypes;
/** Parses ajava files and stores annotations on public elements from ajava files. */
public final AnnotationFileElementTypes ajavaTypes;
/**
* If type checking a Java file, stores annotations read from an ajava file for that class if one
* exists. Unlike {@link #ajavaTypes}, which only stores annotations on public elements, this
* stores annotations on all element locations such as in anonymous class bodies.
*/
public @Nullable AnnotationFileElementTypes currentFileAjavaTypes;
/**
* A cache used to store elements whose declaration annotations have already been stored by
* calling the method {@link #getDeclAnnotations(Element)}.
*/
private final Map<Element, Set<AnnotationMirror>> cacheDeclAnnos;
/**
* A set containing declaration annotations that should be inherited. A declaration annotation
* will be inherited if it is in this set, or if it has the meta-annotation @InheritedAnnotation.
*/
private final Set<AnnotationMirror> inheritedAnnotations = AnnotationUtils.createAnnotationSet();
/** The checker to use for option handling and resource management. */
protected final BaseTypeChecker checker;
/** Map keys are canonical names of aliased annotations. */
private final Map<@FullyQualifiedName String, Alias> aliases = new HashMap<>();
/**
* Scans all parts of the {@link AnnotatedTypeMirror} so that all of its fields are initialized.
*/
private SimpleAnnotatedTypeScanner<Void, Void> atmInitializer =
new SimpleAnnotatedTypeScanner<>((type1, q) -> null);
/**
* Initializes all fields of {@code type}.
*
* @param type annotated type mirror
*/
public void initializeAtm(AnnotatedTypeMirror type) {
atmInitializer.visit(type);
}
/**
* Information about one annotation alias.
*
* <p>The information is either an AnotationMirror that can be used directly, or information for a
* builder (name and fields not to copy); see checkRep.
*/
private static class Alias {
/** The canonical annotation (or null if copyElements == true). */
AnnotationMirror canonical;
/** Whether elements should be copied over when translating to the canonical annotation. */
boolean copyElements;
/** The canonical annotation name (or null if copyElements == false). */
@CanonicalName String canonicalName;
/** Which elements should not be copied over (or null if copyElements == false). */
String[] ignorableElements;
/**
* Create an Alias with the given components.
*
* @param aliasName the alias name; only used for debugging
* @param canonical the canonical annotation
* @param copyElements whether elements should be copied over when translating to the canonical
* annotation
* @param canonicalName the canonical annotation name (or null if copyElements == false)
* @param ignorableElements elements that should not be copied over
*/
Alias(
String aliasName,
AnnotationMirror canonical,
boolean copyElements,
@CanonicalName String canonicalName,
String[] ignorableElements) {
this.canonical = canonical;
this.copyElements = copyElements;
this.canonicalName = canonicalName;
this.ignorableElements = ignorableElements;
checkRep(aliasName);
}
/**
* Throw an exception if this object is malformed.
*
* @param aliasName the alias name; only used for diagnostic messages
*/
void checkRep(String aliasName) {
if (copyElements) {
if (!(canonical == null && canonicalName != null && ignorableElements != null)) {
throw new BugInCF(
"Bad Alias for %s: [canonical=%s] copyElements=%s canonicalName=%s"
+ " ignorableElements=%s",
aliasName, canonical, copyElements, canonicalName, ignorableElements);
}
} else {
if (!(canonical != null && canonicalName == null && ignorableElements == null)) {
throw new BugInCF(
"Bad Alias for %s: canonical=%s copyElements=%s [canonicalName=%s"
+ " ignorableElements=%s]",
aliasName, canonical, copyElements, canonicalName, ignorableElements);
}
}
}
}
/**
* A map from the class of an annotation to the set of classes for annotations with the same
* meaning, as well as the annotation mirror that should be used.
*/
private final Map<
Class<? extends Annotation>, Pair<AnnotationMirror, Set<Class<? extends Annotation>>>>
declAliases = new HashMap<>();
/** Unique ID counter; for debugging purposes. */
private static int uidCounter = 0;
/** Unique ID of the current object; for debugging purposes. */
public final int uid;
/**
* Object that is used to resolve reflective method calls, if reflection resolution is turned on.
*/
protected ReflectionResolver reflectionResolver;
/** This loads type annotation classes via reflective lookup. */
protected AnnotationClassLoader loader;
/**
* Which whole-program inference output format to use, if doing whole-program inference. This
* variable would be final, but it is not set unless WPI is enabled.
*/
public WholeProgramInference.OutputFormat wpiOutputFormat;
/**
* Should results be cached? This means that ATM.deepCopy() will be called. ATM.deepCopy() used to
* (and perhaps still does) side effect the ATM being copied. So setting this to false is not
* equivalent to setting shouldReadCache to false.
*/
public boolean shouldCache;
/** Size of LRU cache if one isn't specified using the atfCacheSize option. */
private static final int DEFAULT_CACHE_SIZE = 300;
/** Mapping from a Tree to its annotated type; defaults have been applied. */
private final Map<Tree, AnnotatedTypeMirror> classAndMethodTreeCache;
/**
* Mapping from an expression tree to its annotated type; before defaults are applied, just what
* the programmer wrote.
*/
protected final Map<Tree, AnnotatedTypeMirror> fromExpressionTreeCache;
/**
* Mapping from a member tree to its annotated type; before defaults are applied, just what the
* programmer wrote.
*/
protected final Map<Tree, AnnotatedTypeMirror> fromMemberTreeCache;
/**
* Mapping from a type tree to its annotated type; before defaults are applied, just what the
* programmer wrote.
*/
protected final Map<Tree, AnnotatedTypeMirror> fromTypeTreeCache;
/**
* Mapping from an Element to its annotated type; before defaults are applied, just what the
* programmer wrote.
*/
private final Map<Element, AnnotatedTypeMirror> elementCache;
/** Mapping from an Element to the source Tree of the declaration. */
private final Map<Element, Tree> elementToTreeCache;
/** Mapping from a Tree to its TreePath. Shared between all instances. */
private final TreePathCacher treePathCache;
/** Mapping from CFG-generated trees to their enclosing elements. */
protected final Map<Tree, Element> artificialTreeToEnclosingElementMap;
/**
* Whether to ignore uninferred type arguments. This is a temporary flag to work around Issue 979.
*/
public final boolean ignoreUninferredTypeArguments;
/** The Object.getClass method. */
protected final ExecutableElement objectGetClass;
/** Size of the annotationClassNames cache. */
private static final int ANNOTATION_CACHE_SIZE = 500;
/** Maps classes representing AnnotationMirrors to their canonical names. */
private final Map<Class<? extends Annotation>, @CanonicalName String> annotationClassNames;
/** An annotated type of the declaration of {@link Iterable} without any annotations. */
private AnnotatedDeclaredType iterableDeclType;
/**
* Constructs a factory from the given {@link ProcessingEnvironment} instance and syntax tree
* root. (These parameters are required so that the factory may conduct the appropriate
* annotation-gathering analyses on certain tree types.)
*
* <p>Root can be {@code null} if the factory does not operate on trees.
*
* <p>A subclass must call postInit at the end of its constructor. postInit must be the last call
* in the constructor or else types from stub files may not be created as expected.
*
* @param checker the {@link SourceChecker} to which this factory belongs
* @throws IllegalArgumentException if either argument is {@code null}
*/
public AnnotatedTypeFactory(BaseTypeChecker checker) {
uid = ++uidCounter;
this.processingEnv = checker.getProcessingEnvironment();
// this.root = root;
this.checker = checker;
this.trees = Trees.instance(processingEnv);
this.elements = processingEnv.getElementUtils();
this.types = processingEnv.getTypeUtils();
this.supportedQuals = new HashSet<>();
this.supportedQualNames = new HashSet<>();
this.stubTypes = new AnnotationFileElementTypes(this);
this.ajavaTypes = new AnnotationFileElementTypes(this);
this.currentFileAjavaTypes = null;
this.cacheDeclAnnos = new HashMap<>();
this.artificialTreeToEnclosingElementMap = new HashMap<>();
// get the shared instance from the checker
this.treePathCache = checker.getTreePathCacher();
this.shouldCache = !checker.hasOption("atfDoNotCache");
if (shouldCache) {
int cacheSize = getCacheSize();
this.classAndMethodTreeCache = CollectionUtils.createLRUCache(cacheSize);
this.fromExpressionTreeCache = CollectionUtils.createLRUCache(cacheSize);
this.fromMemberTreeCache = CollectionUtils.createLRUCache(cacheSize);
this.fromTypeTreeCache = CollectionUtils.createLRUCache(cacheSize);
this.elementCache = CollectionUtils.createLRUCache(cacheSize);
this.elementToTreeCache = CollectionUtils.createLRUCache(cacheSize);
this.annotationClassNames =
Collections.synchronizedMap(CollectionUtils.createLRUCache(ANNOTATION_CACHE_SIZE));
} else {
this.classAndMethodTreeCache = null;
this.fromExpressionTreeCache = null;
this.fromMemberTreeCache = null;
this.fromTypeTreeCache = null;
this.elementCache = null;
this.elementToTreeCache = null;
this.annotationClassNames = null;
}
this.typeFormatter = createAnnotatedTypeFormatter();
this.annotationFormatter = createAnnotationFormatter();
if (checker.hasOption("infer")) {
checkInvalidOptionsInferSignatures();
String inferArg = checker.getOption("infer");
// No argument means "jaifs", for (temporary) backwards compatibility.
if (inferArg == null) {
inferArg = "jaifs";
}
switch (inferArg) {
case "stubs":
wpiOutputFormat = WholeProgramInference.OutputFormat.STUB;
break;
case "jaifs":
wpiOutputFormat = WholeProgramInference.OutputFormat.JAIF;
break;
case "ajava":
wpiOutputFormat = WholeProgramInference.OutputFormat.AJAVA;
break;
default:
throw new UserError(
"Bad argument -Ainfer="
+ inferArg
+ " should be one of: -Ainfer=jaifs, -Ainfer=stubs, -Ainfer=ajava");
}
boolean showWpiFailedInferences = checker.hasOption("showWpiFailedInferences");
boolean inferOutputOriginal = checker.hasOption("inferOutputOriginal");
if (inferOutputOriginal && wpiOutputFormat != WholeProgramInference.OutputFormat.AJAVA) {
checker.message(
Diagnostic.Kind.WARNING,
"-AinferOutputOriginal only works with -Ainfer=ajava, so it is being ignored.");
}
if (wpiOutputFormat == WholeProgramInference.OutputFormat.AJAVA) {
wholeProgramInference =
new WholeProgramInferenceImplementation<AnnotatedTypeMirror>(
this,
new WholeProgramInferenceJavaParserStorage(this, inferOutputOriginal),
showWpiFailedInferences);
} else {
wholeProgramInference =
new WholeProgramInferenceImplementation<ATypeElement>(
this, new WholeProgramInferenceScenesStorage(this), showWpiFailedInferences);
}
if (!checker.hasOption("warns")) {
// Without -Awarns, the inference output may be incomplete, because javac halts
// after issuing an error.
checker.message(Diagnostic.Kind.ERROR, "Do not supply -Ainfer without -Awarns");
}
} else {
wholeProgramInference = null;
}
ignoreUninferredTypeArguments = !checker.hasOption("conservativeUninferredTypeArguments");
objectGetClass = TreeUtils.getMethod("java.lang.Object", "getClass", 0, processingEnv);
this.debugStubParser = checker.hasOption("stubDebug");
annotatedForValueElement = TreeUtils.getMethod(AnnotatedFor.class, "value", 0, processingEnv);
ensuresQualifierExpressionElement =
TreeUtils.getMethod(EnsuresQualifier.class, "expression", 0, processingEnv);
ensuresQualifierListValueElement =
TreeUtils.getMethod(EnsuresQualifier.List.class, "value", 0, processingEnv);
ensuresQualifierIfExpressionElement =
TreeUtils.getMethod(EnsuresQualifierIf.class, "expression", 0, processingEnv);
ensuresQualifierIfResultElement =
TreeUtils.getMethod(EnsuresQualifierIf.class, "result", 0, processingEnv);
ensuresQualifierIfListValueElement =
TreeUtils.getMethod(EnsuresQualifierIf.List.class, "value", 0, processingEnv);
fieldInvariantFieldElement =
TreeUtils.getMethod(FieldInvariant.class, "field", 0, processingEnv);
fieldInvariantQualifierElement =
TreeUtils.getMethod(FieldInvariant.class, "qualifier", 0, processingEnv);
hasQualifierParameterValueElement =
TreeUtils.getMethod(HasQualifierParameter.class, "value", 0, processingEnv);
methodValClassNameElement = TreeUtils.getMethod(MethodVal.class, "className", 0, processingEnv);
methodValMethodNameElement =
TreeUtils.getMethod(MethodVal.class, "methodName", 0, processingEnv);
methodValParamsElement = TreeUtils.getMethod(MethodVal.class, "params", 0, processingEnv);
noQualifierParameterValueElement =
TreeUtils.getMethod(NoQualifierParameter.class, "value", 0, processingEnv);
requiresQualifierExpressionElement =
TreeUtils.getMethod(RequiresQualifier.class, "expression", 0, processingEnv);
requiresQualifierListValueElement =
TreeUtils.getMethod(RequiresQualifier.List.class, "value", 0, processingEnv);
requiresQualifierTM =
ElementUtils.getTypeElement(processingEnv, RequiresQualifier.class).asType();
requiresQualifierListTM =
ElementUtils.getTypeElement(processingEnv, RequiresQualifier.List.class).asType();
ensuresQualifierTM =
ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.class).asType();
ensuresQualifierListTM =
ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.List.class).asType();
ensuresQualifierIfTM =
ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.class).asType();
ensuresQualifierIfListTM =
ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.List.class).asType();
}
/**
* @throws BugInCF If supportedQuals is empty or if any of the support qualifiers has a @Target
* meta-annotation that contain something besides TYPE_USE or TYPE_PARAMETER. (@Target({}) is
* allowed.)
*/
private void checkSupportedQuals() {
if (supportedQuals.isEmpty()) {
throw new TypeSystemError("Found no supported qualifiers.");
}
for (Class<? extends Annotation> annotationClass : supportedQuals) {
// Check @Target values
ElementType[] targetValues = annotationClass.getAnnotation(Target.class).value();
List<ElementType> badTargetValues = new ArrayList<>();
for (ElementType element : targetValues) {
if (!(element == ElementType.TYPE_USE || element == ElementType.TYPE_PARAMETER)) {
// if there's an ElementType with an enumerated value of something other
// than TYPE_USE or TYPE_PARAMETER then it isn't a valid qualifier
badTargetValues.add(element);
}
}
if (!badTargetValues.isEmpty()) {
String msg =
"The @Target meta-annotation on type qualifier "
+ annotationClass.toString()
+ " must not contain "
+ StringsPlume.conjunction("or", badTargetValues)
+ ".";
throw new TypeSystemError(msg);
}
}
}
/**
* This method is called only when {@code -Ainfer} is passed as an option. It checks if another
* option that should not occur simultaneously with the whole-program inference is also passed as
* argument, and aborts the process if that is the case. For example, the whole-program inference
* process was not designed to work with conservative defaults.
*
* <p>Subclasses may override this method to add more options.
*/
protected void checkInvalidOptionsInferSignatures() {
// See Issue 683
// https://github.com/typetools/checker-framework/issues/683
if (checker.useConservativeDefault("source") || checker.useConservativeDefault("bytecode")) {
throw new UserError(
"The option -Ainfer=... cannot be used together with conservative defaults.");
}
}
/**
* Actions that logically belong in the constructor, but need to run after the subclass
* constructor has completed. In particular, {@link AnnotationFileElementTypes#parseStubFiles()}
* may try to do type resolution with this AnnotatedTypeFactory.
*/
protected void postInit() {
this.qualHierarchy = createQualifierHierarchy();
if (qualHierarchy == null) {
throw new TypeSystemError(
"AnnotatedTypeFactory with null qualifier hierarchy not supported.");
} else if (!qualHierarchy.isValid()) {
throw new TypeSystemError(
"AnnotatedTypeFactory: invalid qualifier hierarchy: %s %s ",
qualHierarchy.getClass(), qualHierarchy);
}
this.typeHierarchy = createTypeHierarchy();
this.typeVarSubstitutor = createTypeVariableSubstitutor();
this.typeArgumentInference = createTypeArgumentInference();
this.qualifierUpperBounds = createQualifierUpperBounds();
// TODO: is this the best location for declaring this alias?
addAliasedDeclAnnotation(
org.jmlspecs.annotation.Pure.class,
org.checkerframework.dataflow.qual.Pure.class,
AnnotationBuilder.fromClass(elements, org.checkerframework.dataflow.qual.Pure.class));
// Accommodate the inability to write @InheritedAnnotation on these annotations.
addInheritedAnnotation(
AnnotationBuilder.fromClass(elements, org.checkerframework.dataflow.qual.Pure.class));
addInheritedAnnotation(
AnnotationBuilder.fromClass(
elements, org.checkerframework.dataflow.qual.SideEffectFree.class));
addInheritedAnnotation(
AnnotationBuilder.fromClass(
elements, org.checkerframework.dataflow.qual.Deterministic.class));
addInheritedAnnotation(
AnnotationBuilder.fromClass(
elements, org.checkerframework.dataflow.qual.TerminatesExecution.class));
initializeReflectionResolution();
if (this.getClass() == AnnotatedTypeFactory.class) {
this.parseAnnotationFiles();
}
TypeMirror iterableTypeMirror =
ElementUtils.getTypeElement(processingEnv, Iterable.class).asType();
this.iterableDeclType =
(AnnotatedDeclaredType) AnnotatedTypeMirror.createType(iterableTypeMirror, this, true);
}
/**
* Returns the checker associated with this factory.
*
* @return the checker associated with this factory
*/
public BaseTypeChecker getChecker() {
return checker;
}
/**
* Returns the names of the annotation processors that are being run.
*
* @return the names of the annotation processors that are being run
*/
@SuppressWarnings("JdkObsolete") // ClassLoader.getResources returns an Enumeration
public String[] getCheckerNames() {
com.sun.tools.javac.util.Context context =
((JavacProcessingEnvironment) processingEnv).getContext();
String processorArg = Options.instance(context).get("-processor");
if (processorArg != null) {
return processorArg.split(",");
}
try {
String filename = "META-INF/services/javax.annotation.processing.Processor";
List<String> lines = new ArrayList<>();
Enumeration<URL> urls = getClass().getClassLoader().getResources(filename);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()))) {
lines.addAll(in.lines().collect(Collectors.toList()));
}
}
String[] result = lines.toArray(new String[0]);
return result;
} catch (IOException e) {
throw new BugInCF(e);
}
}
/**
* Creates {@link QualifierUpperBounds} for this type factory.
*
* @return a new {@link QualifierUpperBounds} for this type factory
*/
protected QualifierUpperBounds createQualifierUpperBounds() {
return new QualifierUpperBounds(this);
}
/**
* Return {@link QualifierUpperBounds} for this type factory.
*
* @return {@link QualifierUpperBounds} for this type factory
*/
public QualifierUpperBounds getQualifierUpperBounds() {
return qualifierUpperBounds;
}
/**
* Returns the WholeProgramInference instance (may be null).
*
* @return the WholeProgramInference instance, or null
*/
public WholeProgramInference getWholeProgramInference() {
return wholeProgramInference;
}
protected void initializeReflectionResolution() {
if (checker.shouldResolveReflection()) {
boolean debug = "debug".equals(checker.getOption("resolveReflection"));
MethodValChecker methodValChecker = checker.getSubchecker(MethodValChecker.class);
assert methodValChecker != null
: "AnnotatedTypeFactory: reflection resolution was requested, but MethodValChecker isn't"
+ " a subchecker.";
MethodValAnnotatedTypeFactory methodValATF =
(MethodValAnnotatedTypeFactory) methodValChecker.getAnnotationProvider();
reflectionResolver = new DefaultReflectionResolver(checker, methodValATF, debug);
}
}
/**
* Set the CompilationUnitTree that should be used.
*
* @param root the new compilation unit to use
*/
public void setRoot(@Nullable CompilationUnitTree root) {
if (root != null && wholeProgramInference != null) {
for (Tree typeDecl : root.getTypeDecls()) {
if (typeDecl.getKind() == Tree.Kind.CLASS) {
ClassTree classTree = (ClassTree) typeDecl;
wholeProgramInference.preprocessClassTree(classTree);
}
}
}
this.root = root;
// Do not clear here. Only the primary checker should clear this cache.
// treePathCache.clear();
// setRoot in a GenericAnnotatedTypeFactory will clear this;
// if this isn't a GenericATF, then it must clear it itself.
if (!(this instanceof GenericAnnotatedTypeFactory)) {
artificialTreeToEnclosingElementMap.clear();
}
if (shouldCache) {
// Clear the caches with trees because once the compilation unit changes,
// the trees may be modified and lose type arguments.
elementToTreeCache.clear();
fromExpressionTreeCache.clear();
fromMemberTreeCache.clear();
fromTypeTreeCache.clear();
classAndMethodTreeCache.clear();
// There is no need to clear the following cache, it is limited by cache size and it
// contents won't change between compilation units.
// elementCache.clear();
}
if (root != null && checker.hasOption("ajava")) {
// Search for an ajava file with annotations for the current source file and the current
// checker. It will be in a directory specified by the "ajava" option in a subdirectory
// corresponding to this file's package. For example, a file in package a.b would be in a
// subdirectory a/b. The filename is ClassName-checker.qualified.name.ajava. If such a file
// exists, read its detailed annotation data, including annotations on private elements.
String packagePrefix =
root.getPackageName() != null
? TreeUtils.nameExpressionToString(root.getPackageName()) + "."
: "";
// The method getName() returns a path.
String className = root.getSourceFile().getName();
// Extract the basename.
int lastSeparator = className.lastIndexOf(File.separator);
if (lastSeparator != -1) {
className = className.substring(lastSeparator + 1);
}
// Drop the ".java" extension.
if (className.endsWith(".java")) {
className = className.substring(0, className.length() - ".java".length());
}
String qualifiedName = packagePrefix + className;
// If the set candidateAjavaFiles has exactly one element after the loop, a specific .ajava
// file was supplied, with no ambiguity, and can be parsed. For an explanation, see the
// comment below about possible ambiguity.
Set<String> candidateAjavaFiles = new HashSet<>(1);
// All .ajava files for this class + checker combo end in this string.
String ajavaEnding =
qualifiedName.replaceAll("\\.", "/")
+ "-"
+ checker.getClass().getCanonicalName()
+ ".ajava";
for (String ajavaLocation : checker.getOption("ajava").split(File.pathSeparator)) {
// ajavaLocation might either be (1) a directory, or (2) the name of a specific
// ajava file. This code must handle both possible cases.
// Case (1): ajavaPath is a directory
String ajavaPath = ajavaLocation + File.separator + ajavaEnding;
File ajavaFileInDir = new File(ajavaPath);
if (ajavaFileInDir.exists()) {
// There is a candidate ajava file in one of the root directories.
candidateAjavaFiles.add(ajavaPath);
} else {
// Check case (2): ajavaPath might be a specific .ajava file. The tricky thing about this
// is that the "root" is not known, so the correct .ajava file might be
// ambiguous. Consider the following: there are two ajava files:
// ~/foo/foo/Bar-checker.ajava and ~/baz/foo/Bar-checker.ajava. Which is the correct one
// for class foo.Bar? It depends on whether there is a foo.foo.Bar or a baz.foo.Bar
// elsewhere in the project. For that reason, parsing using a specific file is done at the
// **end** of the loop, and if there is more than one match no file is parsed for this
// class and a warning is issued instead. The user can disambiguate by supplying a root
// directory, instead of specific files.
if (ajavaLocation.endsWith(File.separator + ajavaEnding)) {
// This is a candidate ajava file. If it is the only candidate, then it might be
// unambiguous. If not, issue a warning.
candidateAjavaFiles.add(ajavaLocation);
}
}
}
if (candidateAjavaFiles.size() == 1) {
currentFileAjavaTypes = new AnnotationFileElementTypes(this);
currentFileAjavaTypes.parseAjavaFileWithTree(
candidateAjavaFiles.toArray(new String[1])[0], root);
} else if (candidateAjavaFiles.size() > 1) {
checker.reportWarning(root, "ambiguous.ajava", String.join(", ", candidateAjavaFiles));
}
} else {
currentFileAjavaTypes = null;
}
}
@SideEffectFree
@Override
public String toString() {
return getClass().getSimpleName() + "#" + uid;
}
/**
* Returns the {@link QualifierHierarchy} to be used by this checker.
*
* <p>The implementation builds the type qualifier hierarchy for the {@link
* #getSupportedTypeQualifiers()} using the meta-annotations found in them. The current
* implementation returns an instance of {@code NoElementQualifierHierarchy}.
*
* <p>Subclasses must override this method if their qualifiers have elements; the method must
* return an implementation of {@link QualifierHierarchy}, such as {@link
* ElementQualifierHierarchy}.
*
* @return a QualifierHierarchy for this type system
*/
protected QualifierHierarchy createQualifierHierarchy() {
return new NoElementQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
}
/**
* Returns the type qualifier hierarchy graph to be used by this processor.
*
* @see #createQualifierHierarchy()
* @return the {@link QualifierHierarchy} for this checker
*/
public final QualifierHierarchy getQualifierHierarchy() {
return qualHierarchy;
}
/**
* Creates the type hierarchy to be used by this factory.
*
* <p>Subclasses may override this method to specify new type-checking rules beyond the typical
* Java subtyping rules.
*
* @return the type relations class to check type subtyping
*/
protected TypeHierarchy createTypeHierarchy() {
return new DefaultTypeHierarchy(
checker,
getQualifierHierarchy(),
checker.getBooleanOption("ignoreRawTypeArguments", true),
checker.hasOption("invariantArrays"));
}
public final TypeHierarchy getTypeHierarchy() {
return typeHierarchy;