From 088d45f1605aa7b874ecf13d801426b9ff92a9cb Mon Sep 17 00:00:00 2001 From: Mandeep Singh Grang Date: Tue, 27 Jul 2021 08:30:43 -0700 Subject: [PATCH] [BoundsWidening] Use invertibility to support bounds widening in loops (#1137) An expression that modifies an LValue is said to be invertible w.r.t. the LValue if we can write an expression in terms of the original value of the LValue before the modification. For example, the expression x + 1 is invertible w.r.t x because we can write this expression in terms of the original value of x which is (x - 1) + 1. In this PR, we use invertibility of statements to support bounds widening in loops. More specifically, if a statement modifies a variable that occurs in the bounds expression of a null-terminated array then instead of killing its bounds at that statement we use invertibility of the statement to try to write the widened bounds in terms of the original value of the variable. --- .../clang/Sema/BoundsWideningAnalysis.h | 59 +++- clang/lib/Sema/BoundsWideningAnalysis.cpp | 286 +++++++++++++++--- .../CheckedC/inferred-bounds/bounds-context.c | 4 +- .../widened-bounds-invertibility.c | 209 +++++++++++++ .../CheckedC/inferred-bounds/widened-bounds.c | 14 +- 5 files changed, 516 insertions(+), 56 deletions(-) create mode 100644 clang/test/CheckedC/inferred-bounds/widened-bounds-invertibility.c diff --git a/clang/include/clang/Sema/BoundsWideningAnalysis.h b/clang/include/clang/Sema/BoundsWideningAnalysis.h index 5d893b55293d..d304306499a3 100644 --- a/clang/include/clang/Sema/BoundsWideningAnalysis.h +++ b/clang/include/clang/Sema/BoundsWideningAnalysis.h @@ -18,6 +18,7 @@ #include "clang/AST/ExprUtils.h" #include "clang/AST/PrettyPrinter.h" #include "clang/Analysis/Analyses/PostOrderCFGView.h" +#include "clang/Sema/BoundsUtils.h" #include "clang/Sema/CheckedCAnalysesPrepass.h" #include "clang/Sema/Sema.h" @@ -54,6 +55,14 @@ namespace clang { // for printing the blocks in a deterministic order. using OrderedBlocksTy = std::vector; + // A tuple (Tup) of three elements such that we need to replace Tup[0] with + // Tup[1] in the bounds of every pointer in Tup[3]. + using LValuesToReplaceInBoundsTy = std::tuple; + + // A mapping of invertible statements to LValuesToReplaceInBoundsTy. + using InvertibleStmtMapTy = llvm::DenseMap; + } // end namespace clang namespace clang { @@ -128,20 +137,20 @@ namespace clang { // Get the set of variables that are pointers to null-terminated arrays and // in whose lower bounds expressions the variables in Vars occur. // @param[in] Vars is a set of variables. - // @param[out] NullTermPtrsWithVarsInLowerBounds is a set of variables that - // are pointers to null-terminated arrays and in whose lower bounds - // expressions the variables in Vars occur. - void GetNullTermPtrsWithVarsInLowerBounds( - VarSetTy &Vars, VarSetTy &NullTermPtrsWithVarsInLowerBounds) const; + // @param[out] PtrsWithVarsInLowerBounds is a set of variables that are + // pointers to null-terminated arrays and in whose lower bounds expressions + // the variables in Vars occur. + void GetPtrsWithVarsInLowerBounds( + VarSetTy &Vars, VarSetTy &PtrsWithVarsInLowerBounds) const; // Get the set of variables that are pointers to null-terminated arrays and // in whose upper bounds expressions the variables in Vars occur. // @param[in] Vars is a set of variables. - // @param[out] NullTermPtrsWithVarsInLowerBounds is a set of variables that - // are pointers to null-terminated arrays and in whose upper bounds - // expressions the variables in Vars occur. - void GetNullTermPtrsWithVarsInUpperBounds( - VarSetTy &Vars, VarSetTy &NullTermPtrsWithVarsInUpperBounds) const; + // @param[out] PtrsWithVarsInLowerBounds is a set of variables that are + // pointers to null-terminated arrays and in whose upper bounds expressions + // the variables in Vars occur. + void GetPtrsWithVarsInUpperBounds( + VarSetTy &Vars, VarSetTy &PtrsWithVarsInUpperBounds) const; // Add an offset to a given expression. // @param[in] E is the given expression. @@ -287,6 +296,14 @@ namespace clang { // The Out set of the previous statement of a statement in a block. BoundsMapTy OutOfPrevStmt; + // This stores the adjusted bounds after we have determined the + // invertibility of the current statement that modifies variables + // occurring in bounds expressions. + StmtBoundsMapTy AdjustedBounds; + + // A mapping of invertible statements to LValuesToReplaceInBoundsTy. + InvertibleStmtMapTy InvertibleStmts; + ElevatedCFGBlock(const CFGBlock *B) : Block(B) {} }; // end of ElevatedCFGBlock class. @@ -492,6 +509,28 @@ namespace clang { const Stmt *CurrStmt, BoundsMapTy &VarsAndBounds); + // Check if CurrStmt is invertible w.r.t. the variables modified by + // CurrStmt. + // Note: This function modifies the set EB->InvertibleStmts. + // @param[in] EB is the current ElevatedCFGBlock. + // @param[in] CurrStmt is the current statement. + // @param[in] PtrsWithAffectedBounds is the set of variables that are + // pointers to null-terminated arrays whose bounds are affected by + // modification to variables that occur in their bounds expressions by + // CurrStmt. + void CheckStmtInvertibility(ElevatedCFGBlock *EB, + const Stmt *CurrStmt, + VarSetTy PtrsWithAffectedBounds) const; + + // Update the bounds in StmtOut with the adjusted bounds for the current + // statement, if they exist. + // @param[in] EB is the current ElevatedCFGBlock. + // @param[in] CurrStmt is the current statement. + // @param[out] StmtOut is updated with the adjusted bounds for CurrStmt, if + // they exist. + void UpdateAdjustedBounds(ElevatedCFGBlock *EB, const Stmt *CurrStmt, + BoundsMapTy &StmtOut) const; + // Order the blocks by block number to get a deterministic iteration order // for the blocks. // @return Blocks ordered by block number from higher to lower since block diff --git a/clang/lib/Sema/BoundsWideningAnalysis.cpp b/clang/lib/Sema/BoundsWideningAnalysis.cpp index e2c21d61701d..b70096730e2f 100644 --- a/clang/lib/Sema/BoundsWideningAnalysis.cpp +++ b/clang/lib/Sema/BoundsWideningAnalysis.cpp @@ -93,17 +93,90 @@ BoundsMapTy BoundsWideningAnalysis::GetOutOfLastStmt( if (!CurrStmt) continue; + // The In of the current statement is the value of StmtOut computed so far. + BoundsMapTy InOfCurrStmt = StmtOut; + // If this is the last statement of the current block, then at this point - // StmtOut contains the Out set of the second last statement of the block. - // This is equal to the In set for the last statement of this block. So we - // set InOfLastStmt to StmtOut. + // InOfCurrStmt contains the Out set of the second last statement of the + // block. This is equal to the In set for the last statement of this + // block. So we set InOfLastStmt to InOfCurrStmt. if (CurrStmt == EB->LastStmt) - EB->InOfLastStmt = StmtOut; - - auto Diff = BWUtil.Difference(StmtOut, EB->StmtKill[CurrStmt]); + EB->InOfLastStmt = InOfCurrStmt; - // TODO: Update StmtOut based on the invertibility of CurrStmt. + // StmtOut = (InOfCurrStmt - StmtKill) u StmtGen. + auto Diff = BWUtil.Difference(InOfCurrStmt, EB->StmtKill[CurrStmt]); StmtOut = BWUtil.Union(Diff, EB->StmtGen[CurrStmt]); + + // Update StmtOut based on the invertibility of CurrStmt. + auto InvStmtIt = EB->InvertibleStmts.find(CurrStmt); + if (InvStmtIt == EB->InvertibleStmts.end()) + continue; + + // At CurrStmt, we need to replace the ModifiedLValue with the + // OriginalLValue in the bounds of every null-terminated array occurring in + // PtrsWithAffectedBounds. + auto ValuesToReplaceInBounds = InvStmtIt->second; + + Expr *ModifiedLValue = std::get<0>(ValuesToReplaceInBounds); + Expr *OriginalLValue = std::get<1>(ValuesToReplaceInBounds); + VarSetTy PtrsWithAffectedBounds = std::get<2>(ValuesToReplaceInBounds); + + // TODO: Determine the Checked scope for each statement. + CheckedScopeSpecifier CSS = CheckedScopeSpecifier::CSS_None; + + for (const VarDecl *V : PtrsWithAffectedBounds) { + auto StmtInIt = InOfCurrStmt.find(V); + if (StmtInIt == InOfCurrStmt.end()) + continue; + + BoundsExpr *SrcBounds = StmtInIt->second; + + // Replace the modified LValue with the original LValue in the bounds + // expression of V. + BoundsExpr *AdjustedBounds = + BoundsUtil::ReplaceLValueInBounds(SemaRef, SrcBounds, ModifiedLValue, + OriginalLValue, CSS); + RangeBoundsExpr *AdjustedRangeBounds = + dyn_cast_or_null(AdjustedBounds); + + if (!AdjustedRangeBounds) + llvm_unreachable("Invalid RangeBoundsExpr!"); + + // In the bounds widening analysis the widest value of an upper bounds + // expression is Top, whereas the narrowest value is the declared upper + // bound. This means that the upper bound can/should never become + // narrower than the declared upper bound. + // So in case we have an invertible statement that modifies a variable + // occurring in the bounds expression of a null-terminated array we + // should always reset the bounds to the declared upper bound except when + // replacement of the modified LValue with its original LValue results in + // a bounds expression which is strictly wider than the declared upper + // bound. + // So we will proceed only if AdjustedRangeBounds is wider than + // StmtOut[V] which contains the declared bounds of V at this point. For + // example: + + // Let DB = Declared bounds, AB = Adjusted bounds. + + // DB = (p, p + len), AB = (p, p + len + 1) + // ==> AB is wider than DB ==> set bounds of V to AB. + + // DB = (p, p + len), AB = (p, p + len - 1) + // ==> AB is not wider than DB ==> set bounds of V to DB. + + // DB = (p, p + len), AB = (p, p + len + i) + // ==> AB cannot be compared to DB ==> set bounds of V to DB. + + if (!BWUtil.IsSubRange(AdjustedRangeBounds, StmtOut[V])) + continue; + + // Update the bounds of V with the adjusted bounds. + StmtOut[V] = AdjustedRangeBounds; + + // Store the adjusted bounds for the current statement. We will use these + // when clients invoke GetStmtIn or GetStmtOut. + EB->AdjustedBounds[CurrStmt][V] = AdjustedRangeBounds; + } } return StmtOut; } @@ -247,7 +320,7 @@ bool BoundsWideningAnalysis::ComputeInSet(ElevatedCFGBlock *EB) { } // Return true if the In set has changed, false otherwise. - return !BWUtil.IsEqual(EB->In, OrigIn); + return !BWUtil.IsEqual(OrigIn, EB->In); } BoundsMapTy BoundsWideningAnalysis::PruneOutSet( @@ -444,7 +517,7 @@ bool BoundsWideningAnalysis::ComputeOutSet(ElevatedCFGBlock *EB) { EB->Out = GetOutOfLastStmt(EB); // Return true if the Out set has changed, false otherwise. - return !BWUtil.IsEqual(EB->Out, OrigOut); + return !BWUtil.IsEqual(OrigOut, EB->Out); } void BoundsWideningAnalysis::InitBlockInOutSets(FunctionDecl *FD, @@ -511,15 +584,37 @@ BoundsMapTy BoundsWideningAnalysis::GetStmtOut(const CFGBlock *B, ElevatedCFGBlock *EB = BlockIt->second; - if (CurrStmt) { - auto Diff = BWUtil.Difference(EB->OutOfPrevStmt, EB->StmtKill[CurrStmt]); - auto StmtOut = BWUtil.Union(Diff, EB->StmtGen[CurrStmt]); - EB->OutOfPrevStmt = StmtOut; - return StmtOut; + // CurrStmt will be null if: + // 1. This method is called with a null value for CurrStmt, or + // 2. GetStmtIn calls this method to get the In set for the first statement + // of the block. Because the Out of the previous statement is equal to the In + // of the current statement, GetStmtIn will call this function with the + // previous statement of the first statment (which would be null). + + // In both cases we will set the OutOfPrevStmt to the In set of the block and + // return it. + if (!CurrStmt) { + EB->OutOfPrevStmt = EB->In; + return EB->In; } - EB->OutOfPrevStmt = EB->In; - return EB->In; + // If we are here it means the client wants the Out set for the first + // statement of the block (that is the reason PrevStmtMap[CurrStmt] is null). + // In this case, we set OutOfPrevStmt to the In set of the block and then + // apply the regular (In - Kill) u Gen computation on it. + if (!EB->PrevStmtMap[CurrStmt]) + EB->OutOfPrevStmt = EB->In; + + auto Diff = BWUtil.Difference(EB->OutOfPrevStmt, EB->StmtKill[CurrStmt]); + auto StmtOut = BWUtil.Union(Diff, EB->StmtGen[CurrStmt]); + + // Account for bounds which are killed by the current statement but which may + // have been adjusted using invertibility of the statement. This function + // modifies StmtOut. + UpdateAdjustedBounds(EB, CurrStmt, StmtOut); + + EB->OutOfPrevStmt = StmtOut; + return StmtOut; } BoundsMapTy BoundsWideningAnalysis::GetStmtIn(const CFGBlock *B, @@ -549,7 +644,14 @@ BoundsMapTy BoundsWideningAnalysis::GetBoundsWidenedAndNotKilled( ElevatedCFGBlock *EB = BlockIt->second; BoundsMapTy InOfCurrStmt = GetStmtIn(B, CurrStmt); - return BWUtil.Difference(InOfCurrStmt, EB->StmtKill[CurrStmt]); + auto BoundsWidenedAndNotKilled = BWUtil.Difference(InOfCurrStmt, + EB->StmtKill[CurrStmt]); + + // Account for bounds which are killed by the current statement but which may + // have been adjusted using invertibility of the statement. This function + // modifies BoundsWidenedAndNotKilled. + UpdateAdjustedBounds(EB, CurrStmt, BoundsWidenedAndNotKilled); + return BoundsWidenedAndNotKilled; } void BoundsWideningAnalysis::InitNullTermPtrsInFunc(FunctionDecl *FD) { @@ -659,20 +761,19 @@ void BoundsWideningAnalysis::GetVarsAndBoundsInPtrDeref( // _Nt_array_ptr s : bounds(p, s); // On a dereference expression like "*(p + i + j + 1)" - // GetNullTermPtrsWithVarsInUpperBounds() will return {p, q, r} because p + // GetPtrsWithVarsInUpperBounds() will return {p, q, r} because p // occurs in the upper bounds expressions of p, q and r. VarSetTy Vars; Vars.insert(NullTermPtrInExpr); - VarSetTy NullTermPtrsWithVarsInUpperBounds; - BWUtil.GetNullTermPtrsWithVarsInUpperBounds( - Vars, NullTermPtrsWithVarsInUpperBounds); + VarSetTy PtrsWithAffectedBounds; + BWUtil.GetPtrsWithVarsInUpperBounds(Vars, PtrsWithAffectedBounds); - // Now, the bounds of all variables in NullTermPtrsWithVarsInUpperBounds can - // potentially be widened to bounds(lower, DerefExpr + 1). + // Now, the bounds of all variables in PtrsWithAffectedBounds can potentially + // be widened to bounds(lower, DerefExpr + 1). - for (const VarDecl *V : NullTermPtrsWithVarsInUpperBounds) { + for (const VarDecl *V : PtrsWithAffectedBounds) { BoundsExpr *NormalizedBounds = SemaRef.NormalizeBounds(V); RangeBoundsExpr *R = dyn_cast_or_null(NormalizedBounds); @@ -700,18 +801,126 @@ void BoundsWideningAnalysis::GetVarsAndBoundsForModifiedVars( // Get the set of variables that are pointers to null-terminated arrays and // in whose lower and upper bounds expressions the modified variables occur. - VarSetTy NullTermPtrsWithVarsInBounds; - BWUtil.GetNullTermPtrsWithVarsInLowerBounds(ModifiedVars, - NullTermPtrsWithVarsInBounds); - BWUtil.GetNullTermPtrsWithVarsInUpperBounds(ModifiedVars, - NullTermPtrsWithVarsInBounds); + VarSetTy PtrsWithAffectedBounds; + BWUtil.GetPtrsWithVarsInLowerBounds(ModifiedVars, + PtrsWithAffectedBounds); + BWUtil.GetPtrsWithVarsInUpperBounds(ModifiedVars, + PtrsWithAffectedBounds); // For each null-terminated array we need to reset the bounds to its declared // bounds. - for (const VarDecl *V : NullTermPtrsWithVarsInBounds) { + for (const VarDecl *V : PtrsWithAffectedBounds) { BoundsExpr *NormalizedBounds = SemaRef.NormalizeBounds(V); VarsAndBounds[V] = dyn_cast_or_null(NormalizedBounds); } + + // If the modification of a variable by the current statement affects the + // bounds of a null-terminated array, then check invertibility of the + // statement. If the statement is invertible then store the statement, the + // modified LValue, the original LValue and the set of null-terminated arrays + // whose bounds are affected by the statement. We will use this info in the + // computation of the Out sets of statements which will, in turn be used to + // compute the Out sets of blocks. + CheckStmtInvertibility(EB, CurrStmt, PtrsWithAffectedBounds); +} + +void BoundsWideningAnalysis::CheckStmtInvertibility(ElevatedCFGBlock *EB, + const Stmt *CurrStmt, VarSetTy PtrsWithAffectedBounds) const { + + // If the variables modified by the current statement do not affect the + // bounds of any null-terminated array we do not need to check statement + // invertibility. + if (PtrsWithAffectedBounds.size() == 0) + return; + + Expr *ModifiedLValue = nullptr; + Expr *ModifyingExpr = nullptr; + + // If the current statement is a unary inc/dec. For example: ++len + if (const auto *UO = dyn_cast(CurrStmt)) { + if (!UO->isIncrementDecrementOp()) + return; + + // Get the LValue being incremented/decremented. For example: len + ModifiedLValue = UO->getSubExpr(); + if (!ModifiedLValue) + return; + + // Normalize the inc/dec of the LValue to LValue +/- 1. + // For example: ++len is normalized to len + 1 + // len-- is normalized to len - 1 + IntegerLiteral *One = ExprCreatorUtil::CreateIntegerLiteral( + Ctx, 1, ModifiedLValue->getType()); + + BinaryOperatorKind OpKind = UnaryOperator::isIncrementOp(UO->getOpcode()) ? + BO_Add : BO_Sub; + + // Here ModifyingExpr will be of the form len +/- 1. + ModifyingExpr = + ExprCreatorUtil::CreateBinaryOperator(SemaRef, ModifiedLValue, + One, OpKind); + + // Else if the current statement is an assignment statement. For example: + // len = e1 + } else if (const auto *BO = dyn_cast(CurrStmt)) { + if (!BO->isAssignmentOp()) + return; + + // ModifiedLValue is len. + ModifiedLValue = BO->getLHS(); + // ModifyingExpr is e1. + ModifyingExpr = BO->getRHS(); + + BinaryOperatorKind OpKind = BO->getOpcode(); + // If the current statement is of the form len += e1. + if (OpKind == BO_AddAssign || OpKind == BO_SubAssign) { + OpKind = OpKind == BO_AddAssign ? BO_Add : BO_Sub; + + // Normalize the ModifyingExpr to len + e1. + ModifyingExpr = + ExprCreatorUtil::CreateBinaryOperator(SemaRef, ModifiedLValue, + ModifyingExpr, OpKind); + } + } + + if (!ModifiedLValue || !ModifyingExpr) + return; + + CastExpr *Target = + ExprCreatorUtil::CreateImplicitCast(SemaRef, ModifiedLValue, + CK_LValueToRValue, + ModifiedLValue->getType()); + + // Check if the modifying expr is invertible w.r.t. the modified LValue. + if (InverseUtil::IsInvertible(SemaRef, ModifiedLValue, ModifyingExpr)) { + // Get the original LValue for the modified LValue. For example, for len++ + // the original LValue would be len - 1. + Expr *OriginalLValue = InverseUtil::Inverse(SemaRef, ModifiedLValue, + Target, ModifyingExpr); + + // Store the modified LValue, the original LValue and the set of + // null-terminated arrays whose bounds expressions are affected by the + // LValue being modified. + if (OriginalLValue) + EB->InvertibleStmts[CurrStmt] = std::make_tuple(ModifiedLValue, + OriginalLValue, + PtrsWithAffectedBounds); + } +} + +void BoundsWideningAnalysis::UpdateAdjustedBounds( + ElevatedCFGBlock *EB, const Stmt *CurrStmt, BoundsMapTy &StmtOut) const { + + auto AdjBoundsIt = EB->AdjustedBounds.find(CurrStmt); + if (AdjBoundsIt == EB->AdjustedBounds.end()) + return; + + for (auto Item : AdjBoundsIt->second) { + const VarDecl *V = Item.first; + RangeBoundsExpr *AdjustedBounds = Item.second; + + StmtOut[V] = AdjustedBounds; + } } void BoundsWideningAnalysis::PrintVarSet(VarSetTy VarSet, @@ -839,6 +1048,11 @@ void BoundsWideningAnalysis::DumpWidenedBounds(FunctionDecl *FD, PrintBoundsMap(EB->Out, PrintOption); } + if (CurrBlock->empty()) { + OS << "\n"; + continue; + } + for (CFGElement Elem : *CurrBlock) { if (Elem.getKind() != CFGElement::Statement) continue; @@ -1078,8 +1292,8 @@ void BoundsWideningUtil::GetModifiedVars(const Stmt *CurrStmt, GetModifiedVars(NestedStmt, ModifiedVars); } -void BoundsWideningUtil::GetNullTermPtrsWithVarsInLowerBounds( - VarSetTy &Vars, VarSetTy &NullTermPtrsWithVarsInLowerBounds) const { +void BoundsWideningUtil::GetPtrsWithVarsInLowerBounds( + VarSetTy &Vars, VarSetTy &PtrsWithVarsInLowerBounds) const { // Get the set of variables that are pointers to null-terminated arrays and // in whose lower bounds expressions the variables in Vars occur. @@ -1089,13 +1303,13 @@ void BoundsWideningUtil::GetNullTermPtrsWithVarsInLowerBounds( if (VarPtrIt != BoundsVarsLower.end()) { for (const VarDecl *Ptr : VarPtrIt->second) if (!Ptr->isInvalidDecl() && IsNtArrayType(Ptr)) - NullTermPtrsWithVarsInLowerBounds.insert(Ptr); + PtrsWithVarsInLowerBounds.insert(Ptr); } } } -void BoundsWideningUtil::GetNullTermPtrsWithVarsInUpperBounds( - VarSetTy &Vars, VarSetTy &NullTermPtrsWithVarsInUpperBounds) const { +void BoundsWideningUtil::GetPtrsWithVarsInUpperBounds( + VarSetTy &Vars, VarSetTy &PtrsWithVarsInUpperBounds) const { // Get the set of variables that are pointers to null-terminated arrays and // in whose upper bounds expressions the variables in Vars occur. @@ -1105,7 +1319,7 @@ void BoundsWideningUtil::GetNullTermPtrsWithVarsInUpperBounds( if (VarPtrIt != BoundsVarsUpper.end()) { for (const VarDecl *Ptr : VarPtrIt->second) if (!Ptr->isInvalidDecl() && IsNtArrayType(Ptr)) - NullTermPtrsWithVarsInUpperBounds.insert(Ptr); + PtrsWithVarsInUpperBounds.insert(Ptr); } } } diff --git a/clang/test/CheckedC/inferred-bounds/bounds-context.c b/clang/test/CheckedC/inferred-bounds/bounds-context.c index db0ec1fbdf2d..bcbc674c9402 100644 --- a/clang/test/CheckedC/inferred-bounds/bounds-context.c +++ b/clang/test/CheckedC/inferred-bounds/bounds-context.c @@ -300,7 +300,7 @@ void assign1(array_ptr arr : count(1)) { // expected-note {{(expanded) decl // Assignment to a variable used in other variables' bounds void assign2( array_ptr a : count(len - 1), // expected-note {{(expanded) declared bounds are 'bounds(a, a + len - 1)'}} - char b nt_checked[0] : count(len), // expected-note {{(expanded) declared bounds are 'bounds(b, b + len)'}} + char b nt_checked[0] : count(len), unsigned len ) { // Observed bounds context before assignment: { a => bounds(a, a + len - 1), b => bounds(b, b + len) } @@ -308,8 +308,6 @@ void assign2( // Observed bounds context after assignment : { a => bounds(a, a + ((len + 3) - 1)), b => bounds(b, b + (len + 3)) } len = len - 3; // expected-warning {{cannot prove declared bounds for 'a' are valid after assignment}} \ // expected-note {{(expanded) inferred bounds are 'bounds(a, a + len + 3 - 1)'}} \ - // expected-warning {{cannot prove declared bounds for 'b' are valid after assignment}} \ - // expected-note {{(expanded) inferred bounds are 'bounds(b, b + len + 3)'}} // CHECK: Statement S: // CHECK-NEXT: BinaryOperator {{.*}} '=' // CHECK-NEXT: DeclRefExpr {{.*}} 'len' diff --git a/clang/test/CheckedC/inferred-bounds/widened-bounds-invertibility.c b/clang/test/CheckedC/inferred-bounds/widened-bounds-invertibility.c new file mode 100644 index 000000000000..22a33d076caa --- /dev/null +++ b/clang/test/CheckedC/inferred-bounds/widened-bounds-invertibility.c @@ -0,0 +1,209 @@ +// Tests for invertibility of statements in the datafow analysis for bounds +// widening of null-terminated arrays. +// +// RUN: %clang_cc1 -fdump-widened-bounds -verify \ +// RUN: -verify-ignore-unexpected=note -verify-ignore-unexpected=warning %s \ +// RUN: | FileCheck %s + +// expected-no-diagnostics + +#include +#include + +int a; + +void f1(_Nt_array_ptr p : count(len), unsigned len) { + + while (*(p + len)) { + len++; + a = 1; + } + +// CHECK: Function: f1 +// CHECK: Block: B25 (Entry), Pred: Succ: B24 + +// CHECK: Block: B24, Pred: B22, B25, Succ: B23, B21 +// CHECK: Widened bounds before stmt: *(p + len) +// CHECK: p: bounds(p, p + len) + +// CHECK: Block: B23, Pred: B24, Succ: B22 +// CHECK: Widened bounds before stmt: len++ +// CHECK: p: bounds(p, p + len + 1) + +// CHECK: Widened bounds before stmt: a = 1 +// CHECK: p: bounds(p, p + len - 1U + 1) + + while (*(p + len)) { + ++len; + a = 2; + } + +// CHECK: Block: B22, Pred: B23, Succ: B24 + +// CHECK: Block: B21, Pred: B19, B24, Succ: B20, B18 +// CHECK: Widened bounds before stmt: *(p + len) +// CHECK: p: bounds(p, p + len) + +// CHECK: Block: B20, Pred: B21, Succ: B19 +// CHECK: Widened bounds before stmt: ++len +// CHECK: p: bounds(p, p + len + 1) + +// CHECK: Widened bounds before stmt: a = 2 +// CHECK: p: bounds(p, p + len - 1U + 1) + + while (*(p + len)) { + len--; + a = 3; + } + +// CHECK: Block: B19, Pred: B20, Succ: B21 + +// CHECK: Block: B18, Pred: B16, B21, Succ: B17, B15 +// CHECK: Widened bounds before stmt: *(p + len) +// CHECK: p: bounds(p, p + len) + +// CHECK: Block: B17, Pred: B18, Succ: B16 +// CHECK: Widened bounds before stmt: len-- +// CHECK: p: bounds(p, p + len + 1) + +// CHECK: Widened bounds before stmt: a = 3 +// CHECK: p: bounds(p, p + len + 1U + 1) + + while (*(p + len)) { + --len; + a = 4; + } + +// CHECK: Block: B16, Pred: B17, Succ: B18 + +// CHECK: Block: B15, Pred: B13, B18, Succ: B14, B12 +// CHECK: Widened bounds before stmt: *(p + len) +// CHECK: p: bounds(p, p + len) + +// CHECK: Block: B14, Pred: B15, Succ: B13 +// CHECK: Widened bounds before stmt: --len +// CHECK: p: bounds(p, p + len + 1) + +// CHECK: Widened bounds before stmt: a = 4 +// CHECK: p: bounds(p, p + len + 1U + 1) + + while (*(p + len)) { + len = len + 1; + a = 5; + } + +// CHECK: Block: B13, Pred: B14, Succ: B15 + +// CHECK: Block: B12, Pred: B10, B15, Succ: B11, B9 +// CHECK: Widened bounds before stmt: *(p + len) +// CHECK: p: bounds(p, p + len) + +// CHECK: Block: B11, Pred: B12, Succ: B10 +// CHECK: Widened bounds before stmt: len = len + 1 +// CHECK: p: bounds(p, p + len + 1) + +// CHECK: Widened bounds before stmt: a = 5 +// CHECK: p: bounds(p, p + len - 1 + 1) + + while (*(p + len)) { + len = len - 1; + a = 6; + } + +// CHECK: Block: B10, Pred: B11, Succ: B12 + +// CHECK: Block: B9, Pred: B7, B12, Succ: B8, B6 +// CHECK: Widened bounds before stmt: *(p + len) +// CHECK: p: bounds(p, p + len) + +// CHECK: Block: B8, Pred: B9, Succ: B7 +// CHECK: Widened bounds before stmt: len = len - 1 +// CHECK: p: bounds(p, p + len + 1) + +// CHECK: Widened bounds before stmt: a = 6 +// CHECK: p: bounds(p, p + len + 1 + 1) + + while (*(p + len)) { + len += 1; + a = 7; + } + +// CHECK: Block: B7, Pred: B8, Succ: B9 + +// CHECK: Block: B6, Pred: B4, B9, Succ: B5, B3 +// CHECK: Widened bounds before stmt: *(p + len) +// CHECK: p: bounds(p, p + len) + +// CHECK: Block: B5, Pred: B6, Succ: B4 +// CHECK: Widened bounds before stmt: len += 1 +// CHECK: p: bounds(p, p + len + 1) + +// CHECK: Widened bounds before stmt: a = 7 +// CHECK: p: bounds(p, p + len - 1 + 1) + + while (*(p + len)) { + len -= 1; + a = 8; + } + +// CHECK: Block: B4, Pred: B5, Succ: B6 + +// CHECK: Block: B3, Pred: B1, B6, Succ: B2, B0 +// CHECK: Widened bounds before stmt: *(p + len) +// CHECK: p: bounds(p, p + len) + +// CHECK: Block: B2, Pred: B3, Succ: B1 +// CHECK: Widened bounds before stmt: len -= 1 +// CHECK: p: bounds(p, p + len + 1) + +// CHECK: Widened bounds before stmt: a = 8 +// CHECK: p: bounds(p, p + len + 1 + 1) +} + +void f2(_Nt_array_ptr p : count(len), unsigned len) { + while (*(p + len)) { + if (*(p + len + 1)) { + if (*(p + len + 2)) { + len = len + 1; + a = *(p + len + 1); + } + } + } + +// CHECK: Function: f2 +// CHECK: Block: B6 (Entry), Pred: Succ: B5 + +// CHECK: Block: B5, Pred: B1, B6, Succ: B4, B0 +// CHECK: Widened bounds before stmt: *(p + len) +// CHECK: p: bounds(p, p + len) + +// CHECK: Block: B4, Pred: B5, Succ: B3, B1 +// CHECK: Widened bounds before stmt: *(p + len + 1) +// CHECK: p: bounds(p, p + len + 1) + +// CHECK: Block: B3, Pred: B4, Succ: B2, B1 +// CHECK: Widened bounds before stmt: *(p + len + 2) +// CHECK: p: bounds(p, p + len + 1 + 1) + +// CHECK: Block: B2, Pred: B3, Succ: B1 +// CHECK: Widened bounds before stmt: len = len + 1 +// CHECK: p: bounds(p, p + len + 2 + 1) + +// CHECK: Widened bounds before stmt: a = *(p + len + 1) +// CHECK: p: bounds(p, p + len - 1 + 2 + 1) +} + +void f3(_Nt_array_ptr p : count(len), unsigned len) { + len--; + a = 1; + +// CHECK: Function: f3 +// CHECK: Block: B2 (Entry), Pred: Succ: B1 + +// CHECK: Block: B1, Pred: B2, Succ: B0 +// CHECK: Widened bounds before stmt: len-- +// CHECK: p: bounds(p, p + len) + +// CHECK: Widened bounds before stmt: a = 1 +// CHECK: p: bounds(p, p + len + 1U) +} diff --git a/clang/test/CheckedC/inferred-bounds/widened-bounds.c b/clang/test/CheckedC/inferred-bounds/widened-bounds.c index 914b8056956a..eef5b6ce3ee1 100644 --- a/clang/test/CheckedC/inferred-bounds/widened-bounds.c +++ b/clang/test/CheckedC/inferred-bounds/widened-bounds.c @@ -1540,7 +1540,7 @@ void f24() { while (*p) { p++; - while (*(p + 1)) { // expected-error {{out-of-bounds memory access}} + while (*(p + 1)) { a = 1; } } @@ -1550,7 +1550,7 @@ void f24() { // CHECK: Block: B8, Pred: B9, Succ: B7 // CHECK: Widened bounds before stmt: _Nt_array_ptr p : count(0) = ""; -// CHECK: +// CHECK: // CHECK: Block: B7, Pred: B2, B8, Succ: B6, B1 // CHECK: Widened bounds before stmt: *p @@ -1562,11 +1562,11 @@ void f24() { // CHECK: Block: B5, Pred: B3, B6, Succ: B4, B2 // CHECK: Widened bounds before stmt: *(p + 1) -// CHECK: p: bounds(p, p + 0) +// CHECK: p: bounds(p - 1, p - 1 + 1) // CHECK: Block: B4, Pred: B5, Succ: B3 // CHECK: Widened bounds before stmt: a = 1 -// CHECK: p: bounds(p, p + 0) +// CHECK: p: bounds(p - 1, p - 1 + 1) // CHECK: Block: B3, Pred: B4, Succ: B5 @@ -1642,7 +1642,7 @@ void f25_1() { for (; *p; ) { p++; - for (; *(p + 1); ) { // expected-error {{out-of-bounds memory access}} + for (; *(p + 1); ) { a = 7; } } @@ -1657,11 +1657,11 @@ void f25_1() { // CHECK: Block: B5, Pred: B3, B6, Succ: B4, B2 // CHECK: Widened bounds before stmt: *(p + 1) -// CHECK: p: bounds(p, p + 0) +// CHECK: p: bounds(p - 1, p - 1 + 1) // CHECK: Block: B4, Pred: B5, Succ: B3 // CHECK: Widened bounds before stmt: a = 7 -// CHECK: p: bounds(p, p + 0) +// CHECK: p: bounds(p - 1, p - 1 + 1) // CHECK: Block: B3, Pred: B4, Succ: B5