Skip to content

Commit a7982d5

Browse files
authored
[analyzer] UncountedCallArgsChecker: Detect & ignore trivial function calls. (#81808)
This PR introduces the concept of a "trivial function" which applies to a function that only calls other trivial functions and contain literals and expressions that don't result in heap mutations (specifically it does not call deref). This is implemented using ConstStmtVisitor and checking each statement and expression's trivialness. This PR also introduces the concept of a "ingleton function", which is a static member function or a free standing function which ends with the suffix "singleton". Such a function's return value is understood to be safe to call any function with.
1 parent ff409d3 commit a7982d5

File tree

6 files changed

+495
-17
lines changed

6 files changed

+495
-17
lines changed

clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,13 @@ tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) {
6666
E = call->getArg(0);
6767
continue;
6868
}
69+
6970
if (isReturnValueRefCounted(callee))
7071
return {E, true};
7172

73+
if (isSingleton(callee))
74+
return {E, true};
75+
7276
if (isPtrConversion(callee)) {
7377
E = call->getArg(0);
7478
continue;

clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "clang/AST/Decl.h"
1313
#include "clang/AST/DeclCXX.h"
1414
#include "clang/AST/ExprCXX.h"
15+
#include "clang/AST/StmtVisitor.h"
1516
#include <optional>
1617

1718
using namespace clang;
@@ -222,4 +223,216 @@ bool isPtrConversion(const FunctionDecl *F) {
222223
return false;
223224
}
224225

226+
bool isSingleton(const FunctionDecl *F) {
227+
assert(F);
228+
// FIXME: check # of params == 1
229+
if (auto *MethodDecl = dyn_cast<CXXMethodDecl>(F)) {
230+
if (!MethodDecl->isStatic())
231+
return false;
232+
}
233+
const auto &Name = safeGetName(F);
234+
std::string SingletonStr = "singleton";
235+
auto index = Name.find(SingletonStr);
236+
return index != std::string::npos &&
237+
index == Name.size() - SingletonStr.size();
238+
}
239+
240+
// We only care about statements so let's use the simple
241+
// (non-recursive) visitor.
242+
class TrivialFunctionAnalysisVisitor
243+
: public ConstStmtVisitor<TrivialFunctionAnalysisVisitor, bool> {
244+
245+
// Returns false if at least one child is non-trivial.
246+
bool VisitChildren(const Stmt *S) {
247+
for (const Stmt *Child : S->children()) {
248+
if (Child && !Visit(Child))
249+
return false;
250+
}
251+
252+
return true;
253+
}
254+
255+
public:
256+
using CacheTy = TrivialFunctionAnalysis::CacheTy;
257+
258+
TrivialFunctionAnalysisVisitor(CacheTy &Cache) : Cache(Cache) {}
259+
260+
bool VisitStmt(const Stmt *S) {
261+
// All statements are non-trivial unless overriden later.
262+
// Don't even recurse into children by default.
263+
return false;
264+
}
265+
266+
bool VisitCompoundStmt(const CompoundStmt *CS) {
267+
// A compound statement is allowed as long each individual sub-statement
268+
// is trivial.
269+
return VisitChildren(CS);
270+
}
271+
272+
bool VisitReturnStmt(const ReturnStmt *RS) {
273+
// A return statement is allowed as long as the return value is trivial.
274+
if (auto *RV = RS->getRetValue())
275+
return Visit(RV);
276+
return true;
277+
}
278+
279+
bool VisitDeclStmt(const DeclStmt *DS) { return VisitChildren(DS); }
280+
bool VisitDoStmt(const DoStmt *DS) { return VisitChildren(DS); }
281+
bool VisitIfStmt(const IfStmt *IS) { return VisitChildren(IS); }
282+
bool VisitSwitchStmt(const SwitchStmt *SS) { return VisitChildren(SS); }
283+
bool VisitCaseStmt(const CaseStmt *CS) { return VisitChildren(CS); }
284+
bool VisitDefaultStmt(const DefaultStmt *DS) { return VisitChildren(DS); }
285+
286+
bool VisitUnaryOperator(const UnaryOperator *UO) {
287+
// Operator '*' and '!' are allowed as long as the operand is trivial.
288+
if (UO->getOpcode() == UO_Deref || UO->getOpcode() == UO_LNot)
289+
return Visit(UO->getSubExpr());
290+
291+
// Other operators are non-trivial.
292+
return false;
293+
}
294+
295+
bool VisitBinaryOperator(const BinaryOperator *BO) {
296+
// Binary operators are trivial if their operands are trivial.
297+
return Visit(BO->getLHS()) && Visit(BO->getRHS());
298+
}
299+
300+
bool VisitConditionalOperator(const ConditionalOperator *CO) {
301+
// Ternary operators are trivial if their conditions & values are trivial.
302+
return VisitChildren(CO);
303+
}
304+
305+
bool VisitDeclRefExpr(const DeclRefExpr *DRE) {
306+
if (auto *decl = DRE->getDecl()) {
307+
if (isa<ParmVarDecl>(decl))
308+
return true;
309+
}
310+
return false;
311+
}
312+
313+
bool VisitStaticAssertDecl(const StaticAssertDecl *SAD) {
314+
// Any static_assert is considered trivial.
315+
return true;
316+
}
317+
318+
bool VisitCallExpr(const CallExpr *CE) {
319+
if (!checkArguments(CE))
320+
return false;
321+
322+
auto *Callee = CE->getDirectCallee();
323+
if (!Callee)
324+
return false;
325+
const auto &Name = safeGetName(Callee);
326+
327+
if (Name == "WTFCrashWithInfo" || Name == "WTFBreakpointTrap" ||
328+
Name == "compilerFenceForCrash" || Name == "__builtin_unreachable")
329+
return true;
330+
331+
return TrivialFunctionAnalysis::isTrivialImpl(Callee, Cache);
332+
}
333+
334+
bool VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
335+
if (!checkArguments(MCE))
336+
return false;
337+
338+
bool TrivialThis = Visit(MCE->getImplicitObjectArgument());
339+
if (!TrivialThis)
340+
return false;
341+
342+
auto *Callee = MCE->getMethodDecl();
343+
if (!Callee)
344+
return false;
345+
346+
std::optional<bool> IsGetterOfRefCounted = isGetterOfRefCounted(Callee);
347+
if (IsGetterOfRefCounted && *IsGetterOfRefCounted)
348+
return true;
349+
350+
// Recursively descend into the callee to confirm that it's trivial as well.
351+
return TrivialFunctionAnalysis::isTrivialImpl(Callee, Cache);
352+
}
353+
354+
bool checkArguments(const CallExpr *CE) {
355+
for (const Expr *Arg : CE->arguments()) {
356+
if (Arg && !Visit(Arg))
357+
return false;
358+
}
359+
return true;
360+
}
361+
362+
bool VisitCXXConstructExpr(const CXXConstructExpr *CE) {
363+
for (const Expr *Arg : CE->arguments()) {
364+
if (Arg && !Visit(Arg))
365+
return false;
366+
}
367+
368+
// Recursively descend into the callee to confirm that it's trivial.
369+
return TrivialFunctionAnalysis::isTrivialImpl(CE->getConstructor(), Cache);
370+
}
371+
372+
bool VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
373+
return Visit(ICE->getSubExpr());
374+
}
375+
376+
bool VisitExplicitCastExpr(const ExplicitCastExpr *ECE) {
377+
return Visit(ECE->getSubExpr());
378+
}
379+
380+
bool VisitParenExpr(const ParenExpr *PE) { return Visit(PE->getSubExpr()); }
381+
382+
bool VisitInitListExpr(const InitListExpr *ILE) {
383+
for (const Expr *Child : ILE->inits()) {
384+
if (Child && !Visit(Child))
385+
return false;
386+
}
387+
return true;
388+
}
389+
390+
bool VisitMemberExpr(const MemberExpr *ME) {
391+
// Field access is allowed but the base pointer may itself be non-trivial.
392+
return Visit(ME->getBase());
393+
}
394+
395+
bool VisitCXXThisExpr(const CXXThisExpr *CTE) {
396+
// The expression 'this' is always trivial, be it explicit or implicit.
397+
return true;
398+
}
399+
400+
// Constant literal expressions are always trivial
401+
bool VisitIntegerLiteral(const IntegerLiteral *E) { return true; }
402+
bool VisitFloatingLiteral(const FloatingLiteral *E) { return true; }
403+
bool VisitFixedPointLiteral(const FixedPointLiteral *E) { return true; }
404+
bool VisitCharacterLiteral(const CharacterLiteral *E) { return true; }
405+
bool VisitStringLiteral(const StringLiteral *E) { return true; }
406+
407+
bool VisitConstantExpr(const ConstantExpr *CE) {
408+
// Constant expressions are trivial.
409+
return true;
410+
}
411+
412+
private:
413+
CacheTy Cache;
414+
};
415+
416+
bool TrivialFunctionAnalysis::isTrivialImpl(
417+
const Decl *D, TrivialFunctionAnalysis::CacheTy &Cache) {
418+
// If the function isn't in the cache, conservatively assume that
419+
// it's not trivial until analysis completes. This makes every recursive
420+
// function non-trivial. This also guarantees that each function
421+
// will be scanned at most once.
422+
auto [It, IsNew] = Cache.insert(std::make_pair(D, false));
423+
if (!IsNew)
424+
return It->second;
425+
426+
const Stmt *Body = D->getBody();
427+
if (!Body)
428+
return false;
429+
430+
TrivialFunctionAnalysisVisitor V(Cache);
431+
bool Result = V.Visit(Body);
432+
if (Result)
433+
Cache[D] = true;
434+
435+
return Result;
436+
}
437+
225438
} // namespace clang

clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
#define LLVM_CLANG_ANALYZER_WEBKIT_PTRTYPESEMANTICS_H
1111

1212
#include "llvm/ADT/APInt.h"
13+
#include "llvm/ADT/DenseMap.h"
1314
#include <optional>
1415

1516
namespace clang {
1617
class CXXBaseSpecifier;
1718
class CXXMethodDecl;
1819
class CXXRecordDecl;
20+
class Decl;
1921
class FunctionDecl;
2022
class Type;
2123

@@ -60,6 +62,25 @@ std::optional<bool> isGetterOfRefCounted(const clang::CXXMethodDecl* Method);
6062
/// pointer types.
6163
bool isPtrConversion(const FunctionDecl *F);
6264

65+
/// \returns true if \p F is a static singleton function.
66+
bool isSingleton(const FunctionDecl *F);
67+
68+
/// An inter-procedural analysis facility that detects functions with "trivial"
69+
/// behavior with respect to reference counting, such as simple field getters.
70+
class TrivialFunctionAnalysis {
71+
public:
72+
/// \returns true if \p D is a "trivial" function.
73+
bool isTrivial(const Decl *D) const { return isTrivialImpl(D, TheCache); }
74+
75+
private:
76+
friend class TrivialFunctionAnalysisVisitor;
77+
78+
using CacheTy = llvm::DenseMap<const Decl *, bool>;
79+
mutable CacheTy TheCache{};
80+
81+
static bool isTrivialImpl(const Decl *D, CacheTy &Cache);
82+
};
83+
6384
} // namespace clang
6485

6586
#endif

clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class UncountedCallArgsChecker
3232
"WebKit coding guidelines"};
3333
mutable BugReporter *BR;
3434

35+
TrivialFunctionAnalysis TFA;
36+
3537
public:
3638

3739
void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
@@ -134,6 +136,11 @@ class UncountedCallArgsChecker
134136
}
135137

136138
bool shouldSkipCall(const CallExpr *CE) const {
139+
const auto *Callee = CE->getDirectCallee();
140+
141+
if (Callee && TFA.isTrivial(Callee))
142+
return true;
143+
137144
if (CE->getNumArgs() == 0)
138145
return false;
139146

@@ -155,7 +162,6 @@ class UncountedCallArgsChecker
155162
return false;
156163
}
157164

158-
const auto *Callee = CE->getDirectCallee();
159165
if (!Callee)
160166
return false;
161167

0 commit comments

Comments
 (0)