Skip to content

Commit a518ed2

Browse files
authored
Respect the [[clang::unsafe_buffer_usage]] attribute for field and constructor initializers (#91991)
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 #80482
1 parent ca1154d commit a518ed2

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;
@@ -1972,14 +1978,18 @@ class DerefSimplePtrArithFixableGadget : public FixableGadget {
19721978
};
19731979

19741980
/// Scan the function and return a list of gadgets found with provided kits.
1975-
static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker>
1976-
findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
1977-
bool EmitSuggestions) {
1981+
static void findGadgets(const Stmt *S, ASTContext &Ctx,
1982+
const UnsafeBufferUsageHandler &Handler,
1983+
bool EmitSuggestions, FixableGadgetList &FixableGadgets,
1984+
WarningGadgetList &WarningGadgets,
1985+
DeclUseTracker &Tracker) {
19781986

19791987
struct GadgetFinderCallback : MatchFinder::MatchCallback {
1980-
FixableGadgetList FixableGadgets;
1981-
WarningGadgetList WarningGadgets;
1982-
DeclUseTracker Tracker;
1988+
GadgetFinderCallback(FixableGadgetList &FixableGadgets,
1989+
WarningGadgetList &WarningGadgets,
1990+
DeclUseTracker &Tracker)
1991+
: FixableGadgets(FixableGadgets), WarningGadgets(WarningGadgets),
1992+
Tracker(Tracker) {}
19831993

19841994
void run(const MatchFinder::MatchResult &Result) override {
19851995
// In debug mode, assert that we've found exactly one gadget.
@@ -2020,10 +2030,14 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
20202030
assert(numFound >= 1 && "Gadgets not found in match result!");
20212031
assert(numFound <= 1 && "Conflicting bind tags in gadgets!");
20222032
}
2033+
2034+
FixableGadgetList &FixableGadgets;
2035+
WarningGadgetList &WarningGadgets;
2036+
DeclUseTracker &Tracker;
20232037
};
20242038

20252039
MatchFinder M;
2026-
GadgetFinderCallback CB;
2040+
GadgetFinderCallback CB{FixableGadgets, WarningGadgets, Tracker};
20272041

20282042
// clang-format off
20292043
M.addMatcher(
@@ -2068,9 +2082,7 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
20682082
// clang-format on
20692083
}
20702084

2071-
M.match(*D->getBody(), D->getASTContext());
2072-
return {std::move(CB.FixableGadgets), std::move(CB.WarningGadgets),
2073-
std::move(CB.Tracker)};
2085+
M.match(*S, Ctx);
20742086
}
20752087

20762088
// Compares AST nodes by source locations.
@@ -3614,39 +3626,9 @@ class VariableGroupsManagerImpl : public VariableGroupsManager {
36143626
}
36153627
};
36163628

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-
3629+
void applyGadgets(const Decl *D, FixableGadgetList FixableGadgets,
3630+
WarningGadgetList WarningGadgets, DeclUseTracker Tracker,
3631+
UnsafeBufferUsageHandler &Handler, bool EmitSuggestions) {
36503632
if (!EmitSuggestions) {
36513633
// Our job is very easy without suggestions. Just warn about
36523634
// every problematic operation and consider it done. No need to deal
@@ -3690,8 +3672,10 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
36903672
if (WarningGadgets.empty())
36913673
return;
36923674

3693-
UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));
3694-
FixablesForAllVars = groupFixablesByVar(std::move(FixableGadgets));
3675+
WarningGadgetSets UnsafeOps =
3676+
groupWarningGadgetsByVar(std::move(WarningGadgets));
3677+
FixableGadgetSets FixablesForAllVars =
3678+
groupFixablesByVar(std::move(FixableGadgets));
36953679

36963680
std::map<const VarDecl *, FixItList> FixItsForVariableGroup;
36973681

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

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
@@ -157,3 +157,23 @@ namespace test_flag {
157157

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

0 commit comments

Comments
 (0)