Skip to content

Commit 2943673

Browse files
authored
[NFC][-Wunsafe-buffer-usage] Refactor safe pattern check for pointer-size pairs (#145626)
Refactor the safe pattern analysis of pointer and size expression pairs so that the check can be re-used in more places. For example, it can be used to check whether the following cases are safe: - `std::span<T>{ptr, size} // span construction` - `snprintf(ptr, size, "%s", ...) // unsafe libc call` - `printf("%.*s", size, ptr) // unsafe libc call`
1 parent 7c30897 commit 2943673

File tree

2 files changed

+112
-101
lines changed

2 files changed

+112
-101
lines changed

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 109 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -453,22 +453,110 @@ static bool areEqualIntegers(const Expr *E1, const Expr *E2, ASTContext &Ctx) {
453453
}
454454
}
455455

456+
// Providing that `Ptr` is a pointer and `Size` is an unsigned-integral
457+
// expression, returns true iff they follow one of the following safe
458+
// patterns:
459+
// 1. Ptr is `DRE.data()` and Size is `DRE.size()`, where DRE is a hardened
460+
// container or view;
461+
//
462+
// 2. Ptr is `a` and Size is `n`, where `a` is of an array-of-T with constant
463+
// size `n`;
464+
//
465+
// 3. Ptr is `&var` and Size is `1`; or
466+
// Ptr is `std::addressof(...)` and Size is `1`;
467+
//
468+
// 4. Size is `0`;
469+
static bool isPtrBufferSafe(const Expr *Ptr, const Expr *Size,
470+
ASTContext &Ctx) {
471+
// Pattern 1:
472+
if (auto *MCEPtr = dyn_cast<CXXMemberCallExpr>(Ptr->IgnoreParenImpCasts()))
473+
if (auto *MCESize =
474+
dyn_cast<CXXMemberCallExpr>(Size->IgnoreParenImpCasts())) {
475+
auto *DREOfPtr = dyn_cast<DeclRefExpr>(
476+
MCEPtr->getImplicitObjectArgument()->IgnoreParenImpCasts());
477+
auto *DREOfSize = dyn_cast<DeclRefExpr>(
478+
MCESize->getImplicitObjectArgument()->IgnoreParenImpCasts());
479+
480+
if (!DREOfPtr || !DREOfSize)
481+
return false; // not in safe pattern
482+
// We need to make sure 'a' is identical to 'b' for 'a.data()' and
483+
// 'b.size()' otherwise we do not know they match:
484+
if (DREOfPtr->getDecl() != DREOfSize->getDecl())
485+
return false;
486+
if (MCEPtr->getMethodDecl()->getName() != "data")
487+
return false;
488+
// `MCEPtr->getRecordDecl()` must be non-null as `DREOfPtr` is non-null:
489+
if (!MCEPtr->getRecordDecl()->isInStdNamespace())
490+
return false;
491+
492+
auto *ObjII = MCEPtr->getRecordDecl()->getIdentifier();
493+
494+
if (!ObjII)
495+
return false;
496+
497+
bool AcceptSizeBytes = Ptr->getType()->getPointeeType()->isCharType();
498+
499+
if (!((AcceptSizeBytes &&
500+
MCESize->getMethodDecl()->getName() == "size_bytes") ||
501+
// Note here the pointer must be a pointer-to-char type unless there
502+
// is explicit casting. If there is explicit casting, this branch
503+
// is unreachable. Thus, at this branch "size" and "size_bytes" are
504+
// equivalent as the pointer is a char pointer:
505+
MCESize->getMethodDecl()->getName() == "size"))
506+
return false;
507+
508+
return llvm::is_contained({SIZED_CONTAINER_OR_VIEW_LIST},
509+
ObjII->getName());
510+
}
511+
512+
Expr::EvalResult ER;
513+
514+
// Pattern 2-4:
515+
if (Size->EvaluateAsInt(ER, Ctx)) {
516+
// Pattern 2:
517+
if (auto *DRE = dyn_cast<DeclRefExpr>(Ptr->IgnoreParenImpCasts())) {
518+
if (auto *CAT = Ctx.getAsConstantArrayType(DRE->getType())) {
519+
llvm::APSInt SizeInt = ER.Val.getInt();
520+
521+
return llvm::APSInt::compareValues(
522+
SizeInt, llvm::APSInt(CAT->getSize(), true)) == 0;
523+
}
524+
return false;
525+
}
526+
527+
// Pattern 3:
528+
if (ER.Val.getInt().isOne()) {
529+
if (auto *UO = dyn_cast<UnaryOperator>(Ptr->IgnoreParenImpCasts()))
530+
return UO && UO->getOpcode() == UnaryOperator::Opcode::UO_AddrOf;
531+
if (auto *CE = dyn_cast<CallExpr>(Ptr->IgnoreParenImpCasts())) {
532+
auto *FnDecl = CE->getDirectCallee();
533+
534+
return FnDecl && FnDecl->getNameAsString() == "addressof" &&
535+
FnDecl->isInStdNamespace();
536+
}
537+
return false;
538+
}
539+
// Pattern 4:
540+
if (ER.Val.getInt().isZero())
541+
return true;
542+
}
543+
return false;
544+
}
545+
456546
// Given a two-param std::span construct call, matches iff the call has the
457547
// following forms:
458548
// 1. `std::span<T>{new T[n], n}`, where `n` is a literal or a DRE
459549
// 2. `std::span<T>{new T, 1}`
460-
// 3. `std::span<T>{&var, 1}` or `std::span<T>{std::addressof(...), 1}`
461-
// 4. `std::span<T>{a, n}`, where `a` is of an array-of-T with constant size
462-
// `n`
463-
// 5. `std::span<T>{any, 0}`
464-
// 6. `std::span<T>{ (char *)f(args), args[N] * arg*[M]}`, where
550+
// 3. `std::span<T>{ (char *)f(args), args[N] * arg*[M]}`, where
465551
// `f` is a function with attribute `alloc_size(N, M)`;
466552
// `args` represents the list of arguments;
467553
// `N, M` are parameter indexes to the allocating element number and size.
468554
// Sometimes, there is only one parameter index representing the total
469555
// size.
470-
// 7. `std::span<T>{x.begin(), x.end()}` where `x` is an object in the
556+
// 4. `std::span<T>{x.begin(), x.end()}` where `x` is an object in the
471557
// SIZED_CONTAINER_OR_VIEW_LIST.
558+
// 5. `isPtrBufferSafe` returns true for the two arguments of the span
559+
// constructor
472560
static bool isSafeSpanTwoParamConstruct(const CXXConstructExpr &Node,
473561
ASTContext &Ctx) {
474562
assert(Node.getNumArgs() == 2 &&
@@ -495,7 +583,7 @@ static bool isSafeSpanTwoParamConstruct(const CXXConstructExpr &Node,
495583
// Check form 5:
496584
return true;
497585

498-
// Check forms 1-3:
586+
// Check forms 1-2:
499587
switch (Arg0->getStmtClass()) {
500588
case Stmt::CXXNewExprClass:
501589
if (auto Size = cast<CXXNewExpr>(Arg0)->getArraySize()) {
@@ -509,35 +597,11 @@ static bool isSafeSpanTwoParamConstruct(const CXXConstructExpr &Node,
509597
return Arg1CV && Arg1CV->isOne();
510598
}
511599
break;
512-
case Stmt::UnaryOperatorClass:
513-
if (cast<UnaryOperator>(Arg0)->getOpcode() ==
514-
UnaryOperator::Opcode::UO_AddrOf)
515-
// Check form 3:
516-
return Arg1CV && Arg1CV->isOne();
517-
break;
518-
case Stmt::CallExprClass:
519-
// Check form 3:
520-
if (const auto *CE = dyn_cast<CallExpr>(Arg0)) {
521-
const auto FnDecl = CE->getDirectCallee();
522-
if (FnDecl && FnDecl->getNameAsString() == "addressof" &&
523-
FnDecl->isInStdNamespace()) {
524-
return Arg1CV && Arg1CV->isOne();
525-
}
526-
}
527-
break;
528600
default:
529601
break;
530602
}
531603

532-
QualType Arg0Ty = Arg0->IgnoreImplicit()->getType();
533-
534-
if (auto *ConstArrTy = Ctx.getAsConstantArrayType(Arg0Ty)) {
535-
const llvm::APSInt ConstArrSize = llvm::APSInt(ConstArrTy->getSize());
536-
537-
// Check form 4:
538-
return Arg1CV && llvm::APSInt::compareValues(ConstArrSize, *Arg1CV) == 0;
539-
}
540-
// Check form 6:
604+
// Check form 3:
541605
if (auto CCast = dyn_cast<CStyleCastExpr>(Arg0)) {
542606
if (!CCast->getType()->isPointerType())
543607
return false;
@@ -566,7 +630,7 @@ static bool isSafeSpanTwoParamConstruct(const CXXConstructExpr &Node,
566630
}
567631
}
568632
}
569-
// Check form 7:
633+
// Check form 4:
570634
auto IsMethodCallToSizedObject = [](const Stmt *Node, StringRef MethodName) {
571635
if (const auto *MC = dyn_cast<CXXMemberCallExpr>(Node)) {
572636
const auto *MD = MC->getMethodDecl();
@@ -592,7 +656,9 @@ static bool isSafeSpanTwoParamConstruct(const CXXConstructExpr &Node,
592656
cast<CXXMemberCallExpr>(Arg1)
593657
->getImplicitObjectArgument()
594658
->IgnoreParenImpCasts());
595-
return false;
659+
660+
// Check 5:
661+
return isPtrBufferSafe(Arg0, Arg1, Ctx);
596662
}
597663

598664
static bool isSafeArraySubscript(const ArraySubscriptExpr &Node,
@@ -1052,24 +1118,11 @@ static bool hasUnsafePrintfStringArg(const CallExpr &Node, ASTContext &Ctx,
10521118
return false;
10531119
}
10541120

1055-
// This matcher requires that it is known that the callee `isNormalPrintf`.
1056-
// Then it matches if the first two arguments of the call is a pointer and an
1057-
// integer and they are not in a safe pattern.
1058-
//
1059-
// For the first two arguments: `ptr` and `size`, they are safe if in the
1060-
// following patterns:
1061-
//
1062-
// Pattern 1:
1063-
// ptr := DRE.data();
1064-
// size:= DRE.size()/DRE.size_bytes()
1065-
// And DRE is a hardened container or view.
1066-
//
1067-
// Pattern 2:
1068-
// ptr := Constant-Array-DRE;
1069-
// size:= any expression that has compile-time constant value equivalent to
1070-
// sizeof (Constant-Array-DRE)
1071-
static bool hasUnsafeSnprintfBuffer(const CallExpr &Node,
1072-
const ASTContext &Ctx) {
1121+
// This function requires that it is known that the callee `isNormalPrintf`.
1122+
// It returns true iff the first two arguments of the call is a pointer
1123+
// `Ptr` and an unsigned integer `Size` and they are NOT safe, i.e.,
1124+
// `!isPtrBufferSafe(Ptr, Size)`.
1125+
static bool hasUnsafeSnprintfBuffer(const CallExpr &Node, ASTContext &Ctx) {
10731126
const FunctionDecl *FD = Node.getDirectCallee();
10741127

10751128
assert(FD && "It should have been checked that FD is non-null.");
@@ -1085,57 +1138,12 @@ static bool hasUnsafeSnprintfBuffer(const CallExpr &Node,
10851138
QualType FirstPteTy = FirstParmTy->castAs<PointerType>()->getPointeeType();
10861139
const Expr *Buf = Node.getArg(0), *Size = Node.getArg(1);
10871140

1088-
if (FirstPteTy.isConstQualified() || !Buf->getType()->isPointerType() ||
1089-
!Size->getType()->isIntegerType())
1141+
if (FirstPteTy.isConstQualified() || !FirstPteTy->isAnyCharacterType() ||
1142+
!Buf->getType()->isPointerType() ||
1143+
!Size->getType()->isUnsignedIntegerType())
10901144
return false; // not an snprintf call
10911145

1092-
// Pattern 1:
1093-
static StringRef SizedObjs[] = {SIZED_CONTAINER_OR_VIEW_LIST};
1094-
Buf = Buf->IgnoreParenImpCasts();
1095-
Size = Size->IgnoreParenImpCasts();
1096-
if (auto *MCEPtr = dyn_cast<CXXMemberCallExpr>(Buf))
1097-
if (auto *MCESize = dyn_cast<CXXMemberCallExpr>(Size)) {
1098-
auto *DREOfPtr = dyn_cast<DeclRefExpr>(
1099-
MCEPtr->getImplicitObjectArgument()->IgnoreParenImpCasts());
1100-
auto *DREOfSize = dyn_cast<DeclRefExpr>(
1101-
MCESize->getImplicitObjectArgument()->IgnoreParenImpCasts());
1102-
1103-
if (!DREOfPtr || !DREOfSize)
1104-
return true; // not in safe pattern
1105-
if (DREOfPtr->getDecl() != DREOfSize->getDecl())
1106-
return true; // not in safe pattern
1107-
if (MCEPtr->getMethodDecl()->getName() != "data")
1108-
return true; // not in safe pattern
1109-
1110-
if (MCESize->getMethodDecl()->getName() == "size_bytes" ||
1111-
// Note here the pointer must be a pointer-to-char type unless there
1112-
// is explicit casting. If there is explicit casting, this branch
1113-
// is unreachable. Thus, at this branch "size" and "size_bytes" are
1114-
// equivalent as the pointer is a char pointer:
1115-
MCESize->getMethodDecl()->getName() == "size")
1116-
for (StringRef SizedObj : SizedObjs)
1117-
if (MCEPtr->getRecordDecl()->isInStdNamespace() &&
1118-
MCEPtr->getRecordDecl()->getCanonicalDecl()->getName() ==
1119-
SizedObj)
1120-
return false; // It is in fact safe
1121-
}
1122-
1123-
// Pattern 2:
1124-
if (auto *DRE = dyn_cast<DeclRefExpr>(Buf->IgnoreParenImpCasts())) {
1125-
if (auto *CAT = Ctx.getAsConstantArrayType(DRE->getType())) {
1126-
Expr::EvalResult ER;
1127-
// The array element type must be compatible with `char` otherwise an
1128-
// explicit cast will be needed, which will make this check unreachable.
1129-
// Therefore, the array extent is same as its' bytewise size.
1130-
if (Size->EvaluateAsInt(ER, Ctx)) {
1131-
llvm::APSInt EVal = ER.Val.getInt(); // Size must have integer type
1132-
1133-
return llvm::APSInt::compareValues(
1134-
EVal, llvm::APSInt(CAT->getSize(), true)) != 0;
1135-
}
1136-
}
1137-
}
1138-
return true; // ptr and size are not in safe pattern
1146+
return !isPtrBufferSafe(Buf, Size, Ctx);
11391147
}
11401148
} // namespace libc_func_matchers
11411149

clang/test/SemaCXX/warn-unsafe-buffer-usage-libc-functions.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,13 @@ void safe_examples(std::string s1, int *p) {
125125
fprintf((FILE*)0, s1.c_str(), __PRETTY_FUNCTION__, *p, "hello", s1.c_str()); // no warn
126126

127127
char a[10];
128+
char c = 'c';
128129

129130
snprintf(a, sizeof a, "%s%d%s%p%s", __PRETTY_FUNCTION__, *p, "hello", s1.c_str()); // no warn
130131
snprintf(a, sizeof(decltype(a)), "%s%d%s%p%s", __PRETTY_FUNCTION__, *p, "hello", s1.c_str()); // no warn
131132
snprintf(a, 10, "%s%d%s%p%s", __PRETTY_FUNCTION__, *p, "hello", s1.c_str()); // no warn
133+
snprintf(&c, 1, "%s%d%s%p%s", __PRETTY_FUNCTION__, *p, "hello", s1.c_str()); // no warn
134+
snprintf(nullptr, 0, "%s%d%s%p%s", __PRETTY_FUNCTION__, *p, "hello", s1.c_str()); // no warn
132135
}
133136

134137

0 commit comments

Comments
 (0)