Skip to content

Commit aab1f4a

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}} };
1 parent 84da0fe commit aab1f4a

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
@@ -1387,8 +1387,8 @@ class DerefSimplePtrArithFixableGadget : public FixableGadget {
13871387

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

13931393
struct GadgetFinderCallback : MatchFinder::MatchCallback {
13941394
FixableGadgetList FixableGadgets;
@@ -1483,7 +1483,7 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
14831483
// clang-format on
14841484
}
14851485

1486-
M.match(*D->getBody(), D->getASTContext());
1486+
M.match(*S, Ctx);
14871487
return {std::move(CB.FixableGadgets), std::move(CB.WarningGadgets),
14881488
std::move(CB.Tracker)};
14891489
}
@@ -3030,39 +3030,9 @@ class VariableGroupsManagerImpl : public VariableGroupsManager {
30303030
}
30313031
};
30323032

3033-
void clang::checkUnsafeBufferUsage(const Decl *D,
3034-
UnsafeBufferUsageHandler &Handler,
3035-
bool EmitSuggestions) {
3036-
#ifndef NDEBUG
3037-
Handler.clearDebugNotes();
3038-
#endif
3039-
3040-
assert(D && D->getBody());
3041-
// We do not want to visit a Lambda expression defined inside a method
3042-
// independently. Instead, it should be visited along with the outer method.
3043-
// FIXME: do we want to do the same thing for `BlockDecl`s?
3044-
if (const auto *fd = dyn_cast<CXXMethodDecl>(D)) {
3045-
if (fd->getParent()->isLambda() && fd->getParent()->isLocalClass())
3046-
return;
3047-
}
3048-
3049-
// Do not emit fixit suggestions for functions declared in an
3050-
// extern "C" block.
3051-
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
3052-
for (FunctionDecl *FReDecl : FD->redecls()) {
3053-
if (FReDecl->isExternC()) {
3054-
EmitSuggestions = false;
3055-
break;
3056-
}
3057-
}
3058-
}
3059-
3060-
WarningGadgetSets UnsafeOps;
3061-
FixableGadgetSets FixablesForAllVars;
3062-
3063-
auto [FixableGadgets, WarningGadgets, Tracker] =
3064-
findGadgets(D, Handler, EmitSuggestions);
3065-
3033+
void applyGadgets(const Decl *D, FixableGadgetList FixableGadgets,
3034+
WarningGadgetList WarningGadgets, DeclUseTracker Tracker,
3035+
UnsafeBufferUsageHandler &Handler, bool EmitSuggestions) {
30663036
if (!EmitSuggestions) {
30673037
// Our job is very easy without suggestions. Just warn about
30683038
// every problematic operation and consider it done. No need to deal
@@ -3106,8 +3076,10 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
31063076
if (WarningGadgets.empty())
31073077
return;
31083078

3109-
UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));
3110-
FixablesForAllVars = groupFixablesByVar(std::move(FixableGadgets));
3079+
WarningGadgetSets UnsafeOps =
3080+
groupWarningGadgetsByVar(std::move(WarningGadgets));
3081+
FixableGadgetSets FixablesForAllVars =
3082+
groupFixablesByVar(std::move(FixableGadgets));
31113083

31123084
std::map<const VarDecl *, FixItList> FixItsForVariableGroup;
31133085

@@ -3328,3 +3300,63 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
33283300
}
33293301
}
33303302
}
3303+
3304+
void clang::checkUnsafeBufferUsage(const Decl *D,
3305+
UnsafeBufferUsageHandler &Handler,
3306+
bool EmitSuggestions) {
3307+
#ifndef NDEBUG
3308+
Handler.clearDebugNotes();
3309+
#endif
3310+
3311+
assert(D);
3312+
3313+
SmallVector<Stmt *> Stmts;
3314+
3315+
// We do not want to visit a Lambda expression defined inside a method
3316+
// independently. Instead, it should be visited along with the outer method.
3317+
// FIXME: do we want to do the same thing for `BlockDecl`s?
3318+
if (const auto *fd = dyn_cast<CXXMethodDecl>(D)) {
3319+
if (fd->getParent()->isLambda() && fd->getParent()->isLocalClass())
3320+
return;
3321+
}
3322+
3323+
// Do not emit fixit suggestions for functions declared in an
3324+
// extern "C" block.
3325+
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
3326+
for (FunctionDecl *FReDecl : FD->redecls()) {
3327+
if (FReDecl->isExternC()) {
3328+
EmitSuggestions = false;
3329+
break;
3330+
}
3331+
}
3332+
3333+
Stmts.push_back(FD->getBody());
3334+
3335+
if (const auto *ID = dyn_cast<CXXConstructorDecl>(D)) {
3336+
for (const CXXCtorInitializer *CI : ID->inits()) {
3337+
Stmts.push_back(CI->getInit());
3338+
}
3339+
}
3340+
}
3341+
3342+
if (const auto *FD = dyn_cast<FieldDecl>(D)) {
3343+
// Visit in-class initializers for fields.
3344+
if (!FD->hasInClassInitializer())
3345+
return;
3346+
3347+
Stmts.push_back(FD->getInClassInitializer());
3348+
}
3349+
3350+
if (isa<BlockDecl>(D) || isa<ObjCMethodDecl>(D)) {
3351+
Stmts.push_back(D->getBody());
3352+
}
3353+
3354+
assert(!Stmts.empty());
3355+
3356+
for (Stmt *S : Stmts) {
3357+
auto [FixableGadgets, WarningGadgets, Tracker] =
3358+
findGadgets(S, D->getASTContext(), Handler, EmitSuggestions);
3359+
applyGadgets(D, std::move(FixableGadgets), std::move(WarningGadgets),
3360+
std::move(Tracker), Handler, EmitSuggestions);
3361+
}
3362+
}

clang/lib/Sema/AnalysisBasedWarnings.cpp

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

2471+
bool VisitFieldDecl(FieldDecl *Node) {
2472+
if (cast<DeclContext>(Node->getParent())->isDependentContext())
2473+
return true; // Not to analyze dependent decl
2474+
if (Node->hasInClassInitializer())
2475+
Callback(Node);
2476+
return true;
2477+
}
2478+
24712479
bool VisitFunctionDecl(FunctionDecl *Node) {
24722480
if (cast<DeclContext>(Node)->isDependentContext())
24732481
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
@@ -154,3 +154,13 @@ namespace test_flag {
154154

155155
}
156156
} //namespace test_flag
157+
158+
struct HoldsStdSpan {
159+
char* Ptr;
160+
unsigned Size;
161+
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}}
162+
163+
HoldsStdSpan(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+
};

0 commit comments

Comments
 (0)