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 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
32 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
33 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
34 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
35 | #include "clang/StaticAnalyzer/Core/Checker.h" |
36 | #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" |
37 | #include "llvm/Support/Debug.h" |
38 | |
39 | using namespace clang; |
40 | using namespace ento; |
41 | using namespace ast_matchers; |
42 | |
43 | namespace { |
44 | |
45 | |
46 | const char *WarnAtNode = "waitcall"; |
47 | |
48 | class GCDAntipatternChecker : public Checker<check::ASTCodeBody> { |
49 | public: |
50 | void checkASTCodeBody(const Decl *D, |
51 | AnalysisManager &AM, |
52 | BugReporter &BR) const; |
53 | }; |
54 | |
55 | auto callsName(const char *FunctionName) |
56 | -> decltype(callee(functionDecl())) { |
57 | return callee(functionDecl(hasName(FunctionName))); |
58 | } |
59 | |
60 | auto equalsBoundArgDecl(int ArgIdx, const char *DeclName) |
61 | -> decltype(hasArgument(0, expr())) { |
62 | return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr( |
63 | to(varDecl(equalsBoundNode(DeclName)))))); |
64 | } |
65 | |
66 | auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) { |
67 | return hasLHS(ignoringParenImpCasts( |
68 | declRefExpr(to(varDecl().bind(DeclName))))); |
69 | } |
70 | |
71 | |
72 | |
73 | |
74 | |
75 | static bool isTest(const Decl *D) { |
76 | if (const auto* ND = dyn_cast<NamedDecl>(D)) { |
77 | std::string DeclName = ND->getNameAsString(); |
78 | if (StringRef(DeclName).startswith("test")) |
79 | return true; |
80 | } |
81 | if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) { |
82 | if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) { |
83 | std::string ContainerName = CD->getNameAsString(); |
84 | StringRef CN(ContainerName); |
85 | if (CN.contains_lower("test") || CN.contains_lower("mock")) |
86 | return true; |
87 | } |
88 | } |
89 | return false; |
90 | } |
91 | |
92 | static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) { |
93 | |
94 | const char *SemaphoreBinding = "semaphore_name"; |
95 | auto SemaphoreCreateM = callExpr(allOf( |
96 | callsName("dispatch_semaphore_create"), |
97 | hasArgument(0, ignoringParenCasts(integerLiteral(equals(0)))))); |
98 | |
99 | auto SemaphoreBindingM = anyOf( |
100 | forEachDescendant( |
101 | varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)), |
102 | forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding), |
103 | hasRHS(SemaphoreCreateM)))); |
104 | |
105 | auto HasBlockArgumentM = hasAnyArgument(hasType( |
106 | hasCanonicalType(blockPointerType()) |
107 | )); |
108 | |
109 | auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr( |
110 | allOf( |
111 | callsName("dispatch_semaphore_signal"), |
112 | equalsBoundArgDecl(0, SemaphoreBinding) |
113 | ))))); |
114 | |
115 | auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM); |
116 | |
117 | auto HasBlockCallingSignalM = |
118 | forEachDescendant( |
119 | stmt(anyOf( |
120 | callExpr(HasBlockAndCallsSignalM), |
121 | objcMessageExpr(HasBlockAndCallsSignalM) |
122 | ))); |
123 | |
124 | auto SemaphoreWaitM = forEachDescendant( |
125 | callExpr( |
126 | allOf( |
127 | callsName("dispatch_semaphore_wait"), |
128 | equalsBoundArgDecl(0, SemaphoreBinding) |
129 | ) |
130 | ).bind(WarnAtNode)); |
131 | |
132 | return compoundStmt( |
133 | SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM); |
134 | } |
135 | |
136 | static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) { |
137 | |
138 | const char *GroupBinding = "group_name"; |
139 | auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create")); |
140 | |
141 | auto GroupBindingM = anyOf( |
142 | forEachDescendant( |
143 | varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)), |
144 | forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding), |
145 | hasRHS(DispatchGroupCreateM)))); |
146 | |
147 | auto GroupEnterM = forEachDescendant( |
148 | stmt(callExpr(allOf(callsName("dispatch_group_enter"), |
149 | equalsBoundArgDecl(0, GroupBinding))))); |
150 | |
151 | auto HasBlockArgumentM = hasAnyArgument(hasType( |
152 | hasCanonicalType(blockPointerType()) |
153 | )); |
154 | |
155 | auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr( |
156 | allOf( |
157 | callsName("dispatch_group_leave"), |
158 | equalsBoundArgDecl(0, GroupBinding) |
159 | ))))); |
160 | |
161 | auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM); |
162 | |
163 | auto AcceptsBlockM = |
164 | forEachDescendant( |
165 | stmt(anyOf( |
166 | callExpr(HasBlockAndCallsLeaveM), |
167 | objcMessageExpr(HasBlockAndCallsLeaveM) |
168 | ))); |
169 | |
170 | auto GroupWaitM = forEachDescendant( |
171 | callExpr( |
172 | allOf( |
173 | callsName("dispatch_group_wait"), |
174 | equalsBoundArgDecl(0, GroupBinding) |
175 | ) |
176 | ).bind(WarnAtNode)); |
177 | |
178 | return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM); |
179 | } |
180 | |
181 | static void emitDiagnostics(const BoundNodes &Nodes, |
182 | const char* Type, |
183 | BugReporter &BR, |
184 | AnalysisDeclContext *ADC, |
185 | const GCDAntipatternChecker *Checker) { |
186 | const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode); |
187 | assert(SW); |
188 | |
189 | std::string Diagnostics; |
190 | llvm::raw_string_ostream OS(Diagnostics); |
191 | OS << "Waiting on a callback using a " << Type << " creates useless threads " |
192 | << "and is subject to priority inversion; consider " |
193 | << "using a synchronous API or changing the caller to be asynchronous"; |
194 | |
195 | BR.EmitBasicReport( |
196 | ADC->getDecl(), |
197 | Checker, |
198 | , |
199 | , |
200 | OS.str(), |
201 | PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC), |
202 | SW->getSourceRange()); |
203 | } |
204 | |
205 | void GCDAntipatternChecker::checkASTCodeBody(const Decl *D, |
206 | AnalysisManager &AM, |
207 | BugReporter &BR) const { |
208 | if (isTest(D)) |
209 | return; |
210 | |
211 | AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); |
212 | |
213 | auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore(); |
214 | auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext()); |
215 | for (BoundNodes Match : Matches) |
216 | emitDiagnostics(Match, "semaphore", BR, ADC, this); |
217 | |
218 | auto GroupMatcherM = findGCDAntiPatternWithGroup(); |
219 | Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext()); |
220 | for (BoundNodes Match : Matches) |
221 | emitDiagnostics(Match, "group", BR, ADC, this); |
222 | } |
223 | |
224 | } |
225 | |
226 | void ento::registerGCDAntipattern(CheckerManager &Mgr) { |
227 | Mgr.registerChecker<GCDAntipatternChecker>(); |
228 | } |
229 | |
230 | bool ento::shouldRegisterGCDAntipattern(const LangOptions &LO) { |
231 | return true; |
232 | } |
233 | |