-
Notifications
You must be signed in to change notification settings - Fork 61
/
Copy pathCalledOnceCheck.cpp
1705 lines (1492 loc) · 65.1 KB
/
CalledOnceCheck.cpp
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
//===- CalledOnceCheck.cpp - Check 'called once' parameters ---------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/Analysis/Analyses/CalledOnceCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/ParentMap.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtObjC.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/Type.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Analysis/CFG.h"
#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/ADT/BitmaskEnum.h"
#include "llvm/ADT/PointerIntPair.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/ErrorHandling.h"
#include <memory>
#include <optional>
using namespace clang;
namespace {
static constexpr unsigned EXPECTED_MAX_NUMBER_OF_PARAMS = 2;
template <class T>
using ParamSizedVector = llvm::SmallVector<T, EXPECTED_MAX_NUMBER_OF_PARAMS>;
static constexpr unsigned EXPECTED_NUMBER_OF_BASIC_BLOCKS = 8;
template <class T>
using CFGSizedVector = llvm::SmallVector<T, EXPECTED_NUMBER_OF_BASIC_BLOCKS>;
constexpr llvm::StringLiteral CONVENTIONAL_NAMES[] = {
"completionHandler", "completion", "withCompletionHandler",
"withCompletion", "completionBlock", "withCompletionBlock",
"replyTo", "reply", "withReplyTo"};
constexpr llvm::StringLiteral CONVENTIONAL_SUFFIXES[] = {
"WithCompletionHandler", "WithCompletion", "WithCompletionBlock",
"WithReplyTo", "WithReply"};
constexpr llvm::StringLiteral CONVENTIONAL_CONDITIONS[] = {
"error", "cancel", "shouldCall", "done", "OK", "success"};
struct KnownCalledOnceParameter {
llvm::StringLiteral FunctionName;
unsigned ParamIndex;
};
constexpr KnownCalledOnceParameter KNOWN_CALLED_ONCE_PARAMETERS[] = {
{llvm::StringLiteral{"dispatch_async"}, 1},
{llvm::StringLiteral{"dispatch_async_and_wait"}, 1},
{llvm::StringLiteral{"dispatch_after"}, 2},
{llvm::StringLiteral{"dispatch_sync"}, 1},
{llvm::StringLiteral{"dispatch_once"}, 1},
{llvm::StringLiteral{"dispatch_barrier_async"}, 1},
{llvm::StringLiteral{"dispatch_barrier_async_and_wait"}, 1},
{llvm::StringLiteral{"dispatch_barrier_sync"}, 1}};
class ParameterStatus {
public:
// Status kind is basically the main part of parameter's status.
// The kind represents our knowledge (so far) about a tracked parameter
// in the context of this analysis.
//
// Since we want to report on missing and extraneous calls, we need to
// track the fact whether paramater was called or not. This automatically
// decides two kinds: `NotCalled` and `Called`.
//
// One of the erroneous situations is the case when parameter is called only
// on some of the paths. We could've considered it `NotCalled`, but we want
// to report double call warnings even if these two calls are not guaranteed
// to happen in every execution. We also don't want to have it as `Called`
// because not calling tracked parameter on all of the paths is an error
// on its own. For these reasons, we need to have a separate kind,
// `MaybeCalled`, and change `Called` to `DefinitelyCalled` to avoid
// confusion.
//
// Two violations of calling parameter more than once and not calling it on
// every path are not, however, mutually exclusive. In situations where both
// violations take place, we prefer to report ONLY double call. It's always
// harder to pinpoint a bug that has arisen when a user neglects to take the
// right action (and therefore, no action is taken), than when a user takes
// the wrong action. And, in order to remember that we already reported
// a double call, we need another kind: `Reported`.
//
// Our analysis is intra-procedural and, while in the perfect world,
// developers only use tracked parameters to call them, in the real world,
// the picture might be different. Parameters can be stored in global
// variables or leaked into other functions that we know nothing about.
// We try to be lenient and trust users. Another kind `Escaped` reflects
// such situations. We don't know if it gets called there or not, but we
// should always think of `Escaped` as the best possible option.
//
// Some of the paths in the analyzed functions might end with a call
// to noreturn functions. Such paths are not required to have parameter
// calls and we want to track that. For the purposes of better diagnostics,
// we don't want to reuse `Escaped` and, thus, have another kind `NoReturn`.
//
// Additionally, we have `NotVisited` kind that tells us nothing about
// a tracked parameter, but is used for tracking analyzed (aka visited)
// basic blocks.
//
// If we consider `|` to be a JOIN operation of two kinds coming from
// two different paths, the following properties must hold:
//
// 1. for any Kind K: K | K == K
// Joining two identical kinds should result in the same kind.
//
// 2. for any Kind K: Reported | K == Reported
// Doesn't matter on which path it was reported, it still is.
//
// 3. for any Kind K: NoReturn | K == K
// We can totally ignore noreturn paths during merges.
//
// 4. DefinitelyCalled | NotCalled == MaybeCalled
// Called on one path, not called on another - that's simply
// a definition for MaybeCalled.
//
// 5. for any Kind K in [DefinitelyCalled, NotCalled, MaybeCalled]:
// Escaped | K == K
// Escaped mirrors other statuses after joins.
// Every situation, when we join any of the listed kinds K,
// is a violation. For this reason, in order to assume the
// best outcome for this escape, we consider it to be the
// same as the other path.
//
// 6. for any Kind K in [DefinitelyCalled, NotCalled]:
// MaybeCalled | K == MaybeCalled
// MaybeCalled should basically stay after almost every join.
enum Kind {
// No-return paths should be absolutely transparent for the analysis.
// 0x0 is the identity element for selected join operation (binary or).
NoReturn = 0x0, /* 0000 */
// Escaped marks situations when marked parameter escaped into
// another function (so we can assume that it was possibly called there).
Escaped = 0x1, /* 0001 */
// Parameter was definitely called once at this point.
DefinitelyCalled = 0x3, /* 0011 */
// Kinds less or equal to NON_ERROR_STATUS are not considered errors.
NON_ERROR_STATUS = DefinitelyCalled,
// Parameter was not yet called.
NotCalled = 0x5, /* 0101 */
// Parameter was not called at least on one path leading to this point,
// while there is also at least one path that it gets called.
MaybeCalled = 0x7, /* 0111 */
// Parameter was not yet analyzed.
NotVisited = 0x8, /* 1000 */
// We already reported a violation and stopped tracking calls for this
// parameter.
Reported = 0x15, /* 1111 */
LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ Reported)
};
constexpr ParameterStatus() = default;
/* implicit */ ParameterStatus(Kind K) : StatusKind(K) {
assert(!seenAnyCalls(K) && "Can't initialize status without a call");
}
ParameterStatus(Kind K, const Expr *Call) : StatusKind(K), Call(Call) {
assert(seenAnyCalls(K) && "This kind is not supposed to have a call");
}
const Expr &getCall() const {
assert(seenAnyCalls(getKind()) && "ParameterStatus doesn't have a call");
return *Call;
}
static bool seenAnyCalls(Kind K) {
return (K & DefinitelyCalled) == DefinitelyCalled && K != Reported;
}
bool seenAnyCalls() const { return seenAnyCalls(getKind()); }
static bool isErrorStatus(Kind K) { return K > NON_ERROR_STATUS; }
bool isErrorStatus() const { return isErrorStatus(getKind()); }
Kind getKind() const { return StatusKind; }
void join(const ParameterStatus &Other) {
// If we have a pointer already, let's keep it.
// For the purposes of the analysis, it doesn't really matter
// which call we report.
//
// If we don't have a pointer, let's take whatever gets joined.
if (!Call) {
Call = Other.Call;
}
// Join kinds.
StatusKind |= Other.getKind();
}
bool operator==(const ParameterStatus &Other) const {
// We compare only kinds, pointers on their own is only additional
// information.
return getKind() == Other.getKind();
}
private:
// It would've been a perfect place to use llvm::PointerIntPair, but
// unfortunately NumLowBitsAvailable for clang::Expr had been reduced to 2.
Kind StatusKind = NotVisited;
const Expr *Call = nullptr;
};
/// State aggregates statuses of all tracked parameters.
class State {
public:
State(unsigned Size, ParameterStatus::Kind K = ParameterStatus::NotVisited)
: ParamData(Size, K) {}
/// Return status of a parameter with the given index.
/// \{
ParameterStatus &getStatusFor(unsigned Index) { return ParamData[Index]; }
const ParameterStatus &getStatusFor(unsigned Index) const {
return ParamData[Index];
}
/// \}
/// Return true if parameter with the given index can be called.
bool seenAnyCalls(unsigned Index) const {
return getStatusFor(Index).seenAnyCalls();
}
/// Return a reference that we consider a call.
///
/// Should only be used for parameters that can be called.
const Expr &getCallFor(unsigned Index) const {
return getStatusFor(Index).getCall();
}
/// Return status kind of parameter with the given index.
ParameterStatus::Kind getKindFor(unsigned Index) const {
return getStatusFor(Index).getKind();
}
bool isVisited() const {
return llvm::all_of(ParamData, [](const ParameterStatus &S) {
return S.getKind() != ParameterStatus::NotVisited;
});
}
// Join other state into the current state.
void join(const State &Other) {
assert(ParamData.size() == Other.ParamData.size() &&
"Couldn't join statuses with different sizes");
for (auto Pair : llvm::zip(ParamData, Other.ParamData)) {
std::get<0>(Pair).join(std::get<1>(Pair));
}
}
using iterator = ParamSizedVector<ParameterStatus>::iterator;
using const_iterator = ParamSizedVector<ParameterStatus>::const_iterator;
iterator begin() { return ParamData.begin(); }
iterator end() { return ParamData.end(); }
const_iterator begin() const { return ParamData.begin(); }
const_iterator end() const { return ParamData.end(); }
bool operator==(const State &Other) const {
return ParamData == Other.ParamData;
}
private:
ParamSizedVector<ParameterStatus> ParamData;
};
/// A simple class that finds DeclRefExpr in the given expression.
///
/// However, we don't want to find ANY nested DeclRefExpr skipping whatever
/// expressions on our way. Only certain expressions considered "no-op"
/// for our task are indeed skipped.
class DeclRefFinder
: public ConstStmtVisitor<DeclRefFinder, const DeclRefExpr *> {
public:
/// Find a DeclRefExpr in the given expression.
///
/// In its most basic form (ShouldRetrieveFromComparisons == false),
/// this function can be simply reduced to the following question:
///
/// - If expression E is used as a function argument, could we say
/// that DeclRefExpr nested in E is used as an argument?
///
/// According to this rule, we can say that parens, casts and dereferencing
/// (dereferencing only applied to function pointers, but this is our case)
/// can be skipped.
///
/// When we should look into comparisons the question changes to:
///
/// - If expression E is used as a condition, could we say that
/// DeclRefExpr is being checked?
///
/// And even though, these are two different questions, they have quite a lot
/// in common. Actually, we can say that whatever expression answers
/// positively the first question also fits the second question as well.
///
/// In addition, we skip binary operators == and !=, and unary opeartor !.
static const DeclRefExpr *find(const Expr *E,
bool ShouldRetrieveFromComparisons = false) {
return DeclRefFinder(ShouldRetrieveFromComparisons).Visit(E);
}
const DeclRefExpr *VisitDeclRefExpr(const DeclRefExpr *DR) { return DR; }
const DeclRefExpr *VisitUnaryOperator(const UnaryOperator *UO) {
switch (UO->getOpcode()) {
case UO_LNot:
// We care about logical not only if we care about comparisons.
if (!ShouldRetrieveFromComparisons)
return nullptr;
[[fallthrough]];
// Function pointer/references can be dereferenced before a call.
// That doesn't make it, however, any different from a regular call.
// For this reason, dereference operation is a "no-op".
case UO_Deref:
return Visit(UO->getSubExpr());
default:
return nullptr;
}
}
const DeclRefExpr *VisitBinaryOperator(const BinaryOperator *BO) {
if (!ShouldRetrieveFromComparisons)
return nullptr;
switch (BO->getOpcode()) {
case BO_EQ:
case BO_NE: {
const DeclRefExpr *LHS = Visit(BO->getLHS());
return LHS ? LHS : Visit(BO->getRHS());
}
default:
return nullptr;
}
}
const DeclRefExpr *VisitOpaqueValueExpr(const OpaqueValueExpr *OVE) {
return Visit(OVE->getSourceExpr());
}
const DeclRefExpr *VisitCallExpr(const CallExpr *CE) {
if (!ShouldRetrieveFromComparisons)
return nullptr;
// We want to see through some of the boolean builtin functions
// that we are likely to see in conditions.
switch (CE->getBuiltinCallee()) {
case Builtin::BI__builtin_expect:
case Builtin::BI__builtin_expect_with_probability: {
assert(CE->getNumArgs() >= 2);
const DeclRefExpr *Candidate = Visit(CE->getArg(0));
return Candidate != nullptr ? Candidate : Visit(CE->getArg(1));
}
case Builtin::BI__builtin_unpredictable:
return Visit(CE->getArg(0));
default:
return nullptr;
}
}
const DeclRefExpr *VisitExpr(const Expr *E) {
// It is a fallback method that gets called whenever the actual type
// of the given expression is not covered.
//
// We first check if we have anything to skip. And then repeat the whole
// procedure for a nested expression instead.
const Expr *DeclutteredExpr = E->IgnoreParenCasts();
return E != DeclutteredExpr ? Visit(DeclutteredExpr) : nullptr;
}
private:
DeclRefFinder(bool ShouldRetrieveFromComparisons)
: ShouldRetrieveFromComparisons(ShouldRetrieveFromComparisons) {}
bool ShouldRetrieveFromComparisons;
};
const DeclRefExpr *findDeclRefExpr(const Expr *In,
bool ShouldRetrieveFromComparisons = false) {
return DeclRefFinder::find(In, ShouldRetrieveFromComparisons);
}
const ParmVarDecl *
findReferencedParmVarDecl(const Expr *In,
bool ShouldRetrieveFromComparisons = false) {
if (const DeclRefExpr *DR =
findDeclRefExpr(In, ShouldRetrieveFromComparisons)) {
return dyn_cast<ParmVarDecl>(DR->getDecl());
}
return nullptr;
}
/// Return conditions expression of a statement if it has one.
const Expr *getCondition(const Stmt *S) {
if (!S) {
return nullptr;
}
if (const auto *If = dyn_cast<IfStmt>(S)) {
return If->getCond();
}
if (const auto *Ternary = dyn_cast<AbstractConditionalOperator>(S)) {
return Ternary->getCond();
}
return nullptr;
}
/// A small helper class that collects all named identifiers in the given
/// expression. It traverses it recursively, so names from deeper levels
/// of the AST will end up in the results.
/// Results might have duplicate names, if this is a problem, convert to
/// string sets afterwards.
class NamesCollector : public RecursiveASTVisitor<NamesCollector> {
public:
static constexpr unsigned EXPECTED_NUMBER_OF_NAMES = 5;
using NameCollection =
llvm::SmallVector<llvm::StringRef, EXPECTED_NUMBER_OF_NAMES>;
static NameCollection collect(const Expr *From) {
NamesCollector Impl;
Impl.TraverseStmt(const_cast<Expr *>(From));
return Impl.Result;
}
bool VisitDeclRefExpr(const DeclRefExpr *E) {
Result.push_back(E->getDecl()->getName());
return true;
}
bool VisitObjCPropertyRefExpr(const ObjCPropertyRefExpr *E) {
llvm::StringRef Name;
if (E->isImplicitProperty()) {
ObjCMethodDecl *PropertyMethodDecl = nullptr;
if (E->isMessagingGetter()) {
PropertyMethodDecl = E->getImplicitPropertyGetter();
} else {
PropertyMethodDecl = E->getImplicitPropertySetter();
}
assert(PropertyMethodDecl &&
"Implicit property must have associated declaration");
Name = PropertyMethodDecl->getSelector().getNameForSlot(0);
} else {
assert(E->isExplicitProperty());
Name = E->getExplicitProperty()->getName();
}
Result.push_back(Name);
return true;
}
private:
NamesCollector() = default;
NameCollection Result;
};
/// Check whether the given expression mentions any of conventional names.
bool mentionsAnyOfConventionalNames(const Expr *E) {
NamesCollector::NameCollection MentionedNames = NamesCollector::collect(E);
return llvm::any_of(MentionedNames, [](llvm::StringRef ConditionName) {
return llvm::any_of(
CONVENTIONAL_CONDITIONS,
[ConditionName](const llvm::StringLiteral &Conventional) {
return ConditionName.contains_insensitive(Conventional);
});
});
}
/// Clarification is a simple pair of a reason why parameter is not called
/// on every path and a statement to blame.
struct Clarification {
NeverCalledReason Reason;
const Stmt *Location;
};
/// A helper class that can produce a clarification based on the given pair
/// of basic blocks.
class NotCalledClarifier
: public ConstStmtVisitor<NotCalledClarifier,
std::optional<Clarification>> {
public:
/// The main entrypoint for the class, the function that tries to find the
/// clarification of how to explain which sub-path starts with a CFG edge
/// from Conditional to SuccWithoutCall.
///
/// This means that this function has one precondition:
/// SuccWithoutCall should be a successor block for Conditional.
///
/// Because clarification is not needed for non-trivial pairs of blocks
/// (i.e. SuccWithoutCall is not the only successor), it returns meaningful
/// results only for such cases. For this very reason, the parent basic
/// block, Conditional, is named that way, so it is clear what kind of
/// block is expected.
static std::optional<Clarification> clarify(const CFGBlock *Conditional,
const CFGBlock *SuccWithoutCall) {
if (const Stmt *Terminator = Conditional->getTerminatorStmt()) {
return NotCalledClarifier{Conditional, SuccWithoutCall}.Visit(Terminator);
}
return std::nullopt;
}
std::optional<Clarification> VisitIfStmt(const IfStmt *If) {
return VisitBranchingBlock(If, NeverCalledReason::IfThen);
}
std::optional<Clarification>
VisitAbstractConditionalOperator(const AbstractConditionalOperator *Ternary) {
return VisitBranchingBlock(Ternary, NeverCalledReason::IfThen);
}
std::optional<Clarification> VisitSwitchStmt(const SwitchStmt *Switch) {
const Stmt *CaseToBlame = SuccInQuestion->getLabel();
if (!CaseToBlame) {
// If interesting basic block is not labeled, it means that this
// basic block does not represent any of the cases.
return Clarification{NeverCalledReason::SwitchSkipped, Switch};
}
for (const SwitchCase *Case = Switch->getSwitchCaseList(); Case;
Case = Case->getNextSwitchCase()) {
if (Case == CaseToBlame) {
return Clarification{NeverCalledReason::Switch, Case};
}
}
llvm_unreachable("Found unexpected switch structure");
}
std::optional<Clarification> VisitForStmt(const ForStmt *For) {
return VisitBranchingBlock(For, NeverCalledReason::LoopEntered);
}
std::optional<Clarification> VisitWhileStmt(const WhileStmt *While) {
return VisitBranchingBlock(While, NeverCalledReason::LoopEntered);
}
std::optional<Clarification>
VisitBranchingBlock(const Stmt *Terminator, NeverCalledReason DefaultReason) {
assert(Parent->succ_size() == 2 &&
"Branching block should have exactly two successors");
unsigned SuccessorIndex = getSuccessorIndex(Parent, SuccInQuestion);
NeverCalledReason ActualReason =
updateForSuccessor(DefaultReason, SuccessorIndex);
return Clarification{ActualReason, Terminator};
}
std::optional<Clarification> VisitBinaryOperator(const BinaryOperator *) {
// We don't want to report on short-curcuit logical operations.
return std::nullopt;
}
std::optional<Clarification> VisitStmt(const Stmt *Terminator) {
// If we got here, we didn't have a visit function for more derived
// classes of statement that this terminator actually belongs to.
//
// This is not a good scenario and should not happen in practice, but
// at least we'll warn the user.
return Clarification{NeverCalledReason::FallbackReason, Terminator};
}
static unsigned getSuccessorIndex(const CFGBlock *Parent,
const CFGBlock *Child) {
CFGBlock::const_succ_iterator It = llvm::find(Parent->succs(), Child);
assert(It != Parent->succ_end() &&
"Given blocks should be in parent-child relationship");
return It - Parent->succ_begin();
}
static NeverCalledReason
updateForSuccessor(NeverCalledReason ReasonForTrueBranch,
unsigned SuccessorIndex) {
assert(SuccessorIndex <= 1);
unsigned RawReason =
static_cast<unsigned>(ReasonForTrueBranch) + SuccessorIndex;
assert(RawReason <=
static_cast<unsigned>(NeverCalledReason::LARGEST_VALUE));
return static_cast<NeverCalledReason>(RawReason);
}
private:
NotCalledClarifier(const CFGBlock *Parent, const CFGBlock *SuccInQuestion)
: Parent(Parent), SuccInQuestion(SuccInQuestion) {}
const CFGBlock *Parent, *SuccInQuestion;
};
class CalledOnceChecker : public ConstStmtVisitor<CalledOnceChecker> {
public:
static void check(AnalysisDeclContext &AC, CalledOnceCheckHandler &Handler,
bool CheckConventionalParameters) {
CalledOnceChecker(AC, Handler, CheckConventionalParameters).check();
}
private:
CalledOnceChecker(AnalysisDeclContext &AC, CalledOnceCheckHandler &Handler,
bool CheckConventionalParameters)
: FunctionCFG(*AC.getCFG()), AC(AC), Handler(Handler),
CheckConventionalParameters(CheckConventionalParameters),
CurrentState(0) {
initDataStructures();
assert((size() == 0 || !States.empty()) &&
"Data structures are inconsistent");
}
//===----------------------------------------------------------------------===//
// Initializing functions
//===----------------------------------------------------------------------===//
void initDataStructures() {
const Decl *AnalyzedDecl = AC.getDecl();
if (const auto *Function = dyn_cast<FunctionDecl>(AnalyzedDecl)) {
findParamsToTrack(Function);
} else if (const auto *Method = dyn_cast<ObjCMethodDecl>(AnalyzedDecl)) {
findParamsToTrack(Method);
} else if (const auto *Block = dyn_cast<BlockDecl>(AnalyzedDecl)) {
findCapturesToTrack(Block);
findParamsToTrack(Block);
}
// Have something to track, let's init states for every block from the CFG.
if (size() != 0) {
States =
CFGSizedVector<State>(FunctionCFG.getNumBlockIDs(), State(size()));
}
}
void findCapturesToTrack(const BlockDecl *Block) {
for (const auto &Capture : Block->captures()) {
if (const auto *P = dyn_cast<ParmVarDecl>(Capture.getVariable())) {
// Parameter DeclContext is its owning function or method.
const DeclContext *ParamContext = P->getDeclContext();
if (shouldBeCalledOnce(ParamContext, P)) {
TrackedParams.push_back(P);
}
}
}
}
template <class FunctionLikeDecl>
void findParamsToTrack(const FunctionLikeDecl *Function) {
for (unsigned Index : llvm::seq<unsigned>(0u, Function->param_size())) {
if (shouldBeCalledOnce(Function, Index)) {
TrackedParams.push_back(Function->getParamDecl(Index));
}
}
}
//===----------------------------------------------------------------------===//
// Main logic 'check' functions
//===----------------------------------------------------------------------===//
void check() {
// Nothing to check here: we don't have marked parameters.
if (size() == 0 || isPossiblyEmptyImpl())
return;
assert(
llvm::none_of(States, [](const State &S) { return S.isVisited(); }) &&
"None of the blocks should be 'visited' before the analysis");
// For our task, both backward and forward approaches suite well.
// However, in order to report better diagnostics, we decided to go with
// backward analysis.
//
// Let's consider the following CFG and how forward and backward analyses
// will work for it.
//
// FORWARD: | BACKWARD:
// #1 | #1
// +---------+ | +-----------+
// | if | | |MaybeCalled|
// +---------+ | +-----------+
// |NotCalled| | | if |
// +---------+ | +-----------+
// / \ | / \
// #2 / \ #3 | #2 / \ #3
// +----------------+ +---------+ | +----------------+ +---------+
// | foo() | | ... | | |DefinitelyCalled| |NotCalled|
// +----------------+ +---------+ | +----------------+ +---------+
// |DefinitelyCalled| |NotCalled| | | foo() | | ... |
// +----------------+ +---------+ | +----------------+ +---------+
// \ / | \ /
// \ #4 / | \ #4 /
// +-----------+ | +---------+
// | ... | | |NotCalled|
// +-----------+ | +---------+
// |MaybeCalled| | | ... |
// +-----------+ | +---------+
//
// The most natural way to report lacking call in the block #3 would be to
// message that the false branch of the if statement in the block #1 doesn't
// have a call. And while with the forward approach we'll need to find a
// least common ancestor or something like that to find the 'if' to blame,
// backward analysis gives it to us out of the box.
BackwardDataflowWorklist Worklist(FunctionCFG, AC);
// Let's visit EXIT.
const CFGBlock *Exit = &FunctionCFG.getExit();
assignState(Exit, State(size(), ParameterStatus::NotCalled));
Worklist.enqueuePredecessors(Exit);
while (const CFGBlock *BB = Worklist.dequeue()) {
assert(BB && "Worklist should filter out null blocks");
check(BB);
assert(CurrentState.isVisited() &&
"After the check, basic block should be visited");
// Traverse successor basic blocks if the status of this block
// has changed.
if (assignState(BB, CurrentState)) {
Worklist.enqueuePredecessors(BB);
}
}
// Check that we have all tracked parameters at the last block.
// As we are performing a backward version of the analysis,
// it should be the ENTRY block.
checkEntry(&FunctionCFG.getEntry());
}
void check(const CFGBlock *BB) {
// We start with a state 'inherited' from all the successors.
CurrentState = joinSuccessors(BB);
assert(CurrentState.isVisited() &&
"Shouldn't start with a 'not visited' state");
// This is the 'exit' situation, broken promises are probably OK
// in such scenarios.
if (BB->hasNoReturnElement()) {
markNoReturn();
// This block still can have calls (even multiple calls) and
// for this reason there is no early return here.
}
// We use a backward dataflow propagation and for this reason we
// should traverse basic blocks bottom-up.
for (const CFGElement &Element : llvm::reverse(*BB)) {
if (std::optional<CFGStmt> S = Element.getAs<CFGStmt>()) {
check(S->getStmt());
}
}
}
void check(const Stmt *S) { Visit(S); }
void checkEntry(const CFGBlock *Entry) {
// We finalize this algorithm with the ENTRY block because
// we use a backward version of the analysis. This is where
// we can judge that some of the tracked parameters are not called on
// every path from ENTRY to EXIT.
const State &EntryStatus = getState(Entry);
llvm::BitVector NotCalledOnEveryPath(size(), false);
llvm::BitVector NotUsedOnEveryPath(size(), false);
// Check if there are no calls of the marked parameter at all
for (const auto &IndexedStatus : llvm::enumerate(EntryStatus)) {
const ParmVarDecl *Parameter = getParameter(IndexedStatus.index());
switch (IndexedStatus.value().getKind()) {
case ParameterStatus::NotCalled:
// If there were places where this parameter escapes (aka being used),
// we can provide a more useful diagnostic by pointing at the exact
// branches where it is not even mentioned.
if (!hasEverEscaped(IndexedStatus.index())) {
// This parameter is was not used at all, so we should report the
// most generic version of the warning.
if (isCaptured(Parameter)) {
// We want to specify that it was captured by the block.
Handler.handleCapturedNeverCalled(Parameter, AC.getDecl(),
!isExplicitlyMarked(Parameter));
} else {
Handler.handleNeverCalled(Parameter,
!isExplicitlyMarked(Parameter));
}
} else {
// Mark it as 'interesting' to figure out which paths don't even
// have escapes.
NotUsedOnEveryPath[IndexedStatus.index()] = true;
}
break;
case ParameterStatus::MaybeCalled:
// If we have 'maybe called' at this point, we have an error
// that there is at least one path where this parameter
// is not called.
//
// However, reporting the warning with only that information can be
// too vague for the users. For this reason, we mark such parameters
// as "interesting" for further analysis.
NotCalledOnEveryPath[IndexedStatus.index()] = true;
break;
default:
break;
}
}
// Early exit if we don't have parameters for extra analysis...
if (NotCalledOnEveryPath.none() && NotUsedOnEveryPath.none() &&
// ... or if we've seen variables with cleanup functions.
// We can't reason that we've seen every path in this case,
// and thus abandon reporting any warnings that imply that.
!FunctionHasCleanupVars)
return;
// We are looking for a pair of blocks A, B so that the following is true:
// * A is a predecessor of B
// * B is marked as NotCalled
// * A has at least one successor marked as either
// Escaped or DefinitelyCalled
//
// In that situation, it is guaranteed that B is the first block of the path
// where the user doesn't call or use parameter in question.
//
// For this reason, branch A -> B can be used for reporting.
//
// This part of the algorithm is guarded by a condition that the function
// does indeed have a violation of contract. For this reason, we can
// spend more time to find a good spot to place the warning.
//
// The following algorithm has the worst case complexity of O(V + E),
// where V is the number of basic blocks in FunctionCFG,
// E is the number of edges between blocks in FunctionCFG.
for (const CFGBlock *BB : FunctionCFG) {
if (!BB)
continue;
const State &BlockState = getState(BB);
for (unsigned Index : llvm::seq(0u, size())) {
// We don't want to use 'isLosingCall' here because we want to report
// the following situation as well:
//
// MaybeCalled
// | ... |
// MaybeCalled NotCalled
//
// Even though successor is not 'DefinitelyCalled', it is still useful
// to report it, it is still a path without a call.
if (NotCalledOnEveryPath[Index] &&
BlockState.getKindFor(Index) == ParameterStatus::MaybeCalled) {
findAndReportNotCalledBranches(BB, Index);
} else if (NotUsedOnEveryPath[Index] &&
isLosingEscape(BlockState, BB, Index)) {
findAndReportNotCalledBranches(BB, Index, /* IsEscape = */ true);
}
}
}
}
/// Check potential call of a tracked parameter.
void checkDirectCall(const CallExpr *Call) {
if (auto Index = getIndexOfCallee(Call)) {
processCallFor(*Index, Call);
}
}
/// Check the call expression for being an indirect call of one of the tracked
/// parameters. It is indirect in the sense that this particular call is not
/// calling the parameter itself, but rather uses it as the argument.
template <class CallLikeExpr>
void checkIndirectCall(const CallLikeExpr *CallOrMessage) {
// CallExpr::arguments does not interact nicely with llvm::enumerate.
llvm::ArrayRef<const Expr *> Arguments =
llvm::ArrayRef(CallOrMessage->getArgs(), CallOrMessage->getNumArgs());
// Let's check if any of the call arguments is a point of interest.
for (const auto &Argument : llvm::enumerate(Arguments)) {
if (auto Index = getIndexOfExpression(Argument.value())) {
if (shouldBeCalledOnce(CallOrMessage, Argument.index())) {
// If the corresponding parameter is marked as 'called_once' we should
// consider it as a call.
processCallFor(*Index, CallOrMessage);
} else {
// Otherwise, we mark this parameter as escaped, which can be
// interpreted both as called or not called depending on the context.
processEscapeFor(*Index);
}
// Otherwise, let's keep the state as it is.
}
}
}
/// Process call of the parameter with the given index
void processCallFor(unsigned Index, const Expr *Call) {
ParameterStatus &CurrentParamStatus = CurrentState.getStatusFor(Index);
if (CurrentParamStatus.seenAnyCalls()) {
// At this point, this parameter was called, so this is a second call.
const ParmVarDecl *Parameter = getParameter(Index);
Handler.handleDoubleCall(
Parameter, &CurrentState.getCallFor(Index), Call,
!isExplicitlyMarked(Parameter),
// We are sure that the second call is definitely
// going to happen if the status is 'DefinitelyCalled'.
CurrentParamStatus.getKind() == ParameterStatus::DefinitelyCalled);
// Mark this parameter as already reported on, so we don't repeat
// warnings.
CurrentParamStatus = ParameterStatus::Reported;
} else if (CurrentParamStatus.getKind() != ParameterStatus::Reported) {
// If we didn't report anything yet, let's mark this parameter
// as called.
ParameterStatus Called(ParameterStatus::DefinitelyCalled, Call);
CurrentParamStatus = Called;
}
}
/// Process escape of the parameter with the given index
void processEscapeFor(unsigned Index) {
ParameterStatus &CurrentParamStatus = CurrentState.getStatusFor(Index);
// Escape overrides whatever error we think happened.
if (CurrentParamStatus.isErrorStatus()) {
CurrentParamStatus = ParameterStatus::Escaped;
}
}
void findAndReportNotCalledBranches(const CFGBlock *Parent, unsigned Index,
bool IsEscape = false) {
for (const CFGBlock *Succ : Parent->succs()) {
if (!Succ)
continue;
if (getState(Succ).getKindFor(Index) == ParameterStatus::NotCalled) {
assert(Parent->succ_size() >= 2 &&
"Block should have at least two successors at this point");
if (auto Clarification = NotCalledClarifier::clarify(Parent, Succ)) {
const ParmVarDecl *Parameter = getParameter(Index);
Handler.handleNeverCalled(
Parameter, AC.getDecl(), Clarification->Location,
Clarification->Reason, !IsEscape, !isExplicitlyMarked(Parameter));
}
}
}
}
//===----------------------------------------------------------------------===//
// Predicate functions to check parameters
//===----------------------------------------------------------------------===//
/// Return true if parameter is explicitly marked as 'called_once'.
static bool isExplicitlyMarked(const ParmVarDecl *Parameter) {
return Parameter->hasAttr<CalledOnceAttr>();
}
/// Return true if the given name matches conventional pattens.
static bool isConventional(llvm::StringRef Name) {
return llvm::count(CONVENTIONAL_NAMES, Name) != 0;
}
/// Return true if the given name has conventional suffixes.
static bool hasConventionalSuffix(llvm::StringRef Name) {
return llvm::any_of(CONVENTIONAL_SUFFIXES, [Name](llvm::StringRef Suffix) {
return Name.ends_with(Suffix);
});
}
/// Return true if the given type can be used for conventional parameters.
static bool isConventional(QualType Ty) {
if (!Ty->isBlockPointerType()) {
return false;
}
QualType BlockType = Ty->castAs<BlockPointerType>()->getPointeeType();
// Completion handlers should have a block type with void return type.
return BlockType->castAs<FunctionType>()->getReturnType()->isVoidType();
}
/// Return true if the only parameter of the function is conventional.
static bool isOnlyParameterConventional(const FunctionDecl *Function) {
IdentifierInfo *II = Function->getIdentifier();
return Function->getNumParams() == 1 && II &&
hasConventionalSuffix(II->getName());
}
/// Return true/false if 'swift_async' attribute states that the given
/// parameter is conventionally called once.
/// Return std::nullopt if the given declaration doesn't have 'swift_async'