Skip to content

Commit 9816863

Browse files
authored
[-Wunsafe-buffer-usage] Add a new warning for uses of std::span two-parameter constructors (llvm#77148)
Constructing `std::span` objects with the two parameter constructors could introduce mismatched bounds information, which defeats the purpose of using `std::span`. Therefore, we warn every use of such constructors. rdar://115817781
1 parent 80bfac4 commit 9816863

File tree

7 files changed

+304
-5
lines changed

7 files changed

+304
-5
lines changed

clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "clang/AST/Decl.h"
1818
#include "clang/AST/Stmt.h"
19+
#include "clang/Basic/SourceLocation.h"
1920
#include "llvm/Support/Debug.h"
2021

2122
namespace clang {
@@ -98,9 +99,14 @@ class UnsafeBufferUsageHandler {
9899
#endif
99100

100101
public:
101-
/// Returns a reference to the `Preprocessor`:
102+
/// \return true iff buffer safety is opt-out at `Loc`; false otherwise.
102103
virtual bool isSafeBufferOptOut(const SourceLocation &Loc) const = 0;
103104

105+
/// \return true iff unsafe uses in containers should NOT be reported at
106+
/// `Loc`; false otherwise.
107+
virtual bool
108+
ignoreUnsafeBufferInContainer(const SourceLocation &Loc) const = 0;
109+
104110
virtual std::string
105111
getUnsafeBufferUsageAttributeTextAt(SourceLocation Loc,
106112
StringRef WSSuffix = "") const = 0;

clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
#define WARNING_GADGET(name) GADGET(name)
1919
#endif
2020

21+
/// A `WARNING_GADGET` subset, where the code pattern of each gadget
22+
/// corresponds uses of a (possibly hardened) contatiner (e.g., `std::span`).
23+
#ifndef WARNING_CONTAINER_GADGET
24+
#define WARNING_CONTAINER_GADGET(name) WARNING_GADGET(name)
25+
#endif
26+
2127
/// Safe gadgets correspond to code patterns that aren't unsafe but need to be
2228
/// properly recognized in order to emit correct warnings and fixes over unsafe
2329
/// gadgets.
@@ -31,6 +37,7 @@ WARNING_GADGET(ArraySubscript)
3137
WARNING_GADGET(PointerArithmetic)
3238
WARNING_GADGET(UnsafeBufferUsageAttr)
3339
WARNING_GADGET(DataInvocation)
40+
WARNING_CONTAINER_GADGET(SpanTwoParamConstructor) // Uses of `std::span(arg0, arg1)`
3441
FIXABLE_GADGET(ULCArraySubscript) // `DRE[any]` in an Unspecified Lvalue Context
3542
FIXABLE_GADGET(DerefSimplePtrArithFixable)
3643
FIXABLE_GADGET(PointerDereference)
@@ -43,4 +50,5 @@ FIXABLE_GADGET(PointerInit)
4350

4451
#undef FIXABLE_GADGET
4552
#undef WARNING_GADGET
53+
#undef WARNING_CONTAINER_GADGET
4654
#undef GADGET

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12111,6 +12111,9 @@ def note_unsafe_buffer_variable_fixit_together : Note<
1211112111
"%select{|, and change %2 to safe types to make function %4 bounds-safe}3">;
1211212112
def note_safe_buffer_usage_suggestions_disabled : Note<
1211312113
"pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions">;
12114+
def warn_unsafe_buffer_usage_in_container : Warning<
12115+
"the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information">,
12116+
InGroup<UnsafeBufferUsageInContainer>, DefaultIgnore;
1211412117
#ifndef NDEBUG
1211512118
// Not a user-facing diagnostic. Useful for debugging false negatives in
1211612119
// -fsafe-buffer-usage-suggestions (i.e. lack of -Wunsafe-buffer-usage fixits).

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
#include "clang/ASTMatchers/ASTMatchFinder.h"
1515
#include "clang/Lex/Lexer.h"
1616
#include "clang/Lex/Preprocessor.h"
17+
#include "llvm/ADT/APSInt.h"
1718
#include "llvm/ADT/SmallVector.h"
1819
#include <memory>
1920
#include <optional>
20-
#include <sstream>
2121
#include <queue>
22+
#include <sstream>
2223

2324
using namespace llvm;
2425
using namespace clang;
@@ -232,6 +233,11 @@ AST_MATCHER_P(Stmt, notInSafeBufferOptOut, const UnsafeBufferUsageHandler *,
232233
return !Handler->isSafeBufferOptOut(Node.getBeginLoc());
233234
}
234235

236+
AST_MATCHER_P(Stmt, ignoreUnsafeBufferInContainer,
237+
const UnsafeBufferUsageHandler *, Handler) {
238+
return Handler->ignoreUnsafeBufferInContainer(Node.getBeginLoc());
239+
}
240+
235241
AST_MATCHER_P(CastExpr, castSubExpr, internal::Matcher<Expr>, innerMatcher) {
236242
return innerMatcher.matches(*Node.getSubExpr(), Finder, Builder);
237243
}
@@ -325,6 +331,73 @@ isInUnspecifiedUntypedContext(internal::Matcher<Stmt> InnerMatcher) {
325331
// FIXME: Handle loop bodies.
326332
return stmt(anyOf(CompStmt, IfStmtThen, IfStmtElse));
327333
}
334+
335+
// Given a two-param std::span construct call, matches iff the call has the
336+
// following forms:
337+
// 1. `std::span<T>{new T[n], n}`, where `n` is a literal or a DRE
338+
// 2. `std::span<T>{new T, 1}`
339+
// 3. `std::span<T>{&var, 1}`
340+
// 4. `std::span<T>{a, n}`, where `a` is of an array-of-T with constant size
341+
// `n`
342+
// 5. `std::span<T>{any, 0}`
343+
AST_MATCHER(CXXConstructExpr, isSafeSpanTwoParamConstruct) {
344+
assert(Node.getNumArgs() == 2 &&
345+
"expecting a two-parameter std::span constructor");
346+
const Expr *Arg0 = Node.getArg(0)->IgnoreImplicit();
347+
const Expr *Arg1 = Node.getArg(1)->IgnoreImplicit();
348+
auto HaveEqualConstantValues = [&Finder](const Expr *E0, const Expr *E1) {
349+
if (auto E0CV = E0->getIntegerConstantExpr(Finder->getASTContext()))
350+
if (auto E1CV = E1->getIntegerConstantExpr(Finder->getASTContext())) {
351+
return APSInt::compareValues(*E0CV, *E1CV) == 0;
352+
}
353+
return false;
354+
};
355+
auto AreSameDRE = [](const Expr *E0, const Expr *E1) {
356+
if (auto *DRE0 = dyn_cast<DeclRefExpr>(E0))
357+
if (auto *DRE1 = dyn_cast<DeclRefExpr>(E1)) {
358+
return DRE0->getDecl() == DRE1->getDecl();
359+
}
360+
return false;
361+
};
362+
std::optional<APSInt> Arg1CV =
363+
Arg1->getIntegerConstantExpr(Finder->getASTContext());
364+
365+
if (Arg1CV && Arg1CV->isZero())
366+
// Check form 5:
367+
return true;
368+
switch (Arg0->IgnoreImplicit()->getStmtClass()) {
369+
case Stmt::CXXNewExprClass:
370+
if (auto Size = cast<CXXNewExpr>(Arg0)->getArraySize()) {
371+
// Check form 1:
372+
return AreSameDRE((*Size)->IgnoreImplicit(), Arg1) ||
373+
HaveEqualConstantValues(*Size, Arg1);
374+
}
375+
// TODO: what's placeholder type? avoid it for now.
376+
if (!cast<CXXNewExpr>(Arg0)->hasPlaceholderType()) {
377+
// Check form 2:
378+
return Arg1CV && Arg1CV->isOne();
379+
}
380+
break;
381+
case Stmt::UnaryOperatorClass:
382+
if (cast<UnaryOperator>(Arg0)->getOpcode() ==
383+
UnaryOperator::Opcode::UO_AddrOf)
384+
// Check form 3:
385+
return Arg1CV && Arg1CV->isOne();
386+
break;
387+
default:
388+
break;
389+
}
390+
391+
QualType Arg0Ty = Arg0->IgnoreImplicit()->getType();
392+
393+
if (Arg0Ty->isConstantArrayType()) {
394+
const APInt &ConstArrSize = cast<ConstantArrayType>(Arg0Ty)->getSize();
395+
396+
// Check form 4:
397+
return Arg1CV && APSInt::compareValues(APSInt(ConstArrSize), *Arg1CV) == 0;
398+
}
399+
return false;
400+
}
328401
} // namespace clang::ast_matchers
329402

330403
namespace {
@@ -594,6 +667,44 @@ class PointerArithmeticGadget : public WarningGadget {
594667
// FIXME: this gadge will need a fix-it
595668
};
596669

670+
class SpanTwoParamConstructorGadget : public WarningGadget {
671+
static constexpr const char *const SpanTwoParamConstructorTag =
672+
"spanTwoParamConstructor";
673+
const CXXConstructExpr *Ctor; // the span constructor expression
674+
675+
public:
676+
SpanTwoParamConstructorGadget(const MatchFinder::MatchResult &Result)
677+
: WarningGadget(Kind::SpanTwoParamConstructor),
678+
Ctor(Result.Nodes.getNodeAs<CXXConstructExpr>(
679+
SpanTwoParamConstructorTag)) {}
680+
681+
static bool classof(const Gadget *G) {
682+
return G->getKind() == Kind::SpanTwoParamConstructor;
683+
}
684+
685+
static Matcher matcher() {
686+
auto HasTwoParamSpanCtorDecl = hasDeclaration(
687+
cxxConstructorDecl(hasDeclContext(isInStdNamespace()), hasName("span"),
688+
parameterCountIs(2)));
689+
690+
return stmt(cxxConstructExpr(HasTwoParamSpanCtorDecl,
691+
unless(isSafeSpanTwoParamConstruct()))
692+
.bind(SpanTwoParamConstructorTag));
693+
}
694+
695+
const Stmt *getBaseStmt() const override { return Ctor; }
696+
697+
DeclUseList getClaimedVarUseSites() const override {
698+
// If the constructor call is of the form `std::span{var, n}`, `var` is
699+
// considered an unsafe variable.
700+
if (auto *DRE = dyn_cast<DeclRefExpr>(Ctor->getArg(0))) {
701+
if (isa<VarDecl>(DRE->getDecl()))
702+
return {DRE};
703+
}
704+
return {};
705+
}
706+
};
707+
597708
/// A pointer initialization expression of the form:
598709
/// \code
599710
/// int *p = q;
@@ -1210,6 +1321,10 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
12101321
#define WARNING_GADGET(x) \
12111322
allOf(x ## Gadget::matcher().bind(#x), \
12121323
notInSafeBufferOptOut(&Handler)),
1324+
#define WARNING_CONTAINER_GADGET(x) \
1325+
allOf(x ## Gadget::matcher().bind(#x), \
1326+
notInSafeBufferOptOut(&Handler), \
1327+
unless(ignoreUnsafeBufferInContainer(&Handler))),
12131328
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
12141329
// Avoid a hanging comma.
12151330
unless(stmt())

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
#include "clang/AST/StmtCXX.h"
2727
#include "clang/AST/StmtObjC.h"
2828
#include "clang/AST/StmtVisitor.h"
29-
#include "clang/AST/RecursiveASTVisitor.h"
3029
#include "clang/AST/Type.h"
3130
#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
3231
#include "clang/Analysis/Analyses/CalledOnceCheck.h"
@@ -39,6 +38,7 @@
3938
#include "clang/Analysis/CFG.h"
4039
#include "clang/Analysis/CFGStmtMap.h"
4140
#include "clang/Basic/Diagnostic.h"
41+
#include "clang/Basic/DiagnosticSema.h"
4242
#include "clang/Basic/SourceLocation.h"
4343
#include "clang/Basic/SourceManager.h"
4444
#include "clang/Lex/Preprocessor.h"
@@ -2256,6 +2256,9 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
22562256
Range = UO->getSubExpr()->getSourceRange();
22572257
MsgParam = 1;
22582258
}
2259+
} else if (const auto *CtorExpr = dyn_cast<CXXConstructExpr>(Operation)) {
2260+
S.Diag(CtorExpr->getLocation(),
2261+
diag::warn_unsafe_buffer_usage_in_container);
22592262
} else {
22602263
if (isa<CallExpr>(Operation)) {
22612264
// note_unsafe_buffer_operation doesn't have this mode yet.
@@ -2334,6 +2337,10 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
23342337
return S.PP.isSafeBufferOptOut(S.getSourceManager(), Loc);
23352338
}
23362339

2340+
bool ignoreUnsafeBufferInContainer(const SourceLocation &Loc) const override {
2341+
return S.Diags.isIgnored(diag::warn_unsafe_buffer_usage_in_container, Loc);
2342+
}
2343+
23372344
// Returns the text representation of clang::unsafe_buffer_usage attribute.
23382345
// `WSSuffix` holds customized "white-space"s, e.g., newline or whilespace
23392346
// characters.
@@ -2498,6 +2505,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
24982505
if (!Diags.isIgnored(diag::warn_unsafe_buffer_operation,
24992506
Node->getBeginLoc()) ||
25002507
!Diags.isIgnored(diag::warn_unsafe_buffer_variable,
2508+
Node->getBeginLoc()) ||
2509+
!Diags.isIgnored(diag::warn_unsafe_buffer_usage_in_container,
25012510
Node->getBeginLoc())) {
25022511
clang::checkUnsafeBufferUsage(Node, R,
25032512
UnsafeBufferUsageShouldEmitSuggestions);
@@ -2508,7 +2517,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
25082517
// Emit per-function analysis-based warnings that require the whole-TU
25092518
// reasoning. Check if any of them is enabled at all before scanning the AST:
25102519
if (!Diags.isIgnored(diag::warn_unsafe_buffer_operation, SourceLocation()) ||
2511-
!Diags.isIgnored(diag::warn_unsafe_buffer_variable, SourceLocation())) {
2520+
!Diags.isIgnored(diag::warn_unsafe_buffer_variable, SourceLocation()) ||
2521+
!Diags.isIgnored(diag::warn_unsafe_buffer_usage_in_container,
2522+
SourceLocation())) {
25122523
CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU);
25132524
}
25142525
}

0 commit comments

Comments
 (0)