Skip to content

Commit 5c0d283

Browse files
authored
[-Wunsafe-buffer-usage] Respect the attribute for field and constructor initializers (llvm#91991) (#9537)
CXXCtorInitializers are not statements , 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) {} } Field initializers can be found by traversing CXXDefaultInitExprs. This catches warnings in places such as: struct C { P* Ptr; AnUnsafeCtor U{Ptr}; }; We add tests for explicit construction, for field initialization, base class constructor calls, delegated constructor calls, and aggregate initialization. Note that aggregate initialization is not fully covered where a field specifies an initializer and it's not overridden in the aggregate initialization, such as in: struct AggregateViaValueInit { UnsafeMembers f1; // FIXME: A construction of this class does initialize the field // through this initializer, so it should warn. Ideally it should // also point to where the site of the construction is in // testAggregateViaValueInit(). UnsafeMembers f2{3}; }; void testAggregateViaValueInit() { auto A = AggregateViaValueInit(); }; There are 3 tests for different types of aggregate initialization with FIXMEs documenting this future work. One attempt to fix this involved returning true from MatchDescendantVisitor::shouldVisitImplicitCode(), however, it 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. Issue llvm#80482 (cherry picked from commit a518ed2) Co-authored-by: Dana Jansens <[email protected]> (rdar://137999201)
1 parent b1f6197 commit 5c0d283

File tree

3 files changed

+224
-45
lines changed

3 files changed

+224
-45
lines changed

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 82 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ class MatchDescendantVisitor
171171
return VisitorBase::TraverseCXXTypeidExpr(Node);
172172
}
173173

174+
bool TraverseCXXDefaultInitExpr(CXXDefaultInitExpr *Node) {
175+
if (!TraverseStmt(Node->getExpr()))
176+
return false;
177+
return VisitorBase::TraverseCXXDefaultInitExpr(Node);
178+
}
179+
174180
bool TraverseStmt(Stmt *Node, DataRecursionQueue *Queue = nullptr) {
175181
if (!Node)
176182
return true;
@@ -1969,14 +1975,18 @@ class DerefSimplePtrArithFixableGadget : public FixableGadget {
19691975
};
19701976

19711977
/// Scan the function and return a list of gadgets found with provided kits.
1972-
static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker>
1973-
findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
1974-
bool EmitSuggestions) {
1978+
static void findGadgets(const Stmt *S, ASTContext &Ctx,
1979+
const UnsafeBufferUsageHandler &Handler,
1980+
bool EmitSuggestions, FixableGadgetList &FixableGadgets,
1981+
WarningGadgetList &WarningGadgets,
1982+
DeclUseTracker &Tracker) {
19751983

19761984
struct GadgetFinderCallback : MatchFinder::MatchCallback {
1977-
FixableGadgetList FixableGadgets;
1978-
WarningGadgetList WarningGadgets;
1979-
DeclUseTracker Tracker;
1985+
GadgetFinderCallback(FixableGadgetList &FixableGadgets,
1986+
WarningGadgetList &WarningGadgets,
1987+
DeclUseTracker &Tracker)
1988+
: FixableGadgets(FixableGadgets), WarningGadgets(WarningGadgets),
1989+
Tracker(Tracker) {}
19801990

19811991
void run(const MatchFinder::MatchResult &Result) override {
19821992
// In debug mode, assert that we've found exactly one gadget.
@@ -2017,10 +2027,14 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
20172027
assert(numFound >= 1 && "Gadgets not found in match result!");
20182028
assert(numFound <= 1 && "Conflicting bind tags in gadgets!");
20192029
}
2030+
2031+
FixableGadgetList &FixableGadgets;
2032+
WarningGadgetList &WarningGadgets;
2033+
DeclUseTracker &Tracker;
20202034
};
20212035

20222036
MatchFinder M;
2023-
GadgetFinderCallback CB;
2037+
GadgetFinderCallback CB{FixableGadgets, WarningGadgets, Tracker};
20242038

20252039
// clang-format off
20262040
M.addMatcher(
@@ -2065,9 +2079,7 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
20652079
// clang-format on
20662080
}
20672081

2068-
M.match(*D->getBody(), D->getASTContext());
2069-
return {std::move(CB.FixableGadgets), std::move(CB.WarningGadgets),
2070-
std::move(CB.Tracker)};
2082+
M.match(*S, Ctx);
20712083
}
20722084

20732085
// Compares AST nodes by source locations.
@@ -3612,39 +3624,9 @@ class VariableGroupsManagerImpl : public VariableGroupsManager {
36123624
}
36133625
};
36143626

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

3691-
UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));
3692-
FixablesForAllVars = groupFixablesByVar(std::move(FixableGadgets));
3673+
WarningGadgetSets UnsafeOps =
3674+
groupWarningGadgetsByVar(std::move(WarningGadgets));
3675+
FixableGadgetSets FixablesForAllVars =
3676+
groupFixablesByVar(std::move(FixableGadgets));
36933677

36943678
std::map<const VarDecl *, FixItList> FixItsForVariableGroup;
36953679

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

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

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,37 @@ 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 2{{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+
114145
// https://github.com/llvm/llvm-project/issues/80482
115146
void testClassMembers() {
116147
UnsafeMembers(3); // expected-warning{{function introduces unsafe buffer manipulation}}
@@ -122,4 +153,95 @@ void testClassMembers() {
122153
UnsafeMembers()(); // expected-warning{{function introduces unsafe buffer manipulation}}
123154

124155
testFoldExpression(UnsafeMembers(), UnsafeMembers());
156+
157+
HoldsUnsafeMembers();
158+
HoldsUnsafeMembers(3); // expected-warning{{function introduces unsafe buffer manipulation}}
159+
160+
SubclassUnsafeMembers();
161+
SubclassUnsafeMembers(3); // expected-warning{{function introduces unsafe buffer manipulation}}
162+
}
163+
164+
// Not an aggregate, so its constructor is not implicit code and will be
165+
// visited/checked for warnings.
166+
struct NotCalledHoldsUnsafeMembers {
167+
NotCalledHoldsUnsafeMembers()
168+
: FromCtor(3), // expected-warning{{function introduces unsafe buffer manipulation}}
169+
FromCtor2{3} // expected-warning{{function introduces unsafe buffer manipulation}}
170+
{}
171+
172+
UnsafeMembers FromCtor;
173+
UnsafeMembers FromCtor2;
174+
UnsafeMembers FromField{3}; // expected-warning{{function introduces unsafe buffer manipulation}}
175+
};
176+
177+
// An aggregate, so its constructor is implicit code. Since it's not called, it
178+
// is never generated.
179+
struct AggregateUnused {
180+
UnsafeMembers f1;
181+
// While this field would trigger the warning during initialization, since
182+
// it's unused, there's no code generated that does the initialization, so
183+
// no warning.
184+
UnsafeMembers f2{3};
185+
};
186+
187+
struct AggregateExplicitlyInitializedSafe {
188+
UnsafeMembers f1;
189+
// The warning is not fired as the field is always explicltly initialized
190+
// elsewhere. This initializer is never used.
191+
UnsafeMembers f2{3};
192+
};
193+
194+
void testAggregateExplicitlyInitializedSafe() {
195+
AggregateExplicitlyInitializedSafe A{
196+
.f2 = UnsafeMembers(), // A safe constructor.
197+
};
125198
}
199+
200+
struct AggregateExplicitlyInitializedUnsafe {
201+
UnsafeMembers f1;
202+
// The warning is not fired as the field is always explicltly initialized
203+
// elsewhere. This initializer is never used.
204+
UnsafeMembers f2{3};
205+
};
206+
207+
void testAggregateExplicitlyInitializedUnsafe() {
208+
AggregateExplicitlyInitializedUnsafe A{
209+
.f2 = UnsafeMembers(3), // expected-warning{{function introduces unsafe buffer manipulation}}
210+
};
211+
}
212+
213+
struct AggregateViaAggregateInit {
214+
UnsafeMembers f1;
215+
// FIXME: A construction of this class does initialize the field through
216+
// this initializer, so it should warn. Ideally it should also point to
217+
// where the site of the construction is in testAggregateViaAggregateInit().
218+
UnsafeMembers f2{3};
219+
};
220+
221+
void testAggregateViaAggregateInit() {
222+
AggregateViaAggregateInit A{};
223+
};
224+
225+
struct AggregateViaValueInit {
226+
UnsafeMembers f1;
227+
// FIXME: A construction of this class does initialize the field through
228+
// this initializer, so it should warn. Ideally it should also point to
229+
// where the site of the construction is in testAggregateViaValueInit().
230+
UnsafeMembers f2{3};
231+
};
232+
233+
void testAggregateViaValueInit() {
234+
auto A = AggregateViaValueInit();
235+
};
236+
237+
struct AggregateViaDefaultInit {
238+
UnsafeMembers f1;
239+
// FIXME: A construction of this class does initialize the field through
240+
// this initializer, so it should warn. Ideally it should also point to
241+
// where the site of the construction is in testAggregateViaValueInit().
242+
UnsafeMembers f2{3};
243+
};
244+
245+
void testAggregateViaDefaultInit() {
246+
AggregateViaDefaultInit A;
247+
};

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,23 @@ namespace test_flag {
154154

155155
}
156156
} //namespace test_flag
157+
158+
struct HoldsStdSpanAndInitializedInCtor {
159+
char* Ptr;
160+
unsigned Size;
161+
std::span<char> Span{Ptr, Size}; // no-warning (this code is unreachable)
162+
163+
HoldsStdSpanAndInitializedInCtor(char* P, unsigned S)
164+
: 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}}
165+
{}
166+
};
167+
168+
struct HoldsStdSpanAndNotInitializedInCtor {
169+
char* Ptr;
170+
unsigned Size;
171+
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}}
172+
173+
HoldsStdSpanAndNotInitializedInCtor(char* P, unsigned S)
174+
: Ptr(P), Size(S)
175+
{}
176+
};

0 commit comments

Comments
 (0)