Skip to content

Commit 9c0a0ca

Browse files
committed
Generate -Wunsafe-buffer-usage warnings in ctor and field initializers
Field initializers must be found by visiting FieldDecl and then running the warning checks/matchers against the in-class initializer, which is itself a Stmt. This catches warnings in places such as: struct C { P* Ptr; AnUnsafeCtor U{Ptr}; }; CXXCtorInitializers are not statements either, but they point to an initializer expression which is. When visiting a FunctionDecl, also walk through any constructor initializers and run the warning checks/matchers against their initializer expressions. This catches warnings for initializing fields and calling other constructors, such as: struct C { C(P* Ptr) : AnUnsafeCtor(Ptr) {} } We add tests for explicit construction, for field initialization, base class constructor calls, delegated constructor calls, and aggregate initialization. Note that aggregate initialization through `()` is treated differently today by the AST matchers than `{}`. The former is not considered as calling an implicit constructor, while the latter is. MatchDescendantVisitor::shouldVisitImplicitCode() returns false with a TODO, which means we do not catch warnings of the form: struct AggregateInitType { AnUnsafeCtor U; } AggregateInitType{Ptr}; But we do already catch them when written as (in C++20): struct AggregateInitType { AnUnsafeCtor U; } AggregateInitType(Ptr); Returning true from MatchDescendantVisitor::shouldVisitImplicitCode(), however, breaks expectations for field in-class initializers by moving the SourceLocation, possibly to inside the implicit ctor instead of on the line where the field initialization happens. struct C { P* Ptr; AnUnsafeCtor U{Ptr}; // expected-warning{{this is never seen then}} }; Tests are also added for std::span(ptr, size) constructor being called from a field initializer and a constructor initializer.
1 parent 12a8f50 commit 9c0a0ca

File tree

4 files changed

+155
-38
lines changed

4 files changed

+155
-38
lines changed

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1973,8 +1973,8 @@ class DerefSimplePtrArithFixableGadget : public FixableGadget {
19731973

19741974
/// Scan the function and return a list of gadgets found with provided kits.
19751975
static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker>
1976-
findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
1977-
bool EmitSuggestions) {
1976+
findGadgets(const Stmt *S, ASTContext &Ctx,
1977+
const UnsafeBufferUsageHandler &Handler, bool EmitSuggestions) {
19781978

19791979
struct GadgetFinderCallback : MatchFinder::MatchCallback {
19801980
FixableGadgetList FixableGadgets;
@@ -2068,7 +2068,7 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
20682068
// clang-format on
20692069
}
20702070

2071-
M.match(*D->getBody(), D->getASTContext());
2071+
M.match(*S, Ctx);
20722072
return {std::move(CB.FixableGadgets), std::move(CB.WarningGadgets),
20732073
std::move(CB.Tracker)};
20742074
}
@@ -3614,39 +3614,9 @@ class VariableGroupsManagerImpl : public VariableGroupsManager {
36143614
}
36153615
};
36163616

3617-
void clang::checkUnsafeBufferUsage(const Decl *D,
3618-
UnsafeBufferUsageHandler &Handler,
3619-
bool EmitSuggestions) {
3620-
#ifndef NDEBUG
3621-
Handler.clearDebugNotes();
3622-
#endif
3623-
3624-
assert(D && D->getBody());
3625-
// We do not want to visit a Lambda expression defined inside a method
3626-
// independently. Instead, it should be visited along with the outer method.
3627-
// FIXME: do we want to do the same thing for `BlockDecl`s?
3628-
if (const auto *fd = dyn_cast<CXXMethodDecl>(D)) {
3629-
if (fd->getParent()->isLambda() && fd->getParent()->isLocalClass())
3630-
return;
3631-
}
3632-
3633-
// Do not emit fixit suggestions for functions declared in an
3634-
// extern "C" block.
3635-
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
3636-
for (FunctionDecl *FReDecl : FD->redecls()) {
3637-
if (FReDecl->isExternC()) {
3638-
EmitSuggestions = false;
3639-
break;
3640-
}
3641-
}
3642-
}
3643-
3644-
WarningGadgetSets UnsafeOps;
3645-
FixableGadgetSets FixablesForAllVars;
3646-
3647-
auto [FixableGadgets, WarningGadgets, Tracker] =
3648-
findGadgets(D, Handler, EmitSuggestions);
3649-
3617+
void applyGadgets(const Decl *D, FixableGadgetList FixableGadgets,
3618+
WarningGadgetList WarningGadgets, DeclUseTracker Tracker,
3619+
UnsafeBufferUsageHandler &Handler, bool EmitSuggestions) {
36503620
if (!EmitSuggestions) {
36513621
// Our job is very easy without suggestions. Just warn about
36523622
// every problematic operation and consider it done. No need to deal
@@ -3690,8 +3660,10 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
36903660
if (WarningGadgets.empty())
36913661
return;
36923662

3693-
UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));
3694-
FixablesForAllVars = groupFixablesByVar(std::move(FixableGadgets));
3663+
WarningGadgetSets UnsafeOps =
3664+
groupWarningGadgetsByVar(std::move(WarningGadgets));
3665+
FixableGadgetSets FixablesForAllVars =
3666+
groupFixablesByVar(std::move(FixableGadgets));
36953667

36963668
std::map<const VarDecl *, FixItList> FixItsForVariableGroup;
36973669

@@ -3912,3 +3884,63 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
39123884
}
39133885
}
39143886
}
3887+
3888+
void clang::checkUnsafeBufferUsage(const Decl *D,
3889+
UnsafeBufferUsageHandler &Handler,
3890+
bool EmitSuggestions) {
3891+
#ifndef NDEBUG
3892+
Handler.clearDebugNotes();
3893+
#endif
3894+
3895+
assert(D);
3896+
3897+
SmallVector<Stmt *> Stmts;
3898+
3899+
// We do not want to visit a Lambda expression defined inside a method
3900+
// independently. Instead, it should be visited along with the outer method.
3901+
// FIXME: do we want to do the same thing for `BlockDecl`s?
3902+
if (const auto *fd = dyn_cast<CXXMethodDecl>(D)) {
3903+
if (fd->getParent()->isLambda() && fd->getParent()->isLocalClass())
3904+
return;
3905+
}
3906+
3907+
// Do not emit fixit suggestions for functions declared in an
3908+
// extern "C" block.
3909+
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
3910+
for (FunctionDecl *FReDecl : FD->redecls()) {
3911+
if (FReDecl->isExternC()) {
3912+
EmitSuggestions = false;
3913+
break;
3914+
}
3915+
}
3916+
3917+
Stmts.push_back(FD->getBody());
3918+
3919+
if (const auto *ID = dyn_cast<CXXConstructorDecl>(D)) {
3920+
for (const CXXCtorInitializer *CI : ID->inits()) {
3921+
Stmts.push_back(CI->getInit());
3922+
}
3923+
}
3924+
}
3925+
3926+
if (const auto *FD = dyn_cast<FieldDecl>(D)) {
3927+
// Visit in-class initializers for fields.
3928+
if (!FD->hasInClassInitializer())
3929+
return;
3930+
3931+
Stmts.push_back(FD->getInClassInitializer());
3932+
}
3933+
3934+
if (isa<BlockDecl>(D) || isa<ObjCMethodDecl>(D)) {
3935+
Stmts.push_back(D->getBody());
3936+
}
3937+
3938+
assert(!Stmts.empty());
3939+
3940+
for (Stmt *S : Stmts) {
3941+
auto [FixableGadgets, WarningGadgets, Tracker] =
3942+
findGadgets(S, D->getASTContext(), Handler, EmitSuggestions);
3943+
applyGadgets(D, std::move(FixableGadgets), std::move(WarningGadgets),
3944+
std::move(Tracker), Handler, EmitSuggestions);
3945+
}
3946+
}

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2510,6 +2510,14 @@ class CallableVisitor : public RecursiveASTVisitor<CallableVisitor> {
25102510
CallableVisitor(llvm::function_ref<void(const Decl *)> Callback)
25112511
: Callback(Callback) {}
25122512

2513+
bool VisitFieldDecl(FieldDecl *Node) {
2514+
if (cast<DeclContext>(Node->getParent())->isDependentContext())
2515+
return true; // Not to analyze dependent decl
2516+
if (Node->hasInClassInitializer())
2517+
Callback(Node);
2518+
return true;
2519+
}
2520+
25132521
bool VisitFunctionDecl(FunctionDecl *Node) {
25142522
if (cast<DeclContext>(Node)->isDependentContext())
25152523
return true; // Not to analyze dependent decl

clang/test/SemaCXX/warn-unsafe-buffer-usage-function-attr.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,42 @@ int testFoldExpression(Vs&&... v) {
111111
return (... + v); // expected-warning{{function introduces unsafe buffer manipulation}}
112112
}
113113

114+
struct HoldsUnsafeMembers {
115+
HoldsUnsafeMembers()
116+
: FromCtor(3), // expected-warning{{function introduces unsafe buffer manipulation}}
117+
FromCtor2{3} // expected-warning{{function introduces unsafe buffer manipulation}}
118+
{}
119+
120+
[[clang::unsafe_buffer_usage]]
121+
HoldsUnsafeMembers(int i)
122+
: FromCtor(i), // expected-warning{{function introduces unsafe buffer manipulation}}
123+
FromCtor2{i} // expected-warning{{function introduces unsafe buffer manipulation}}
124+
{}
125+
126+
HoldsUnsafeMembers(float f)
127+
: HoldsUnsafeMembers(0) {} // expected-warning{{function introduces unsafe buffer manipulation}}
128+
129+
UnsafeMembers FromCtor;
130+
UnsafeMembers FromCtor2;
131+
UnsafeMembers FromField{3}; // expected-warning{{function introduces unsafe buffer manipulation}}
132+
};
133+
134+
struct SubclassUnsafeMembers : public UnsafeMembers {
135+
SubclassUnsafeMembers()
136+
: UnsafeMembers(3) // expected-warning{{function introduces unsafe buffer manipulation}}
137+
{}
138+
139+
[[clang::unsafe_buffer_usage]]
140+
SubclassUnsafeMembers(int i)
141+
: UnsafeMembers(i) // expected-warning{{function introduces unsafe buffer manipulation}}
142+
{}
143+
};
144+
145+
struct AggregateUnsafeMembers {
146+
UnsafeMembers f1;
147+
UnsafeMembers f2{3}; // expected-warning{{function introduces unsafe buffer manipulation}}
148+
};
149+
114150
// https://github.com/llvm/llvm-project/issues/80482
115151
void testClassMembers() {
116152
UnsafeMembers(3); // expected-warning{{function introduces unsafe buffer manipulation}}
@@ -122,4 +158,35 @@ void testClassMembers() {
122158
UnsafeMembers()(); // expected-warning{{function introduces unsafe buffer manipulation}}
123159

124160
testFoldExpression(UnsafeMembers(), UnsafeMembers());
161+
162+
HoldsUnsafeMembers();
163+
HoldsUnsafeMembers(3); // expected-warning{{function introduces unsafe buffer manipulation}}
164+
165+
SubclassUnsafeMembers();
166+
SubclassUnsafeMembers(3); // expected-warning{{function introduces unsafe buffer manipulation}}
167+
168+
(void)AggregateUnsafeMembers{
169+
.f1 = UnsafeMembers(3), // expected-warning{{function introduces unsafe buffer manipulation}}
170+
};
171+
172+
(void)AggregateUnsafeMembers(3); // expected-warning{{function introduces unsafe buffer manipulation}}
173+
174+
// FIXME: This should generate a warning but InitListExpr construction is
175+
// treated as calling an implicit constructor, while ParentListInitExpr (the
176+
// one above) is not. So `MatchDescendantVisitor::shouldVisitImplicitCode()`
177+
// must return true for this to generate a warning. However that moves the
178+
// SourceLocation of warnings from field initializers that construct through
179+
// InitListExpr, preventing us from matching warnings against them.
180+
(void)AggregateUnsafeMembers{3};
125181
}
182+
183+
struct NotCalledHoldsUnsafeMembers {
184+
NotCalledHoldsUnsafeMembers()
185+
: FromCtor(3), // expected-warning{{function introduces unsafe buffer manipulation}}
186+
FromCtor2{3} // expected-warning{{function introduces unsafe buffer manipulation}}
187+
{}
188+
189+
UnsafeMembers FromCtor;
190+
UnsafeMembers FromCtor2;
191+
UnsafeMembers FromField{3}; // expected-warning{{function introduces unsafe buffer manipulation}}
192+
};

clang/test/SemaCXX/warn-unsafe-buffer-usage-in-container-span-construct.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,13 @@ namespace test_flag {
157157

158158
}
159159
} //namespace test_flag
160+
161+
struct HoldsStdSpan {
162+
char* Ptr;
163+
unsigned Size;
164+
std::span<char> Span{Ptr, Size}; // expected-warning{{the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information}}
165+
166+
HoldsStdSpan(char* P, unsigned S)
167+
: Span(P, S) // expected-warning{{the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information}}
168+
{}
169+
};

0 commit comments

Comments
 (0)