Skip to content

Commit c60b055

Browse files
committed
Reapply "Respect the [[clang::unsafe_buffer_usage]] attribute for field and constructor initializers (#91991)"
It was originally reverted due to an [existing bug](#116286). Relanding it as the bug was fixed by #116433.
1 parent 06246b2 commit c60b055

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
@@ -172,6 +172,12 @@ class MatchDescendantVisitor : public DynamicRecursiveASTVisitor {
172172
return DynamicRecursiveASTVisitor::TraverseCXXTypeidExpr(Node);
173173
}
174174

175+
bool TraverseCXXDefaultInitExpr(CXXDefaultInitExpr *Node) override {
176+
if (!TraverseStmt(Node->getExpr()))
177+
return false;
178+
return DynamicRecursiveASTVisitor::TraverseCXXDefaultInitExpr(Node);
179+
}
180+
175181
bool TraverseStmt(Stmt *Node) override {
176182
if (!Node)
177183
return true;
@@ -1987,14 +1993,18 @@ class DerefSimplePtrArithFixableGadget : public FixableGadget {
19871993
};
19881994

19891995
/// Scan the function and return a list of gadgets found with provided kits.
1990-
static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker>
1991-
findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
1992-
bool EmitSuggestions) {
1996+
static void findGadgets(const Stmt *S, ASTContext &Ctx,
1997+
const UnsafeBufferUsageHandler &Handler,
1998+
bool EmitSuggestions, FixableGadgetList &FixableGadgets,
1999+
WarningGadgetList &WarningGadgets,
2000+
DeclUseTracker &Tracker) {
19932001

19942002
struct GadgetFinderCallback : MatchFinder::MatchCallback {
1995-
FixableGadgetList FixableGadgets;
1996-
WarningGadgetList WarningGadgets;
1997-
DeclUseTracker Tracker;
2003+
GadgetFinderCallback(FixableGadgetList &FixableGadgets,
2004+
WarningGadgetList &WarningGadgets,
2005+
DeclUseTracker &Tracker)
2006+
: FixableGadgets(FixableGadgets), WarningGadgets(WarningGadgets),
2007+
Tracker(Tracker) {}
19982008

19992009
void run(const MatchFinder::MatchResult &Result) override {
20002010
// In debug mode, assert that we've found exactly one gadget.
@@ -2035,10 +2045,14 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
20352045
assert(numFound >= 1 && "Gadgets not found in match result!");
20362046
assert(numFound <= 1 && "Conflicting bind tags in gadgets!");
20372047
}
2048+
2049+
FixableGadgetList &FixableGadgets;
2050+
WarningGadgetList &WarningGadgets;
2051+
DeclUseTracker &Tracker;
20382052
};
20392053

20402054
MatchFinder M;
2041-
GadgetFinderCallback CB;
2055+
GadgetFinderCallback CB{FixableGadgets, WarningGadgets, Tracker};
20422056

20432057
// clang-format off
20442058
M.addMatcher(
@@ -2083,9 +2097,7 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
20832097
// clang-format on
20842098
}
20852099

2086-
M.match(*D->getBody(), D->getASTContext());
2087-
return {std::move(CB.FixableGadgets), std::move(CB.WarningGadgets),
2088-
std::move(CB.Tracker)};
2100+
M.match(*S, Ctx);
20892101
}
20902102

20912103
// Compares AST nodes by source locations.
@@ -3630,39 +3642,9 @@ class VariableGroupsManagerImpl : public VariableGroupsManager {
36303642
}
36313643
};
36323644

3633-
void clang::checkUnsafeBufferUsage(const Decl *D,
3634-
UnsafeBufferUsageHandler &Handler,
3635-
bool EmitSuggestions) {
3636-
#ifndef NDEBUG
3637-
Handler.clearDebugNotes();
3638-
#endif
3639-
3640-
assert(D && D->getBody());
3641-
// We do not want to visit a Lambda expression defined inside a method
3642-
// independently. Instead, it should be visited along with the outer method.
3643-
// FIXME: do we want to do the same thing for `BlockDecl`s?
3644-
if (const auto *fd = dyn_cast<CXXMethodDecl>(D)) {
3645-
if (fd->getParent()->isLambda() && fd->getParent()->isLocalClass())
3646-
return;
3647-
}
3648-
3649-
// Do not emit fixit suggestions for functions declared in an
3650-
// extern "C" block.
3651-
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
3652-
for (FunctionDecl *FReDecl : FD->redecls()) {
3653-
if (FReDecl->isExternC()) {
3654-
EmitSuggestions = false;
3655-
break;
3656-
}
3657-
}
3658-
}
3659-
3660-
WarningGadgetSets UnsafeOps;
3661-
FixableGadgetSets FixablesForAllVars;
3662-
3663-
auto [FixableGadgets, WarningGadgets, Tracker] =
3664-
findGadgets(D, Handler, EmitSuggestions);
3665-
3645+
void applyGadgets(const Decl *D, FixableGadgetList FixableGadgets,
3646+
WarningGadgetList WarningGadgets, DeclUseTracker Tracker,
3647+
UnsafeBufferUsageHandler &Handler, bool EmitSuggestions) {
36663648
if (!EmitSuggestions) {
36673649
// Our job is very easy without suggestions. Just warn about
36683650
// every problematic operation and consider it done. No need to deal
@@ -3706,8 +3688,10 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
37063688
if (WarningGadgets.empty())
37073689
return;
37083690

3709-
UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));
3710-
FixablesForAllVars = groupFixablesByVar(std::move(FixableGadgets));
3691+
WarningGadgetSets UnsafeOps =
3692+
groupWarningGadgetsByVar(std::move(WarningGadgets));
3693+
FixableGadgetSets FixablesForAllVars =
3694+
groupFixablesByVar(std::move(FixableGadgets));
37113695

37123696
std::map<const VarDecl *, FixItList> FixItsForVariableGroup;
37133697

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

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

175175
}
176176
} //namespace test_flag
177+
178+
struct HoldsStdSpanAndInitializedInCtor {
179+
char* Ptr;
180+
unsigned Size;
181+
std::span<char> Span{Ptr, Size}; // no-warning (this code is unreachable)
182+
183+
HoldsStdSpanAndInitializedInCtor(char* P, unsigned S)
184+
: 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}}
185+
{}
186+
};
187+
188+
struct HoldsStdSpanAndNotInitializedInCtor {
189+
char* Ptr;
190+
unsigned Size;
191+
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}}
192+
193+
HoldsStdSpanAndNotInitializedInCtor(char* P, unsigned S)
194+
: Ptr(P), Size(S)
195+
{}
196+
};

0 commit comments

Comments
 (0)