|
14 | 14 | #include "clang/ASTMatchers/ASTMatchFinder.h"
|
15 | 15 | #include "clang/Lex/Lexer.h"
|
16 | 16 | #include "clang/Lex/Preprocessor.h"
|
| 17 | +#include "llvm/ADT/APSInt.h" |
17 | 18 | #include "llvm/ADT/SmallVector.h"
|
18 | 19 | #include <memory>
|
19 | 20 | #include <optional>
|
@@ -234,6 +235,11 @@ AST_MATCHER_P(Stmt, notInSafeBufferOptOut, const UnsafeBufferUsageHandler *,
|
234 | 235 | return !Handler->isSafeBufferOptOut(Node.getBeginLoc());
|
235 | 236 | }
|
236 | 237 |
|
| 238 | +AST_MATCHER_P(Stmt, ignoreUnsafeBufferInContainer, |
| 239 | + const UnsafeBufferUsageHandler *, Handler) { |
| 240 | + return Handler->ignoreUnsafeBufferInContainer(Node.getBeginLoc()); |
| 241 | +} |
| 242 | + |
237 | 243 | AST_MATCHER_P(CastExpr, castSubExpr, internal::Matcher<Expr>, innerMatcher) {
|
238 | 244 | return innerMatcher.matches(*Node.getSubExpr(), Finder, Builder);
|
239 | 245 | }
|
@@ -325,6 +331,73 @@ isInUnspecifiedUntypedContext(internal::Matcher<Stmt> InnerMatcher) {
|
325 | 331 | // FIXME: Handle loop bodies.
|
326 | 332 | return stmt(anyOf(CompStmt, IfStmtThen, IfStmtElse));
|
327 | 333 | }
|
| 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 | +} |
328 | 401 | } // namespace clang::ast_matchers
|
329 | 402 |
|
330 | 403 | namespace {
|
@@ -594,6 +667,44 @@ class PointerArithmeticGadget : public WarningGadget {
|
594 | 667 | // FIXME: this gadge will need a fix-it
|
595 | 668 | };
|
596 | 669 |
|
| 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 | + |
597 | 708 | /// A pointer initialization expression of the form:
|
598 | 709 | /// \code
|
599 | 710 | /// int *p = q;
|
@@ -1203,6 +1314,10 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
|
1203 | 1314 | #define WARNING_GADGET(x) \
|
1204 | 1315 | allOf(x ## Gadget::matcher().bind(#x), \
|
1205 | 1316 | notInSafeBufferOptOut(&Handler)),
|
| 1317 | +#define WARNING_CONTAINER_GADGET(x) \ |
| 1318 | + allOf(x ## Gadget::matcher().bind(#x), \ |
| 1319 | + notInSafeBufferOptOut(&Handler), \ |
| 1320 | + unless(ignoreUnsafeBufferInContainer(&Handler))), |
1206 | 1321 | #include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
|
1207 | 1322 | // Avoid a hanging comma.
|
1208 | 1323 | unless(stmt())
|
|
0 commit comments