diff --git a/clang/include/clang/AST/ExprUtils.h b/clang/include/clang/AST/ExprUtils.h index df06882741fb..a24cc345ae71 100644 --- a/clang/include/clang/AST/ExprUtils.h +++ b/clang/include/clang/AST/ExprUtils.h @@ -145,6 +145,10 @@ class ExprUtil { // pointer). Returns false if E is nullptr. static bool ReadsMemoryViaPointer(Expr *E, bool IncludeAllMemberExprs = false); + // IsReturnValueExpr return true if the expression E is a _Return_value + // expression. + static bool IsReturnValueExpr(Expr *E); + // FindLValue returns true if the given lvalue expression occurs in E. static bool FindLValue(Sema &S, Expr *LValue, Expr *E); diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ecd3732d596e..cdcfd82fcc94 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11386,6 +11386,10 @@ def err_bounds_type_annotation_lost_checking : Error< "argument has unknown bounds, bounds expected because the " "%ordinal0 parameter has bounds">; + def err_expected_bounds_for_return : Error< + "return value has unknown bounds, bounds expected because the " + "function %0 has bounds">; + def err_initializer_expected_with_bounds : Error< "automatic variable %0 with bounds must have initializer">; @@ -11619,8 +11623,29 @@ def err_bounds_type_annotation_lost_checking : Error< def error_static_cast_bounds_invalid : Error< "cast source bounds are too narrow for %0">; + def error_return_bounds_invalid : Error< + "return value bounds do not imply declared return bounds for %0">; + + def error_return_bounds_unprovable: Error< + "it is not possible to prove that return value bounds " + "imply declared return bounds for %0">; + + def warn_return_bounds_invalid: Warning< + "cannot prove return value bounds imply declared return bounds for %0">, + InGroup; + + def warn_checked_scope_return_bounds_invalid : Warning< + "cannot prove return value bounds imply declared return bounds for %0">, + InGroup; + + def note_declared_return_bounds : Note< + "(expanded) declared return bounds are '%0'">; + + def note_inferred_return_bounds : Note< + "(expanded) inferred return value bounds are '%0'">; + def error_out_of_bounds_access : Error< - "out-of-bounds %select{||memory access|base value}0">; + "out-of-bounds %select{|||memory access|base value}0">; def note_source_bounds_empty : Note<"source bounds are an empty range">; @@ -11631,21 +11656,22 @@ def err_bounds_type_annotation_lost_checking : Error< def note_destination_bounds_invalid : Note<"destination bounds are an invalid range">; def note_bounds_too_narrow : Note< - "%select{destination bounds are|target bounds are|memory accessed is|" - "struct/union pointed to by base is}0 wider than the " - "%select{source|source||}0 bounds">; + "%select{destination bounds are|target bounds are|declared return bounds are|" + "memory accessed is|struct/union pointed to by base is|}0 wider " + "than the %select{source|source|return value|source|source}0 bounds">; def note_lower_out_of_bounds : Note< - "%select{destination lower bound is|target lower bound is|accesses memory|" - "base value is}0 below %select{source|source|the|its}0 lower bound">; + "%select{destination lower bound is|target lower bound is|" + "declared return lower bound is|accesses memory|base value is}0 " + "below %select{source|source|return value|the|its}0 lower bound">; def note_upper_out_of_bounds : Note< "%select{destination upper bound is|target upper bound is|" - "accesses memory at or|base value is}0 " - "above %select{source|source|the|its}0 upper bound">; + "declared return upper bound is|accesses memory at or|base value is}0 " + "above %select{source|source|return value|the|its}0 upper bound">; def note_bounds_partially_overlap : Note< - "%select{||accesses memory that|struct/union pointed to by base value}0 is " + "%select{|||accesses memory that|struct/union pointed to by base value}0 is " "only partially in bounds">; def no_prototype_generic_function : Error< diff --git a/clang/lib/AST/ExprUtils.cpp b/clang/lib/AST/ExprUtils.cpp index aa43da135eca..5e8d2a18bad3 100644 --- a/clang/lib/AST/ExprUtils.cpp +++ b/clang/lib/AST/ExprUtils.cpp @@ -306,6 +306,13 @@ bool ExprUtil::ReadsMemoryViaPointer(Expr *E, bool IncludeAllMemberExprs) { } } +bool ExprUtil::IsReturnValueExpr(Expr *E) { + BoundsValueExpr *BVE = dyn_cast_or_null(E); + if (!BVE) + return false; + return BVE->getKind() == BoundsValueExpr::Kind::Return; +} + namespace { class FindLValueHelper : public RecursiveASTVisitor { private: diff --git a/clang/lib/Sema/SemaBounds.cpp b/clang/lib/Sema/SemaBounds.cpp index 0bf468b35243..2d4ff530d14b 100644 --- a/clang/lib/Sema/SemaBounds.cpp +++ b/clang/lib/Sema/SemaBounds.cpp @@ -652,8 +652,20 @@ namespace { uint64_t PointerWidth; Stmt *Body; CFG *Cfg; - BoundsExpr *ReturnBounds; // return bounds expression for enclosing - // function, if any. + + // Declaration for enclosing function. Having this here allows us to emit + // the name of the function in any diagnostic message when checking return + // bounds. + FunctionDecl *FunctionDeclaration; + // Return value expression for enclosing function, if any. Having this + // here allows us to avoid reconstructing a return value for each + // return statement. + BoundsValueExpr *ReturnVal; + // Expanded declared return bounds expression for enclosing function, if + // any. Having this here allows us to avoid re-expanding the return bounds + // for each return statement. + BoundsExpr *ReturnBounds; + ASTContext &Context; std::pair &Facts; @@ -946,6 +958,7 @@ namespace { enum class ProofStmtKind : unsigned { BoundsDeclaration, StaticBoundsCast, + ReturnStmt, MemoryAccess, MemberArrowBase }; @@ -1366,6 +1379,11 @@ namespace { ExprUtil::ReadsMemoryViaPointer(E2, true)) return false; + // If E1 or E2 is a _Return_value expression, we skip since we cannot + // determine the set of variables that occur in these expressions. + if (ExprUtil::IsReturnValueExpr(E1) || ExprUtil::IsReturnValueExpr(E2)) + return false; + bool HasFreeVariables = false; EqualExprTy Vars1 = CollectVariableSet(S, E1); EqualExprTy Vars2 = CollectVariableSet(S, E2); @@ -1943,6 +1961,9 @@ namespace { return false; } + // Methods to try to prove that an inferred bounds expression implies + // the validity of a target bounds expression. + // Try to prove that SrcBounds implies the validity of DeclaredBounds. // // If Kind is StaticBoundsCast, check whether a static cast between Ptr @@ -2020,6 +2041,80 @@ namespace { return ProofResult::Maybe; } + // Try to prove that RetExprBounds implies the validity of ReturnBounds + // (the declared return bounds for the enclosing function). + ProofResult ProveReturnBoundsValidity(Expr *RetExpr, + BoundsExpr *RetExprBounds, + const EquivExprSets EQ, + const EqualExprTy G, + ProofFailure &Cause, + FreeVariableListTy &FreeVariables) { + // Check some basic properties of the declared ReturnBounds and the + // source RetExprBounds. Even though these checks will also be done + // in ProveBoundsDeclValidity, if any of these checks result in an + // early return, we can avoid constructing a modified EquivExprs set + // that records equality between RetVal and RetExpr. + + // Null declared return bounds or declared return bounds(unknown) + // implied by any other bounds. + if (!ReturnBounds || ReturnBounds->isUnknown()) + return ProofResult::True; + + // Ignore invalid bounds. + if (RetExprBounds->isInvalid() || ReturnBounds->isInvalid()) + return ProofResult::True; + + // Return expression bounds(any) implies any declared return bounds. + if (RetExprBounds->isAny()) + return ProofResult::True; + + // Return expression bounds(unknown) cannot imply any non-unknown + // declared return bounds. + if (RetExprBounds->isUnknown()) + return ProofResult::False; + + // Record equality between ReturnVal and all expressions that produce + // the same value as RetExpr. This allows ProveBoundsDeclValidity to + // validate bounds that depend on the return value of the function. + // For example, if the declared function bounds are count(1), then the + // expanded ReturnBounds will be bounds(RetVal, RetVal + 1). + EquivExprSets EquivExprs = EQ; + + // Determine the set of expressions that produce the same value as + // RetExpr. + // If G (the set of expressions that produce the same value as RetExpr) + // is empty, then RetExpr may be an expression that is not allowed to + // be recorded in State.EquivExprs (e.g. an expression that reads memory + // via a pointer). The EquivExprs set that we construct here is temporary + // and is only used to check the return bounds for one return statement, + // so we can add RetExpr to EquivExprs in this case. + EqualExprTy RetSameValue = G; + if (RetSameValue.size() == 0) + RetSameValue.push_back(RetExpr); + + bool FoundRetExpr = false; + for (auto F = EquivExprs.begin(); F != EquivExprs.end(); ++F) { + if (DoExprSetsIntersect(*F, RetSameValue)) { + // Add all expressions in RetSameValue to F that are not already in F. + for (Expr *E : RetSameValue) + if (!EqualExprsContainsExpr(*F, E)) + F->push_back(E); + F->push_back(ReturnVal); + FoundRetExpr = true; + break; + } + } + if (!FoundRetExpr) { + EqualExprTy F = RetSameValue; + F.push_back(ReturnVal); + EquivExprs.push_back(F); + } + + return ProveBoundsDeclValidity(ReturnBounds, RetExprBounds, Cause, + &EquivExprs, FreeVariables, + ProofStmtKind::ReturnStmt); + } + // CompareNormalizedBounds returns true if SrcBounds implies DeclaredBounds // after applying certain transformations to the upper bound expressions // of both bounds. @@ -2569,14 +2664,16 @@ namespace { public: - CheckBoundsDeclarations(Sema &SemaRef, PrepassInfo &Info, Stmt *Body, CFG *Cfg, BoundsExpr *ReturnBounds, std::pair &Facts) : S(SemaRef), + CheckBoundsDeclarations(Sema &SemaRef, PrepassInfo &Info, Stmt *Body, CFG *Cfg, FunctionDecl *FD, std::pair &Facts) : S(SemaRef), DumpBounds(SemaRef.getLangOpts().DumpInferredBounds), DumpState(SemaRef.getLangOpts().DumpCheckingState), DumpSynthesizedMembers(SemaRef.getLangOpts().DumpSynthesizedMembers), PointerWidth(SemaRef.Context.getTargetInfo().getPointerWidth(0)), Body(Body), Cfg(Cfg), - ReturnBounds(ReturnBounds), + FunctionDeclaration(FD), + ReturnVal(nullptr), + ReturnBounds(nullptr), Context(SemaRef.Context), Facts(Facts), BoundsWideningAnalyzer(BoundsWideningAnalysis(SemaRef, Cfg, @@ -2584,7 +2681,16 @@ namespace { Info.BoundsVarsUpper)), AbstractSetMgr(AbstractSetManager(SemaRef, Info.VarUses)), BoundsSiblingFields(Info.BoundsSiblingFields), - IncludeNullTerminator(false) {} + IncludeNullTerminator(false) { + if (FD) { + ReturnVal = + new (S.Context) BoundsValueExpr(SourceLocation(), + FD->getReturnType(), + BoundsValueExpr::Kind::Return); + ReturnBounds = + BoundsUtil::ExpandToRange(S, ReturnVal, FD->getBoundsExpr()); + } + } CheckBoundsDeclarations(Sema &SemaRef, PrepassInfo &Info, std::pair &Facts) : S(SemaRef), DumpBounds(SemaRef.getLangOpts().DumpInferredBounds), @@ -2593,6 +2699,8 @@ namespace { PointerWidth(SemaRef.Context.getTargetInfo().getPointerWidth(0)), Body(nullptr), Cfg(nullptr), + FunctionDeclaration(nullptr), + ReturnVal(nullptr), ReturnBounds(nullptr), Context(SemaRef.Context), Facts(Facts), @@ -3921,14 +4029,13 @@ namespace { return ResultBounds; // Check the return value if it exists. - Check(RetValue, CSS, State); + BoundsExpr *RetValueBounds = Check(RetValue, CSS, State); - if (!ReturnBounds) - return ResultBounds; + // Check that the return expression bounds imply the return bounds. + ValidateReturnBounds(RS, RetValue, RetValueBounds, State.EquivExprs, + State.SameValue, CSS); - // TODO: Actually check that the return expression bounds imply the - // return bounds. - // TODO: Also check that any parameters used in the return bounds are + // TODO: Check that any parameters used in the return bounds are // unmodified. return ResultBounds; } @@ -4499,6 +4606,76 @@ namespace { return Loc; } + // ValidateReturnBounds checks that the observed bounds for the return + // value RetExpr imply the declared bounds for the enclosing function. + void ValidateReturnBounds(ReturnStmt *RS, Expr *RetExpr, + BoundsExpr *RetExprBounds, + const EquivExprSets EquivExprs, + const EqualExprTy RetSameValue, + CheckedScopeSpecifier CSS) { + // In an unchecked scope, if the enclosing function has a bounds-safe + // interface, and the return value has not been implicitly converted + // to an unchecked pointer, we skip checking the return value bounds. + if (CSS == CheckedScopeSpecifier::CSS_Unchecked) { + if (FunctionDeclaration->hasBoundsSafeInterface(Context)) { + if (!IsBoundsSafeInterfaceAssignment(FunctionDeclaration->getReturnType(), RetExpr)) + return; + } + } + + ProofFailure Cause; + FreeVariableListTy FreeVars; + ProofResult Result = ProveReturnBoundsValidity(RetExpr, RetExprBounds, + EquivExprs, RetSameValue, + Cause, FreeVars); + + if (Result == ProofResult::True) + return; + + if (RetExprBounds->isUnknown()) { + DiagnoseUnknownReturnBounds(RS, RetExpr); + return; + } + + // Which diagnostic message to print? + unsigned DiagId = + (Result == ProofResult::False) + ? (TestFailure(Cause, ProofFailure::HasFreeVariables) + ? diag::error_return_bounds_unprovable + : diag::error_return_bounds_invalid) + : (CSS != CheckedScopeSpecifier::CSS_Unchecked + ? diag::warn_checked_scope_return_bounds_invalid + : diag::warn_return_bounds_invalid); + + SourceLocation Loc = RetExpr->getBeginLoc(); + S.Diag(Loc, DiagId) << FunctionDeclaration << RS->getSourceRange(); + if (Result == ProofResult::False) + ExplainProofFailure(Loc, Cause, ProofStmtKind::ReturnStmt); + + if (TestFailure(Cause, ProofFailure::HasFreeVariables)) + DiagnoseFreeVariables(diag::note_free_variable_decl_or_inferred, Loc, + FreeVars); + + SourceLocation DeclaredLoc = FunctionDeclaration->getLocation(); + S.Diag(DeclaredLoc, diag::note_declared_return_bounds) + << ReturnBounds << ReturnBounds->getSourceRange(); + S.Diag(Loc, diag::note_inferred_return_bounds) + << RetExprBounds << RetExprBounds->getSourceRange(); + } + + // DiagnoseUnknownReturnBounds emits an error message at the return + // statement RS and return expression RetExpr whose inferred bounds are + // unknown, where the enclosing function has declared bounds ReturnBounds. + void DiagnoseUnknownReturnBounds(ReturnStmt *RS, Expr *RetExpr) { + SourceLocation Loc = RetExpr->getBeginLoc(); + S.Diag(Loc, diag::err_expected_bounds_for_return) + << FunctionDeclaration << RS->getSourceRange(); + + SourceLocation DeclaredLoc = FunctionDeclaration->getLocation(); + S.Diag(DeclaredLoc, diag::note_declared_return_bounds) + << ReturnBounds << ReturnBounds->getSourceRange(); + } + // Methods to update the checking state. // UpdateAfterAssignment updates the checking state after the lvalue @@ -6167,7 +6344,7 @@ void Sema::CheckFunctionBodyBoundsDecls(FunctionDecl *FD, Stmt *Body) { BO.AddLifetime = true; BO.AddNullStmt = true; std::unique_ptr Cfg = CFG::buildCFG(nullptr, Body, &getASTContext(), BO); - CheckBoundsDeclarations Checker(*this, Info, Body, Cfg.get(), FD->getBoundsExpr(), EmptyFacts); + CheckBoundsDeclarations Checker(*this, Info, Body, Cfg.get(), FD, EmptyFacts); if (Cfg != nullptr) { AvailableFactsAnalysis Collector(*this, Cfg.get()); Collector.Analyze(); diff --git a/clang/test/3C/functionDeclEnd.c b/clang/test/3C/functionDeclEnd.c index 795335db5583..9214b3763b4e 100644 --- a/clang/test/3C/functionDeclEnd.c +++ b/clang/test/3C/functionDeclEnd.c @@ -126,7 +126,7 @@ void test6(int *a) int *test7(int *a) : count(10) //CHECK_NOALL: int *test7(int *a : itype(_Ptr)) : count(10) -//CHECK_ALL: _Array_ptr test7(_Array_ptr a) : count(10) +//CHECK_ALL: _Array_ptr test7(_Array_ptr a : count(10)) : count(10) #else int *test7(int *a) : count(10) @@ -139,8 +139,10 @@ int *test7(int *a) //CHECK: ; int *test7(int *a) : count(10) { - //CHECK_ALL: _Array_ptr test7(_Array_ptr a) : count(10) _Checked { + //CHECK_ALL: _Array_ptr test7(_Array_ptr a : count(10)) : count(10) _Checked { //CHECK_NOALL: int *test7(int *a : itype(_Ptr)) : count(10) { + for (int i = 0; i < 10; i++) + a[i]; return a; } diff --git a/clang/test/CheckedC/dump-dataflow-facts.c b/clang/test/CheckedC/dump-dataflow-facts.c index 99a760761e9c..423afea790b4 100644 --- a/clang/test/CheckedC/dump-dataflow-facts.c +++ b/clang/test/CheckedC/dump-dataflow-facts.c @@ -187,7 +187,7 @@ _Nt_array_ptr fn_5(int a) { return 0; _Nt_array_ptr p : byte_count(d) = g(a); - return p; + return 0; // CHECK-LABEL: fn_5 // CHECK-NEXT: Block #4: { @@ -216,7 +216,7 @@ _Nt_array_ptr fn_6(int a) { int d; if (d <= a) { _Nt_array_ptr p : byte_count(d) = g(a); - return p; + return 0; } return 0; diff --git a/clang/test/CheckedC/static-checking/bounds-decl-checking.c b/clang/test/CheckedC/static-checking/bounds-decl-checking.c index 07b60c552fe2..76bcc8f73a57 100644 --- a/clang/test/CheckedC/static-checking/bounds-decl-checking.c +++ b/clang/test/CheckedC/static-checking/bounds-decl-checking.c @@ -298,20 +298,20 @@ _Array_ptr f37_i(unsigned num) : count(num) { _Array_ptr p : count(0) = q; // expected-warning {{cannot prove declared bounds for 'p' are valid after initialization}} \ // expected-note {{(expanded) declared bounds are 'bounds(p, p + 0)'}} \ // expected-note {{(expanded) inferred bounds are 'bounds(q, q + num)'}} - return p; + return q; } _Array_ptr f37(unsigned num) : count(num) { _Array_ptr q : count(num) = 0; _Array_ptr p : count(0) = q; - return p; + return q; } _Nt_array_ptr f37_n(unsigned num) : count(num) { _Nt_array_ptr q : count(num) = 0; _Nt_array_ptr p : count(0) = q; - return p; + return q; } // @@ -605,7 +605,7 @@ void a_f_15(void) { static _Array_ptr v23 : count(32768); static _Array_ptr a_f_16(int size) : byte_count(size) { v23 = simulate_calloc(32768, sizeof(char)); - return v23; + return 0; } // diff --git a/clang/test/CheckedC/static-checking/return-bounds.c b/clang/test/CheckedC/static-checking/return-bounds.c new file mode 100644 index 000000000000..54a5f43304fa --- /dev/null +++ b/clang/test/CheckedC/static-checking/return-bounds.c @@ -0,0 +1,218 @@ +// Tests for checking that the inferred bounds of a return value imply the +// declared return bounds for a function. Because the static checker is +// mostly unimplemented, we only issue warnings when return bounds cannot +// be proved to hold. +// +// RUN: %clang_cc1 -fcheckedc-extension -Wcheck-bounds-decls -verify %s + +// +// Test null bounds, bounds(unknown), and bounds(any) +// + +_Array_ptr f1(void) : count(1) { + return 0; +} + +_Array_ptr f2(_Array_ptr p : bounds(unknown)) { + return p; +} + +_Array_ptr f3(_Array_ptr p : bounds(unknown)) : bounds(unknown) { + return p; +} + +_Array_ptr f4(_Array_ptr p : bounds(unknown)) : count(1) { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + 1)'}} + return p; // expected-error {{return value has unknown bounds, bounds expected because the function 'f4' has bounds}} +} + +// +// Test no warnings or errors +// + +_Array_ptr f5(_Array_ptr p : count(2)) : bounds(p, p + 2) { + _Array_ptr q : count(2) = p; + return q; +} + +_Nt_array_ptr f6(void) : count(3) { + return "abcd"; +} + +_Array_ptr f7(_Array_ptr p : count(8)) : bounds(_Return_value, _Return_value + 4) { + return p; +} + +// +// Test bounds warnings +// + +_Nt_array_ptr f8(_Nt_array_ptr p, int test) : count(0) { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + 0)'}} + if (test) + return p; + return p + 1; // expected-warning {{cannot prove return value bounds imply declared return bounds for 'f8'}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(p, p + 0)'}} +} + +_Array_ptr f9(int i, int j) : count(i) { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + i)'}} + i = j + 1; + _Array_ptr p : count(j) = 0; + return p; // expected-warning {{cannot prove return value bounds imply declared return bounds for 'f9'}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(p, p + j)'}} +} + +_Array_ptr f10(_Array_ptr p : bounds(p, (p + i) + 1), int i) : bounds(p, (p + i) + 1) { // expected-note {{(expanded) declared return bounds are 'bounds(p, (p + i) + 1)'}} + _Array_ptr q : bounds(p, p + (i - 1)) = 0; + return q; // expected-warning {{cannot prove return value bounds imply declared return bounds for 'f10'}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(p, p + (i - 1))'}} +} + +// +// Test bounds errors +// + +_Array_ptr f11(_Array_ptr p : count(1)) : bounds(_Return_value, _Return_value + 2) { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + 2)'}} + return p; // expected-error {{return value bounds do not imply declared return bounds for 'f11'}} \ + // expected-note {{declared return bounds are wider than the return value bounds}} \ + // expected-note {{declared return upper bound is above return value upper bound}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(p, p + 1)'}} +} + +_Array_ptr f12(_Array_ptr p : count(2)) : bounds(p - 1, p + 2) { // expected-note {{(expanded) declared return bounds are 'bounds(p - 1, p + 2)'}} + return p; // expected-error {{return value bounds do not imply declared return bounds for 'f12'}} \ + // expected-note {{declared return bounds are wider than the return value bounds}} \ + // expected-note {{declared return lower bound is below return value lower bound}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(p, p + 2)'}} +} + +_Nt_array_ptr f13(_Nt_array_ptr p, // expected-note 2 {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + 3)'}} + _Nt_array_ptr q : bounds(q + 1, q + 2), + int test) : count(3) _Unchecked { + if (test) + return p; // expected-error {{return value bounds do not imply declared return bounds for 'f13'}} \ + // expected-note {{source bounds are an empty range}} \ + // expected-note {{declared return upper bound is above return value upper bound}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(p, p + 0)'}} + else + return q; // expected-error {{return value bounds do not imply declared return bounds for 'f13'}} \ + // expected-note {{declared return bounds are wider than the return value bounds}} \ + // expected-note {{declared return lower bound is below return value lower bound}} \ + // expected-note {{declared return upper bound is above return value upper bound}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(q + 1, q + 2)'}} +} + +// +// Test free variable bounds errors +// + +_Array_ptr f14(_Array_ptr p : count(i), int i) : count(2) { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + 2)'}} + return p; // expected-error {{it is not possible to prove that return value bounds imply declared return bounds for 'f14'}} \ + // expected-note {{the inferred upper bounds use the variable 'i' and there is no relational information involving 'i' and any of the expressions used by the declared upper bounds}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(p, p + i)'}} +} + +_Array_ptr f15(_Array_ptr p : count(3), // expected-note {{(expanded) declared return bounds are 'bounds(q, q + 3)'}} + _Array_ptr q : bounds(p, p + 3)) : bounds(q, q + 3) { + return q; // expected-error {{it is not possible to prove that return value bounds imply declared return bounds for 'f15'}} \ + // expected-note {{the declared bounds use the variable 'q' and there is no relational information involving 'q' and any of the expressions used by the inferred bounds}} \ + // expected-note {{the inferred bounds use the variable 'p' and there is no relational information involving 'p' and any of the expressions used by the declared bounds}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(p, p + 3)'}} +} + +_Nt_array_ptr f16(int i) : count(i); + +_Nt_array_ptr f16(int i) : count(i) { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + i)'}} + return "abc"; // expected-error {{it is not possible to prove that return value bounds imply declared return bounds for 'f16'}} \ + // expected-note {{the declared upper bounds use the variable 'i' and there is no relational information involving 'i' and any of the expressions used by the inferred upper bounds}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(value of "abc", value of "abc" + 3)'}} +} + +// +// Test bounds-safe interfaces +// + +int *f17(int p[2]) : byte_count(4) _Unchecked { + return p; +} + +int *f18(int *p : count(2)) : count(5) _Unchecked { + return p; +} + +int *f19(int *p : count(i), unsigned int i) : count(i + 1) _Unchecked { + return (p); +} + +int *f20(_Array_ptr p : bounds(unknown)) : count(6) _Unchecked { + return (int *)p; +} + +int *f21(int *p : count(7)) : count(7) _Unchecked { + return p + 1; +} + +int *f22(_Array_ptr p) : count(8) _Unchecked { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + 8)'}} + return p; // expected-error {{return value has unknown bounds, bounds expected because the function 'f22' has bounds}} +} + +int *f23(int *p : count(1)) : count(9) _Unchecked { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + 9)'}} + return (_Array_ptr)p; // expected-error {{return value bounds do not imply declared return bounds for 'f23'}} \ + // expected-note {{declared return bounds are wider than the return value bounds}} \ + // expected-note {{declared return upper bound is above return value upper bound}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(p, p + 1)'}} +} + +int *f24(int *p : bounds(unknown)) : count(4) _Checked { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + 4)'}} + return p; // expected-error {{return value has unknown bounds, bounds expected because the function 'f24' has bounds}} +} + +int *f25(int *p : count(5)) : bounds(p, p + 5) _Checked { + return p + 1; +} + +int *f26(int *p : itype(_Array_ptr), int test) : count(6) _Checked { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + 6)'}} + if (test) + return p; // expected-error {{return value has unknown bounds, bounds expected because the function 'f26' has bounds}} + + _Unchecked { return p; } +} + +_Array_ptr f27(int *p : count(i), int i) : count(i + 1) _Checked { // expected-note {{(expanded) declared return bounds are 'bounds(_Return_value, _Return_value + i + 1)'}} + return p; // expected-warning {{cannot prove return value bounds imply declared return bounds for 'f27'}} \ + // expected-note {{(expanded) inferred return value bounds are 'bounds(p, p + i)'}} +} + +// +// Test bounds casts +// + +_Array_ptr f28(_Array_ptr p : count(i), int i) : count(3) { + return _Dynamic_bounds_cast<_Array_ptr>(p, count(3)); +} + +_Array_ptr f29(_Array_ptr p : count(4), int i) : count(i) { + return _Assume_bounds_cast<_Array_ptr>(p, count(i)); +} + +_Array_ptr f30(_Array_ptr p : count(i), int i) : count(2) { + return _Dynamic_bounds_cast<_Array_ptr>(p + 1, count(2)); +} + +// +// Test function calls +// + +extern _Array_ptr g1(void) : count(2); +extern int *g2(int size) : count(size); +extern _Nt_array_ptr g3(_Nt_array_ptr p) : bounds(p, p); + +_Array_ptr f31(void) : count(2) { + return g1(); +} + +_Array_ptr f32(int len) : count(len + 1) { + return g2(len + 1); +} + +_Nt_array_ptr f33(_Nt_array_ptr p) : bounds(p, p) { + return g3(p); +}