diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index f3ff4fe4d39c9..95c805f636f9d 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -732,6 +732,10 @@ class ValueDecl : public NamedDecl { bool isInitCapture() const; /* TO_UPSTREAM(BoundsSafety) ON */ + bool isDependentValue() const; + bool isDependentValueWithoutDeref() const; + bool isDependentValueWithDeref() const; + bool isDependentValueThatIsUsedInInoutPointer() const; /// Whether this decl is a dependent parameter referred to by the return type /// that is a bounds-attributed type. bool isDependentParamOfReturnType( diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h index 30d3894982572..5ca126ffbe290 100644 --- a/clang/include/clang/AST/TypeBase.h +++ b/clang/include/clang/AST/TypeBase.h @@ -2651,6 +2651,7 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase { bool isAnyVaListType(ASTContext &) const; bool isDynamicRangePointerType() const; bool isBoundsAttributedType() const; + bool isBoundsAttributedTypeDependingOnInoutValue() const; bool isValueTerminatedType() const; bool isImplicitlyNullTerminatedType(const ASTContext &) const; /* TO_UPSTREAM(BoundsSafety) OFF */ diff --git a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h index 47753a7efdbbd..dd8c9db72a8b1 100644 --- a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h +++ b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h @@ -18,6 +18,7 @@ #include "clang/AST/Expr.h" #include "clang/AST/Stmt.h" #include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/Support/Debug.h" #include @@ -142,6 +143,53 @@ class UnsafeBufferUsageHandler { ASTContext &Ctx) { handleUnsafeOperation(Arg, IsRelatedToDecl, Ctx); } + + virtual void handleStandaloneAssign(const Expr *E, const ValueDecl *VD, + bool IsRelatedToDecl, ASTContext &Ctx) { + handleUnsafeOperation(E, IsRelatedToDecl, Ctx); + } + + enum class AssignToImmutableObjectKind { + PointerToPointer, + PointerToDependentValue, + PointerDependingOnInoutValue, + DependentValueUsedInInoutPointer, + }; + + virtual void handleAssignToImmutableObject(const BinaryOperator *Assign, + const ValueDecl *VD, + AssignToImmutableObjectKind Kind, + bool IsRelatedToDecl, + ASTContext &Ctx) { + handleUnsafeOperation(Assign, IsRelatedToDecl, Ctx); + } + + virtual void handleMissingAssignments( + const Expr *LastAssignInGroup, + const llvm::SmallPtrSetImpl &Required, + const llvm::SmallPtrSetImpl &Missing, + bool IsRelatedToDecl, ASTContext &Ctx) { + handleUnsafeOperation(LastAssignInGroup, IsRelatedToDecl, Ctx); + } + + virtual void handleDuplicatedAssignment(const BinaryOperator *Assign, + const BinaryOperator *PrevAssign, + const ValueDecl *VD, + bool IsRelatedToDecl, + ASTContext &Ctx) { + handleUnsafeOperation(Assign, IsRelatedToDecl, Ctx); + } + + virtual void handleAssignedAndUsed(const BinaryOperator *Assign, + const Expr *Use, const ValueDecl *VD, + bool IsRelatedToDecl, ASTContext &Ctx) { + handleUnsafeOperation(Assign, IsRelatedToDecl, Ctx); + } + + virtual void handleUnsafeCountAttributedPointerAssignment( + const BinaryOperator *Assign, bool IsRelatedToDecl, ASTContext &Ctx) { + handleUnsafeOperation(Assign, IsRelatedToDecl, Ctx); + } /* TO_UPSTREAM(BoundsSafety) OFF */ /// Invoked when a fix is suggested against a variable. This function groups @@ -196,7 +244,8 @@ class UnsafeBufferUsageHandler { // This function invokes the analysis and allows the caller to react to it // through the handler class. void checkUnsafeBufferUsage(const Decl *D, UnsafeBufferUsageHandler &Handler, - bool EmitSuggestions); + bool EmitSuggestions, + bool BoundsSafetyAttributes = false); namespace internal { // Tests if any two `FixItHint`s in `FixIts` conflict. Two `FixItHint`s diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index c0b1b0cb40fa4..a6b6a53fd994c 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -14236,6 +14236,29 @@ def note_unsafe_count_attributed_pointer_argument_null_to_nonnull : Note< def warn_unsafe_single_pointer_argument : Warning< "unsafe assignment to function parameter of __single pointer type">, InGroup, DefaultIgnore; +def warn_standalone_assign_to_bounds_attributed_object : Warning< + "assignment to %select{bounds-attributed pointer|dependent value}0 %1 " + "must be inside of a bounds-attributed group in a compound statement">, + InGroup, DefaultIgnore; +def warn_cannot_assign_to_immutable_bounds_attributed_object : Warning< + "cannot assign to %select{variable|parameter|member}0 %1 because %select{" + "it points to a bounds-attributed pointer|" + "it points to a dependent value|" + "its type depends on an inout dependent value|" + "it's used as dependent value in an inout bounds-attributed pointer" + "}2">, InGroup, DefaultIgnore; +def warn_missing_assignments_in_bounds_attributed_group : Warning< + "bounds-attributed group requires assigning '%0', assignments to '%1' missing">, + InGroup, DefaultIgnore; +def warn_duplicated_assignment_in_bounds_attributed_group : Warning< + "duplicated assignment to %select{variable|parameter|member}0 %1 in " + "bounds-attributed group">, InGroup, DefaultIgnore; +def warn_assigned_and_used_in_bounds_attributed_group : Warning< + "%select{variable|parameter|member}0 %1 is assigned and used in the same " + "bounds-attributed group">, InGroup, DefaultIgnore; +def warn_unsafe_count_attributed_pointer_assignment : Warning< + "unsafe assignment to count-attributed pointer">, + InGroup, DefaultIgnore; #ifndef NDEBUG // Not a user-facing diagnostic. Useful for debugging false negatives in // -fsafe-buffer-usage-suggestions (i.e. lack of -Wunsafe-buffer-usage fixits). diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index accfe96c9cdb7..e17336a4c804c 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -5502,6 +5502,27 @@ bool ValueDecl::isParameterPack() const { } /* TO_UPSTREAM(BoundsSafety) ON */ +bool ValueDecl::isDependentValue() const { + return hasAttr(); +} + +bool ValueDecl::isDependentValueWithoutDeref() const { + const auto *Att = getAttr(); + return Att && !Att->getIsDeref(); +} + +bool ValueDecl::isDependentValueWithDeref() const { + const auto *Att = getAttr(); + return Att && Att->getIsDeref(); +} + +bool ValueDecl::isDependentValueThatIsUsedInInoutPointer() const { + const auto *Att = getAttr(); + return Att && + std::any_of(Att->dependerLevels_begin(), Att->dependerLevels_end(), + [](unsigned Level) { return Level > 0; }); +} + bool ValueDecl::isDependentParamOfReturnType( const BoundsAttributedType **RetType, const TypeCoupledDeclRefInfo **Info) const { diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 08b9b40b3f8a6..3fcda2abe0758 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -829,6 +829,14 @@ bool Type::isBoundsAttributedType() const { return getAs(); } +bool Type::isBoundsAttributedTypeDependingOnInoutValue() const { + const auto *BAT = getAs(); + return BAT && + std::any_of( + BAT->dependent_decl_begin(), BAT->dependent_decl_end(), + [](const TypeCoupledDeclRefInfo &Info) { return Info.isDeref(); }); +} + bool Type::isValueTerminatedType() const { return getAs(); } diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp index 5c38255d2b839..fe767e86a73f4 100644 --- a/clang/lib/Analysis/UnsafeBufferUsage.cpp +++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp @@ -29,6 +29,7 @@ #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/APSInt.h" +#include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" @@ -427,7 +428,8 @@ const Expr *findCountArg(const Expr *Count, const CallExpr *Call) { } // Mapping: dependent decl -> value. -using DependentValuesTy = llvm::DenseMap; +using DeclDerefPair = llvm::PointerIntPair; +using DependentValuesTy = llvm::DenseMap; // Given the call expr, find the mapping from the dependent parameter to the // argument that is passed to that parameter. @@ -445,7 +447,8 @@ getDependentValuesFromCall(const CountAttributedType *CAT, return std::nullopt; const Expr *Arg = Call->getArg(Index); - [[maybe_unused]] bool Inserted = Values.insert({PVD, Arg}).second; + [[maybe_unused]] bool Inserted = + Values.insert({{PVD, /*Deref=*/false}, Arg}).second; assert(Inserted); } return {std::move(Values)}; @@ -499,13 +502,16 @@ struct CompatibleCountExprVisitor const Expr * trySubstituteAndSimplify(const Expr *E, bool &hasBeenSubstituted, const DependentValuesTy *DependentValues) const { + auto trySubstitute = [&](const ValueDecl *VD, bool Deref) -> const Expr * { + if (hasBeenSubstituted || !DependentValues) + return nullptr; + auto It = DependentValues->find({VD, Deref}); + return It != DependentValues->end() ? It->second : nullptr; + }; + // Attempts to simplify `E`: if `E` has the form `*&e`, return `e`; // return `E` without change otherwise: - auto trySimplifyDerefAddressof = - [](const Expr *E, - const DependentValuesTy - *DependentValues, // Deref may need subsitution - bool &hasBeenSubstituted) -> const Expr * { + auto trySimplifyDerefAddressof = [&](const Expr *E) -> const Expr * { const auto *Deref = dyn_cast(E->IgnoreParenImpCasts()); if (!Deref || Deref->getOpcode() != UO_Deref) @@ -513,36 +519,40 @@ struct CompatibleCountExprVisitor const Expr *DerefOperand = Deref->getSubExpr()->IgnoreParenImpCasts(); + // Just simplify `*&...`. if (const auto *UO = dyn_cast(DerefOperand)) if (UO->getOpcode() == UO_AddrOf) return UO->getSubExpr(); + if (const auto *DRE = dyn_cast(DerefOperand)) { - if (!DependentValues || hasBeenSubstituted) - return E; - - if (auto I = DependentValues->find(DRE->getDecl()); - I != DependentValues->end()) - if (const auto *UO = dyn_cast( - I->getSecond()->IgnoreParenImpCasts())) - if (UO->getOpcode() == UO_AddrOf) { - hasBeenSubstituted = true; - return UO->getSubExpr(); - } + // Substitute `*x`. + if (const auto *Sub = trySubstitute(DRE->getDecl(), /*Deref=*/true)) { + hasBeenSubstituted = true; + return Sub; + } + + // Substitute `x` in `*x` if we have `x -> &...` in our mapping. + if (const auto *Sub = trySubstitute(DRE->getDecl(), /*Deref=*/false)) { + if (const auto *UO = + dyn_cast(Sub->IgnoreParenImpCasts()); + UO && UO->getOpcode() == UO_AddrOf) { + hasBeenSubstituted = true; + return UO->getSubExpr(); + } + } } + return E; }; - if (!hasBeenSubstituted && DependentValues) { - if (const auto *DRE = dyn_cast(E->IgnoreParenImpCasts())) { - if (auto It = DependentValues->find(DRE->getDecl()); - It != DependentValues->end()) { - hasBeenSubstituted = true; - return trySimplifyDerefAddressof(It->second, nullptr, - hasBeenSubstituted); - } + if (const auto *DRE = dyn_cast(E->IgnoreParenImpCasts())) { + if (const auto *Sub = trySubstitute(DRE->getDecl(), /*Deref=*/false)) { + hasBeenSubstituted = true; + return trySimplifyDerefAddressof(Sub); } } - return trySimplifyDerefAddressof(E, DependentValues, hasBeenSubstituted); + + return trySimplifyDerefAddressof(E); } explicit CompatibleCountExprVisitor( @@ -5433,9 +5443,509 @@ static void applyGadgets(const Decl *D, FixableGadgetList FixableGadgets, } } +// Checks if Self and Other are the same member bases. This supports only very +// simple forms of member bases. +static bool isSameMemberBase(const Expr *Self, const Expr *Other) { + for (;;) { + if (Self == Other) + return true; + + const auto *SelfICE = dyn_cast(Self); + const auto *OtherICE = dyn_cast(Other); + if (SelfICE && OtherICE && SelfICE->getCastKind() == CK_LValueToRValue && + OtherICE->getCastKind() == CK_LValueToRValue) { + Self = SelfICE->getSubExpr(); + Other = OtherICE->getSubExpr(); + } + + const auto *SelfDRE = dyn_cast(Self); + const auto *OtherDRE = dyn_cast(Other); + if (SelfDRE && OtherDRE) + return SelfDRE->getDecl() == OtherDRE->getDecl(); + + const auto *SelfME = dyn_cast(Self); + const auto *OtherME = dyn_cast(Other); + if (!SelfME || !OtherME || + SelfME->getMemberDecl() != OtherME->getMemberDecl()) { + return false; + } + + Self = SelfME->getBase(); + Other = OtherME->getBase(); + } +} + +using DependentDeclSetTy = llvm::SmallPtrSet; + +static DependentDeclSetTy GetBoundsAttributedClosure(const ValueDecl *InitVD) { + DependentDeclSetTy Set; + + llvm::SmallVector WorkList; + WorkList.push_back(InitVD); + + while (!WorkList.empty()) { + const ValueDecl *CurVD = WorkList.pop_back_val(); + bool Inserted = Set.insert(CurVD).second; + if (!Inserted) + continue; + + // If CurVD is a dependent decl, add the pointers that depend on CurVD. + for (const auto *Attr : CurVD->specific_attrs()) { + for (const Decl *D : Attr->dependerDecls()) { + if (const auto *VD = dyn_cast(D)) + WorkList.push_back(VD); + } + } + + // If CurVD is a bounds-attributed pointer (or pointer to it), add its + // dependent decls. + QualType Ty = CurVD->getType(); + const auto *BAT = Ty->getAs(); + if (!BAT && Ty->isPointerType()) + BAT = Ty->getPointeeType()->getAs(); + if (BAT) { + for (const auto &DI : BAT->dependent_decls()) + WorkList.push_back(DI.getDecl()); + } + } + + return Set; +} + +struct BoundsAttributedObject { + const ValueDecl *Decl = nullptr; + const Expr *MemberBase = nullptr; + int DerefLevel = 0; + + bool operator==(const BoundsAttributedObject &Other) const { + if (Other.Decl != Decl || Other.DerefLevel != DerefLevel) + return false; + if (Other.MemberBase == MemberBase) + return true; + if (Other.MemberBase == nullptr || MemberBase == nullptr) + return false; + return isSameMemberBase(Other.MemberBase, MemberBase); + } +}; + +static std::optional +getBoundsAttributedObject(const Expr *E) { + E = E->IgnoreParenCasts(); + + int DerefLevel = 0; + while (const auto *UO = dyn_cast(E)) { + if (UO->getOpcode() == UO_Deref) + DerefLevel++; + else if (UO->getOpcode() == UO_AddrOf) + DerefLevel--; + else + break; + E = UO->getSubExpr()->IgnoreParenCasts(); + } + assert(DerefLevel >= 0); + + const ValueDecl *Decl; + const Expr *MemberBase = nullptr; + + if (const auto *DRE = dyn_cast(E)) + Decl = DRE->getDecl(); + else if (const auto *ME = dyn_cast(E)) { + Decl = ME->getMemberDecl(); + MemberBase = ME->getBase(); + } else + return std::nullopt; + + QualType Ty = Decl->getType(); + bool IsBoundsAttributedPointer = + Ty->isBoundsAttributedType() || + (Ty->isPointerType() && Ty->getPointeeType()->isBoundsAttributedType()); + bool IsDependentDecl = Decl->hasAttr(); + if (IsBoundsAttributedPointer || IsDependentDecl) + return {{Decl, MemberBase, DerefLevel}}; + + return std::nullopt; +} + +struct BoundsAttributedAssignmentGroup { + DependentDeclSetTy DeclClosure; + llvm::SmallVector Assignments; + llvm::SmallVector AssignedObjects; + using DeclUseTy = std::pair; + llvm::SmallVector Uses; + const Expr *MemberBase = nullptr; + + void Init(const BoundsAttributedObject &Object) { + DeclClosure = GetBoundsAttributedClosure(Object.Decl); + MemberBase = Object.MemberBase; + } + + void Clear() { + DeclClosure.clear(); + Assignments.clear(); + AssignedObjects.clear(); + Uses.clear(); + MemberBase = nullptr; + } + + bool Empty() const { + return DeclClosure.empty(); + } + + bool IsPartOfGroup(const BoundsAttributedObject &Object) const { + if (!DeclClosure.contains(Object.Decl)) + return false; + if (MemberBase) + return Object.MemberBase && + isSameMemberBase(MemberBase, Object.MemberBase); + return true; + } + + void AddAssignment(const BoundsAttributedObject &Object, + const BinaryOperator *BO) { + Assignments.push_back(BO); + AssignedObjects.push_back(Object); + } + + void AddUseIfPartOfGroup(const BoundsAttributedObject &Object, + const Expr *E) { + if (IsPartOfGroup(Object)) + Uses.emplace_back(Object.Decl, E); + } +}; + +struct BoundsAttributedGroupFinder + : public ConstStmtVisitor { + using GroupHandlerTy = void(const BoundsAttributedAssignmentGroup &Group); + using AssignHandlerTy = void(const Expr *, const ValueDecl *); + std::function GroupHandler; + std::function BadStandaloneAssignHandler; + BoundsAttributedAssignmentGroup CurGroup; + + explicit BoundsAttributedGroupFinder( + std::function GroupHandler, + std::function BadStandaloneAssignHandler) + : GroupHandler(std::move(GroupHandler)), + BadStandaloneAssignHandler(std::move(BadStandaloneAssignHandler)) {} + + void VisitChildren(const Stmt *S) { + for (const Stmt *Child : S->children()) + Visit(Child); + } + + void VisitStmt(const Stmt *S) { VisitChildren(S); } + + void VisitCompoundStmt(const CompoundStmt *CS) { + for (const Stmt *Child : CS->children()) { + const Stmt *E = Child; + + if (const auto *EWC = dyn_cast(E)) + E = EWC->getSubExpr(); + + const auto *BO = dyn_cast(E); + if (BO && BO->getOpcode() == BO_Assign) + HandleAssignInCompound(BO); + else { + FinishGroup(); + Visit(Child); + } + } + + FinishGroup(); + } + + void HandleAssignInCompound(const BinaryOperator *AssignOp) { + const auto ObjectOpt = getBoundsAttributedObject(AssignOp->getLHS()); + if (!ObjectOpt.has_value()) { + FinishGroup(); + VisitChildren(AssignOp); + return; + } + + if (!CurGroup.IsPartOfGroup(*ObjectOpt)) { + FinishGroup(); + CurGroup.Init(*ObjectOpt); + } + + CurGroup.AddAssignment(*ObjectOpt, AssignOp); + VisitChildren(AssignOp->getRHS()); + } + + void FinishGroup() { + if (CurGroup.Empty()) + return; + GroupHandler(CurGroup); + CurGroup.Clear(); + } + + // Bad assigns. + + void VisitBinaryOperator(const BinaryOperator *BO) { + VisitChildren(BO); + + if (BO->isAssignmentOp()) + CheckBadStandaloneAssign(BO->getLHS()); + } + + void VisitUnaryOperator(const UnaryOperator *UO) { + VisitChildren(UO); + + if (UO->isIncrementDecrementOp()) + CheckBadStandaloneAssign(UO->getSubExpr()); + } + + void CheckBadStandaloneAssign(const Expr *E) { + const auto DA = getBoundsAttributedObject(E); + if (DA.has_value()) + BadStandaloneAssignHandler(E, DA->Decl); + } + + // Collect uses. + + void VisitDeclRefExpr(const DeclRefExpr *DRE) { + const auto DA = getBoundsAttributedObject(DRE); + if (DA.has_value()) + CurGroup.AddUseIfPartOfGroup(*DA, DRE); + } + + void VisitMemberExpr(const MemberExpr *ME) { + const auto DA = getBoundsAttributedObject(ME); + if (DA.has_value()) + CurGroup.AddUseIfPartOfGroup(*DA, ME); + + Visit(ME->getBase()); + } +}; + +static bool checkImmutableBoundsAttributedObjects( + const BoundsAttributedAssignmentGroup &Group, + UnsafeBufferUsageHandler &Handler, ASTContext &Ctx) { + bool Ok = true; + + for (size_t I = 0, N = Group.Assignments.size(); I < N; ++I) { + const BinaryOperator *Assign = Group.Assignments[I]; + const BoundsAttributedObject &Object = Group.AssignedObjects[I]; + + const ValueDecl *VD = Object.Decl; + QualType Ty = VD->getType(); + int DerefLevel = Object.DerefLevel; + + using AssignKind = UnsafeBufferUsageHandler::AssignToImmutableObjectKind; + + // void foo(int *__counted_by(*len) *p, int *len) { + // p = ...; + // } + if (DerefLevel == 0 && Ty->isPointerType() && + Ty->getPointeeType()->isBoundsAttributedType()) { + Handler.handleAssignToImmutableObject(Assign, VD, + AssignKind::PointerToPointer, + /*IsRelatedToDecl=*/false, Ctx); + Ok = false; + } + + // void foo(int *__counted_by(*len) *p, int *len) { + // len = ...; + // } + if (DerefLevel == 0 && VD->isDependentValueWithDeref()) { + Handler.handleAssignToImmutableObject(Assign, VD, + AssignKind::PointerToDependentValue, + /*IsRelatedToDecl=*/false, Ctx); + Ok = false; + } + + // `p` below should be immutable, because updating `p` would not be visible + // on the call-site to `foo`, but `*len` would, which invalidates the + // relation between the pointer and its count. + // void foo(int *__counted_by(*len) p, int *len) { + // p = ...; + // } + if (DerefLevel == 0 && Ty->isBoundsAttributedTypeDependingOnInoutValue()) { + Handler.handleAssignToImmutableObject( + Assign, VD, AssignKind::PointerDependingOnInoutValue, + /*IsRelatedToDecl=*/false, Ctx); + Ok = false; + } + + // Same as above, we cannot update `len`, because it won't be visible on + // the call-site. + // void foo(int *__counted_by(len) *p, int *__counted_by(len) q, int len) { + // len = ...; // bad because of p + // } + if (VD->isDependentValueWithoutDeref() && + VD->isDependentValueThatIsUsedInInoutPointer()) { + assert(DerefLevel == 0); + Handler.handleAssignToImmutableObject( + Assign, VD, AssignKind::DependentValueUsedInInoutPointer, + /*IsRelatedToDecl=*/false, Ctx); + Ok = false; + } + } + + return Ok; +} + +static bool checkMissingAndDuplicatedAssignments( + const BoundsAttributedAssignmentGroup &Group, + UnsafeBufferUsageHandler &Handler, ASTContext &Ctx) { + llvm::SmallDenseMap + AssignedDecls; + + DependentDeclSetTy RequiredDecls = Group.DeclClosure; + for (const ValueDecl *VD : Group.DeclClosure) { + if (VD->isDependentValueWithoutDeref() && + VD->isDependentValueThatIsUsedInInoutPointer()) { + // This value is immutable, so it's not required to be assigned. + RequiredDecls.erase(VD); + } + } + + DependentDeclSetTy MissingDecls = RequiredDecls; + + bool Ok = true; + + for (size_t I = 0, N = Group.Assignments.size(); I < N; ++I) { + const BinaryOperator *Assign = Group.Assignments[I]; + const ValueDecl *VD = Group.AssignedObjects[I].Decl; + + const auto [It, Inserted] = AssignedDecls.insert({VD, Assign}); + if (Inserted) + MissingDecls.erase(VD); + else { + const BinaryOperator *PrevAssign = It->second; + Handler.handleDuplicatedAssignment(Assign, PrevAssign, VD, + /*IsRelatedToDecl=*/false, Ctx); + Ok = false; + } + } + + if (!MissingDecls.empty()) { + const Expr *LastAssign = Group.Assignments.back(); + Handler.handleMissingAssignments(LastAssign, RequiredDecls, MissingDecls, + /*IsRelatedToDecl=*/false, Ctx); + Ok = false; + } + + return Ok; +} + +static bool checkAssignedAndUsed(const BoundsAttributedAssignmentGroup &Group, + UnsafeBufferUsageHandler &Handler, + ASTContext &Ctx) { + if (Group.Uses.empty()) + return true; + + llvm::SmallDenseMap Assigns; + for (size_t I = 0, N = Group.AssignedObjects.size(); I < N; ++I) { + const BoundsAttributedObject &LHSObj = Group.AssignedObjects[I]; + const BinaryOperator *Assign = Group.Assignments[I]; + + // Ignore self assignments, because they don't matter, since the value stays + // the same. + const auto RHSObj = getBoundsAttributedObject(Assign->getRHS()); + bool IsSelfAssign = RHSObj.has_value() && *RHSObj == LHSObj; + if (IsSelfAssign) + continue; + + const ValueDecl *VD = LHSObj.Decl; + [[maybe_unused]] bool Inserted = Assigns.insert({VD, Assign}).second; + assert(Inserted); + } + + bool Ok = true; + + for (const auto [VD, Use] : Group.Uses) { + const auto It = Assigns.find(VD); + if (It != Assigns.end()) { + Handler.handleAssignedAndUsed(It->second, Use, VD, + /*IsRelatedToDecl=*/false, Ctx); + Ok = false; + } + } + + return Ok; +} + +static bool +checkAssignmentPatterns(const BoundsAttributedAssignmentGroup &Group, + UnsafeBufferUsageHandler &Handler, ASTContext &Ctx) { + // Collect dependent values. + DependentValuesTy DependentValues; + for (size_t I = 0, N = Group.AssignedObjects.size(); I < N; ++I) { + const ValueDecl *VD = Group.AssignedObjects[I].Decl; + const auto *Attr = VD->getAttr(); + if (!Attr) + continue; + + const BinaryOperator *Assign = Group.Assignments[I]; + const Expr *Value = Assign->getRHS(); + + [[maybe_unused]] bool Inserted = + DependentValues.insert({{VD, Attr->getIsDeref()}, Value}).second; + // Previous check should have validated that we have only a single + // assignment. + assert(Inserted); + } + + bool Ok = true; + + // Check every pointer in the group. + for (size_t I = 0, N = Group.AssignedObjects.size(); I < N; ++I) { + const ValueDecl *VD = Group.AssignedObjects[I].Decl; + + QualType Ty = VD->getType(); + const auto *CAT = Ty->getAs(); + if (!CAT && Ty->isPointerType()) + CAT = Ty->getPointeeType()->getAs(); + if (!CAT) + continue; + + const BinaryOperator *Assign = Group.Assignments[I]; + + // TODO: Move this logic to isCountAttributedPointerArgumentSafeImpl. + const Expr *CountArg = + DependentValues.size() == 1 ? DependentValues.begin()->second : nullptr; + + bool IsSafe = isCountAttributedPointerArgumentSafeImpl( + Ctx, Assign->getRHS(), CountArg, CAT, CAT->getCountExpr(), + CAT->isCountInBytes(), CAT->isOrNull(), &DependentValues); + if (!IsSafe) { + Handler.handleUnsafeCountAttributedPointerAssignment( + Assign, /*IsRelatedToDecl=*/false, Ctx); + Ok = false; + } + } + + return Ok; +} + +static bool +checkBoundsAttributedGroup(const BoundsAttributedAssignmentGroup &Group, + UnsafeBufferUsageHandler &Handler, ASTContext &Ctx) { + if (!checkImmutableBoundsAttributedObjects(Group, Handler, Ctx)) + return false; + if (!checkMissingAndDuplicatedAssignments(Group, Handler, Ctx)) + return false; + if (!checkAssignedAndUsed(Group, Handler, Ctx)) + return false; + return checkAssignmentPatterns(Group, Handler, Ctx); +} + +static void checkBoundsSafetyAssignments(const Stmt *S, + UnsafeBufferUsageHandler &Handler, + ASTContext &Ctx) { + BoundsAttributedGroupFinder Finder( + [&](const BoundsAttributedAssignmentGroup &Group) { + checkBoundsAttributedGroup(Group, Handler, Ctx); + }, + [&](const Expr *E, const ValueDecl *VD) { + Handler.handleStandaloneAssign(E, VD, /*IsRelatedToDecl=*/false, Ctx); + }); + Finder.Visit(S); +} + void clang::checkUnsafeBufferUsage(const Decl *D, UnsafeBufferUsageHandler &Handler, - bool EmitSuggestions) { + bool EmitSuggestions, + bool BoundsSafetyAttributes) { #ifndef NDEBUG Handler.clearDebugNotes(); #endif @@ -5481,6 +5991,11 @@ void clang::checkUnsafeBufferUsage(const Decl *D, for (Stmt *S : Stmts) { findGadgets(S, D->getASTContext(), Handler, EmitSuggestions, FixableGadgets, WarningGadgets, Tracker); + + // Run the bounds-safety assignment analysis if the attributes are enabled, + // otherwise don't waste cycles. + if (BoundsSafetyAttributes) + checkBoundsSafetyAssignments(S, Handler, D->getASTContext()); } applyGadgets(D, std::move(FixableGadgets), std::move(WarningGadgets), std::move(Tracker), Handler, EmitSuggestions); diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 2dc393b55581d..359d085ffa3d2 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2609,6 +2609,93 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler { ASTContext &Ctx) override { S.Diag(Arg->getBeginLoc(), diag::warn_unsafe_single_pointer_argument); } + + void handleStandaloneAssign(const Expr *E, const ValueDecl *VD, + bool IsRelatedToDecl, ASTContext &Ctx) override { + SourceLocation Loc = E->getBeginLoc(); + if (const auto *BO = dyn_cast(E)) + Loc = BO->getOperatorLoc(); + + S.Diag(Loc, diag::warn_standalone_assign_to_bounds_attributed_object) + << VD->isDependentValue() << VD; + } + + static int getBoundsAttributedObjectKind(const ValueDecl *VD) { + if (isa(VD)) + return 1; + if (isa(VD)) + return 2; + return 0; // variable + } + + void handleAssignToImmutableObject(const BinaryOperator *Assign, + const ValueDecl *VD, + AssignToImmutableObjectKind Kind, + bool IsRelatedToDecl, + ASTContext &Ctx) override { + S.Diag(Assign->getOperatorLoc(), + diag::warn_cannot_assign_to_immutable_bounds_attributed_object) + << getBoundsAttributedObjectKind(VD) << VD << Kind; + } + + static llvm::SmallString<64> + DeclSetToStr(const llvm::SmallPtrSetImpl &Set) { + llvm::SmallVector V(Set.begin(), Set.end()); + llvm::sort(V.begin(), V.end(), [](const ValueDecl *A, const ValueDecl *B) { + return A->getName().compare(B->getName()) < 0; + }); + llvm::SmallString<64> Str; + for (const ValueDecl *VD : V) { + if (!Str.empty()) + Str += ", "; + Str += VD->getName(); + } + return Str; + } + + void handleMissingAssignments( + const Expr *LastAssignInGroup, + const llvm::SmallPtrSetImpl &Required, + const llvm::SmallPtrSetImpl &Missing, + bool IsRelatedToDecl, ASTContext &Ctx) override { + + llvm::SmallString<64> RequiredAssignments = DeclSetToStr(Required); + llvm::SmallString<64> MissingAssignments = DeclSetToStr(Missing); + auto Loc = + CharSourceRange::getTokenRange(LastAssignInGroup->getSourceRange()) + .getEnd(); + + S.Diag(Loc, diag::warn_missing_assignments_in_bounds_attributed_group) + << RequiredAssignments << MissingAssignments; + } + + void handleDuplicatedAssignment(const BinaryOperator *Assign, + const BinaryOperator *PrevAssign, + const ValueDecl *VD, bool IsRelatedToDecl, + ASTContext &Ctx) override { + S.Diag(Assign->getOperatorLoc(), + diag::warn_duplicated_assignment_in_bounds_attributed_group) + << getBoundsAttributedObjectKind(VD) << VD; + S.Diag(PrevAssign->getOperatorLoc(), + diag::note_bounds_safety_previous_assignment); + } + + void handleAssignedAndUsed(const BinaryOperator *Assign, const Expr *Use, + const ValueDecl *VD, bool IsRelatedToDecl, + ASTContext &Ctx) override { + S.Diag(Assign->getOperatorLoc(), + diag::warn_assigned_and_used_in_bounds_attributed_group) + << getBoundsAttributedObjectKind(VD) << VD; + S.Diag(Use->getBeginLoc(), diag::note_used_here); + } + + void + handleUnsafeCountAttributedPointerAssignment(const BinaryOperator *Assign, + bool IsRelatedToDecl, + ASTContext &Ctx) override { + S.Diag(Assign->getOperatorLoc(), + diag::warn_unsafe_count_attributed_pointer_assignment); + } /* TO_UPSTREAM(BoundsSafety) OFF */ void handleUnsafeVariableGroup(const VarDecl *Variable, @@ -3455,6 +3542,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( bool UnsafeBufferUsageShouldSuggestSuggestions = UnsafeBufferUsageCanEmitSuggestions && !DiagOpts.ShowSafeBufferUsageSuggestions; + bool BoundsSafetyAttributes = S.getLangOpts().BoundsSafetyAttributes; UnsafeBufferUsageReporter R(S, UnsafeBufferUsageShouldSuggestSuggestions); // The Callback function that performs analyses: @@ -3472,7 +3560,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( !Diags.isIgnored(diag::warn_unsafe_buffer_libc_call, Node->getBeginLoc())) { clang::checkUnsafeBufferUsage(Node, R, - UnsafeBufferUsageShouldEmitSuggestions); + UnsafeBufferUsageShouldEmitSuggestions, + BoundsSafetyAttributes); } // More analysis ... diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-count-attributed-pointer-assignment.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-count-attributed-pointer-assignment.cpp new file mode 100644 index 0000000000000..2fb62db9878de --- /dev/null +++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-count-attributed-pointer-assignment.cpp @@ -0,0 +1,528 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++20 -Wno-all -Wunsafe-buffer-usage -fexperimental-bounds-safety-attributes -verify %s + +#include +#include + +namespace std { +template struct span { + T *data() const noexcept; + size_t size() const noexcept; + size_t size_bytes() const noexcept; + span first(size_t count) const noexcept; + span last(size_t count) const noexcept; + span subspan(size_t offset, size_t count) const noexcept; + const T &operator[](const size_t idx) const; +}; +} // namespace std + +struct cb { + int *__counted_by(count) p; + size_t count; +}; + +struct cb_multi { + int *__counted_by(m * n) p; + size_t m; + size_t n; +}; + +// Simple pointer and count + +void good_null(int *__counted_by(count) p, int count) { + p = nullptr; + count = 0; +} + +void good_simple_self(int *__counted_by(count) p, int count) { + p = p; + count = count; +} + +void good_simple_other(int *__counted_by(count) p, int count, int *__counted_by(len) q, int len) { + p = q; + count = len; +} + +void good_simple_span(int *__counted_by(count) p, size_t count, std::span sp) { + p = sp.data(); + count = sp.size(); +} + +void bad_simple_span(int *__counted_by(count) p, size_t count, std::span sp) { + p = sp.data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + count = 42; +} + +void good_simple_subspan_const(int *__counted_by(count) p, int count, std::span sp) { + p = sp.first(42).data(); + count = 42; +} + +void bad_simple_subspan_const(int *__counted_by(count) p, int count, std::span sp) { + p = sp.first(42).data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + count = 43; +} + +void good_simple_subspan_var(int *__counted_by(count) p, int count, std::span sp, int new_count) { + p = sp.first(new_count).data(); + count = new_count; +} + +void bad_simple_subspan_var(int *__counted_by(count) p, int count, std::span sp, int new_count, int new_count2) { + p = sp.first(new_count).data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + count = new_count2; +} + +void good_simple_ifelse(int *__counted_by(count) p, size_t count, std::span a, std::span b) { + if (count % 2 == 0) { + p = a.data(); + count = a.size(); + } else { + count = b.size(); + p = b.data(); + } +} + +// Simple pointer and count in struct + +void good_struct_self(cb *c) { + c->p = c->p; + c->count = c->count; +} + +void good_struct_other_struct(cb *c, cb *d) { + c->p = d->p; + c->count = d->count; +} + +void good_struct_other_param(cb *c, int *__counted_by(count) p, int count) { + c->p = p; + c->count = count; +} + +void good_struct_span(cb *c, std::span sp) { + c->p = sp.data(); + c->count = sp.size(); +} + +void bad_struct_span(cb *c, std::span sp) { + c->p = sp.data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + c->count = 42; +} + +void good_struct_subspan(cb *c, std::span sp) { + c->p = sp.first(42).data(); + c->count = 42; +} + +void bad_struct_subspan(cb *c, std::span sp) { + c->p = sp.first(42).data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + c->count = 43; +} + +void bad_struct_unrelated_structs(cb *c, cb *d, cb *e) { + c->p = d->p; // expected-warning{{unsafe assignment to count-attributed pointer}} + c->count = e->count; +} + +// Pointer with multiple counts + +void bad_multicounts_span(int *__counted_by(a + b) p, size_t a, size_t b, std::span sp) { + p = sp.data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + a = sp.size(); + b = sp.size(); +} + +void good_multicounts_subspan_const(int *__counted_by(a + b) p, int a, int b, std::span sp) { + p = sp.first(42 + 100).data(); + a = 42; + b = 100; +} + +void bad_multicounts_subspan_const(int *__counted_by(a + b) p, int a, int b, std::span sp) { + p = sp.first(42 + 100).data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + a = 42; + b = 99; +} + +void good_multicounts_subspan_var(int *__counted_by(a + b) p, int a, int b, std::span sp, int n, int m) { + p = sp.first(n + m).data(); + a = n; + b = m; +} + +void bad_multicounts_subspan_var(int *__counted_by(a + b) p, int a, int b, std::span sp, int n, int m) { + p = sp.first(n + m).data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + a = n; + b = n; +} + +// Pointer with multiple counts in struct + +void bad_multicount_struct_span(cb_multi *cbm, std::span sp) { + cbm->p = sp.data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + cbm->m = sp.size(); + cbm->n = sp.size(); +} + +void good_multicount_struct_subspan_const(cb_multi *cbm, std::span sp) { + cbm->p = sp.first(42 * 100).data(); + cbm->m = 42; + cbm->n = 100; +} + +void bad_multicount_struct_subspan_const(cb_multi *cbm, std::span sp) { + cbm->p = sp.first(42 * 100).data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + cbm->m = 43; + cbm->n = 100; +} + +void good_multicount_struct_subspan_var(cb_multi *cbm, std::span sp, size_t a, size_t b) { + cbm->p = sp.first(a * b).data(); + cbm->m = a; + cbm->n = b; +} + +void bad_multicount_struct_subspan_var(cb_multi *cbm, std::span sp, size_t a, size_t b) { + cbm->p = sp.first(a * b).data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + cbm->m = a; + cbm->n = a; +} + +bool good_multicount_struct_realistic(cb_multi *cbm, std::span sp, size_t stride) { + if (sp.size() % stride == 0) + return false; + size_t length = sp.size() / stride; + cbm->p = sp.first(length * stride).data(); + cbm->m = length; + cbm->n = stride; + return true; +} + +// Multiple pointers + +void good_multiptr_span(int *__counted_by(count) p, int *__counted_by(count) q, size_t count, std::span sp) { + p = sp.data(); + q = sp.data(); + count = sp.size(); +} + +void bad_multiptr_span(int *__counted_by(count) p, int *__counted_by(count) q, size_t count, std::span sp) { + p = sp.data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + q = sp.data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + count = 42; +} + +void bad_multiptr_span_subspan(int *__counted_by(count) p, int *__counted_by(count) q, size_t count, std::span sp) { + p = sp.data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + q = sp.first(42).data(); + count = 42; +} + +void bad_multiptr_span_subspan2(int *__counted_by(count) p, int *__counted_by(count) q, size_t count, std::span sp) { + p = sp.data(); + q = sp.first(42).data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + count = sp.size(); +} + +void good_multiptr_subspan(int *__counted_by(count) p, int *__counted_by(count) q, size_t count, std::span sp) { + p = sp.first(42).data(); + q = sp.last(42).data(); + count = 42; +} + +// Multiple pointers with multiple counts + +void good_multimix_subspan_complex(int *__counted_by(a * b) p, int *__counted_by((a + b) * c) q, size_t a, size_t b, size_t c, + std::span sp, size_t i, size_t j, size_t k) { + p = sp.first(i * j).data(); + q = sp.last((i + j) * k).data(); + a = i; + b = j; + c = k; +} + +void bad_multimix_subspan_complex(int *__counted_by(a * b) p, int *__counted_by((a + b) * c) q, size_t a, size_t b, size_t c, + std::span sp, size_t i, size_t j, size_t k) { + p = sp.first(i * j).data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + q = sp.last((i + j) * k).data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + a = k; + b = j; + c = i; +} + +void good_multimix_subspan_complex2(int *__counted_by(a * b) p, int *__counted_by((a + b) * c) q, size_t a, size_t b, size_t c, + std::span sp, size_t i, size_t j, size_t k) { + a = i * 2; + b = j; + c = j + k; + p = sp.first((i * 2) * j).data(); + q = sp.last(((i * 2) + j) * (j + k)).data(); +} + +void good_multimix_subspan_complex_multispan(int *__counted_by(a * b) p, int *__counted_by((a + b) * c) q, size_t a, size_t b, size_t c, + std::span sp, std::span sp2, size_t i, size_t j, size_t k) { + a = i; + b = j; + c = k; + p = sp.first(i * j).data(); + q = sp2.first((i + j) * k).data(); +} + +// Inout pointer and count + +void good_inout_span(int *__counted_by(*count) *p, size_t *count, std::span sp) { + *p = sp.data(); + *count = sp.size(); +} + +void good_inout_subspan_const(int *__counted_by(*count) *p, size_t *count, std::span sp) { + *p = sp.first(42).data(); + *count = 42; +} + +void good_inout_subspan_var(int *__counted_by(*count) *p, size_t *count, std::span sp, size_t new_count) { + *p = sp.first(new_count).data(); + *count = new_count; +} + +void good_inout_subspan_complex(int *__counted_by(*count) *p, size_t *count, std::span sp, size_t i, size_t j) { + *p = sp.first(i + j * 2).data(); + *count = i + j * 2; +} + +void good_inout_span_if(int *__counted_by(*count) *p, size_t *count, std::span sp) { + if (p && count) { + *p = sp.data(); + *count = sp.size(); + } +} + +void bad_inout_span_if(int *__counted_by(*count) *p, size_t *count, std::span sp, size_t size) { + if (p && count) { + *p = sp.data(); // expected-warning{{unsafe assignment to count-attributed pointer}} + *count = size; + } +} + +// Inout pointer + +void bad_inout_ptr_span(int *__counted_by(count) *p, int count, std::span sp) { + *p = sp.data(); // expected-warning{{unsafe assignment to count-attributed pointer}} +} + +void good_inout_ptr_subspan(int *__counted_by(count) *p, size_t count, std::span sp) { + *p = sp.first(count).data(); +} + +void good_inout_ptr_const_subspan(int *__counted_by(42) *p, std::span sp) { + *p = sp.first(42).data(); +} + +void good_inout_ptr_multi_subspan(int *__counted_by(a + b) *p, size_t a, size_t b, std::span sp) { + *p = sp.first(a + b).data(); +} + +// Immutable pointers/dependent values + +void immutable_ptr_to_ptr(int *__counted_by(*count) *p, int *count) { + p = nullptr; // expected-warning{{cannot assign to parameter 'p' because it points to a bounds-attributed pointer}} + *count = 0; +} + +void immutable_ptr_to_value(int *__counted_by(*count) *p, int *count) { + *p = nullptr; + count = nullptr; // expected-warning{{cannot assign to parameter 'count' because it points to a dependent value}} +} + +void immutable_ptr_with_inout_value(int *__counted_by(*count) p, int *count) { + p = nullptr; // expected-warning{{cannot assign to parameter 'p' because its type depends on an inout dependent value}} + *count = 0; +} + +void immutable_ptr_with_inout_value2(int *__counted_by(*count) p, int *__counted_by(*count) *q, int *count) { + p = nullptr; // expected-warning{{cannot assign to parameter 'p' because its type depends on an inout dependent value}} + *q = nullptr; + *count = 0; +} + +void immutable_value_with_inout_ptr(int *__counted_by(count) *p, int count) { + *p = nullptr; + count = 0; // expected-warning{{cannot assign to parameter 'count' because it's used as dependent value in an inout bounds-attributed pointer}} +} + +void immutable_value_with_inout_ptr2(int *__counted_by(count) p, int *__counted_by(count) *q, int count) { + p = nullptr; + *q = nullptr; + count = 0; // expected-warning{{cannot assign to parameter 'count' because it's used as dependent value in an inout bounds-attributed pointer}} +} + +// Missing assignments + +void missing_ptr(int *__counted_by(count) p, int count) { + count = 0; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'p' missing}} +} + +void missing_count(int *__counted_by(count) p, int count) { + p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'count' missing}} +} + +void missing_structure(int *__counted_by(count) p, int count) { + { + p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'count' missing}} + } + { + count = 0; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'p' missing}} + } +} + +void missing_structure2(int *__counted_by(count) p, int count) { + p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'count' missing}} + { + count = 0; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'p' missing}} + } +} + +void missing_structure3(int *__counted_by(count) p, int count) { + p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'count' missing}} + if (count > 0) { + count = 0; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'p' missing}} + } +} + +void missing_unrelated(int *__counted_by(count) p, int count, int *__counted_by(len) q, int len) { + p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'count' missing}} + len = 0; // expected-warning{{bounds-attributed group requires assigning 'len, q', assignments to 'q' missing}} +} + +void missing_complex_count1(int *__counted_by(a + b) p, int a, int b) { + p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'a, b, p', assignments to 'a, b' missing}} +} + +void missing_complex_count2(int *__counted_by(a + b) p, int a, int b) { + p = nullptr; + a = 0; // expected-warning{{bounds-attributed group requires assigning 'a, b, p', assignments to 'b' missing}} +} + +void missing_complex_count3(int *__counted_by(a + b) p, int a, int b) { + b = 0; + p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'a, b, p', assignments to 'a' missing}} +} + +void missing_complex_count4(int *__counted_by(a + b) p, int a, int b) { + a = 0; + b = 0; // expected-warning{{bounds-attributed group requires assigning 'a, b, p', assignments to 'p' missing}} +} + +void missing_complex_ptr1(int *__counted_by(count) p, int *__counted_by(count) q, int count) { + p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'count, p, q', assignments to 'count, q' missing}} +} + +void missing_complex_ptr2(int *__counted_by(count) p, int *__counted_by(count) q, int count) { + p = nullptr; + q = nullptr; // expected-warning{{bounds-attributed group requires assigning 'count, p, q', assignments to 'count' missing}} +} + +void missing_complex_ptr3(int *__counted_by(count) p, int *__counted_by(count) q, int count) { + count = 0; + p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'count, p, q', assignments to 'q' missing}} +} + +void missing_complex_ptr4(int *__counted_by(count) p, int *__counted_by(count) q, int count) { + q = nullptr; + count = 0; // expected-warning{{bounds-attributed group requires assigning 'count, p, q', assignments to 'p' missing}} +} + +// Missing assignments in struct + +void missing_struct_ptr(cb *c) { + c->count = 0; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'p' missing}} +} + +void missing_struct_count(cb *c) { + c->p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'count' missing}} +} + +void missing_struct_unrelated(cb *c, cb *d) { + c->p = nullptr; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'count' missing}} + d->count = 0; // expected-warning{{bounds-attributed group requires assigning 'count, p', assignments to 'p' missing}} +} + +// Duplicated assignments + +void duplicated_ptr(int *__counted_by(count) p, int count) { + p = nullptr; // expected-note{{previously assigned here}} + p = nullptr; // expected-warning{{duplicated assignment to parameter 'p' in bounds-attributed group}} + count = 0; +} + +void duplicated_ptr2(int *__counted_by(count) p, int count) { + p = nullptr; // expected-note{{previously assigned here}} + count = 0; + p = nullptr; // expected-warning{{duplicated assignment to parameter 'p' in bounds-attributed group}} +} + +void duplicated_count(int *__counted_by(count) p, int count) { + p = nullptr; + count = 0; // expected-note{{previously assigned here}} + count = 0; // expected-warning{{duplicated assignment to parameter 'count' in bounds-attributed group}} +} + +void duplicated_count2(int *__counted_by(count) p, int count) { + count = 0; // expected-note{{previously assigned here}} + p = nullptr; + count = 0; // expected-warning{{duplicated assignment to parameter 'count' in bounds-attributed group}} +} + +void duplicated_complex(int *__counted_by(a + b) p, + int *__counted_by(a + b + c) q, + int a, int b, int c) { + p = nullptr; + q = nullptr; // expected-note{{previously assigned here}} + a = 0; + b = 0; + c = 0; + q = nullptr; // expected-warning{{duplicated assignment to parameter 'q' in bounds-attributed group}} +} + +// Assigned and used + +void good_assigned_and_used(int *__counted_by(count) p, int count, std::span sp) { + p = sp.first(count).data(); + count = count; +} + +void bad_assigned_and_used(int *__counted_by(count) p, int count, std::span sp, int new_count) { + p = sp.first(count).data(); // expected-note{{used here}} + count = new_count; // expected-warning{{parameter 'count' is assigned and used in the same bounds-attributed group}} +} + +void bad_assigned_and_used2(int *__counted_by(a + b) p, int a, int b, std::span sp) { + p = sp.first(b + 42).data(); // expected-note{{used here}} + b = 42; // expected-warning 2{{parameter 'b' is assigned and used in the same bounds-attributed group}} + a = b; // expected-note{{used here}} +} + +// Standalone assigns to bounds-attributed + +void standalone_assign_to_ptr(int *__counted_by(count) p, int count, int *q) { + q = p; + q = p = q; // expected-warning{{assignment to bounds-attributed pointer 'p' must be inside of a bounds-attributed group in a compound statement}} + q = q = p = q; // expected-warning{{assignment to bounds-attributed pointer 'p' must be inside of a bounds-attributed group in a compound statement}} + + // FIXME: Duplicated error message + the error message doesn't make sense. + p++; // expected-warning{{unsafe pointer arithmetic}} expected-warning{{assignment to bounds-attributed pointer 'p' must be inside of a bounds-attributed group in a compound statement}} expected-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +} + +void standalone_assign_to_count(int *__counted_by(count) p, int count, int n) { + n = count; + n = count = n; // expected-warning{{assignment to dependent value 'count' must be inside of a bounds-attributed group in a compound statement}} + n = n = count = n; // expected-warning{{assignment to dependent value 'count' must be inside of a bounds-attributed group in a compound statement}} + + // FIXME: The error message doesn't make sense. + count++; // expected-warning{{assignment to dependent value 'count' must be inside of a bounds-attributed group in a compound statement}} + --count; // expected-warning{{assignment to dependent value 'count' must be inside of a bounds-attributed group in a compound statement}} + count += 42; // expected-warning{{assignment to dependent value 'count' must be inside of a bounds-attributed group in a compound statement}} + count -= 42; // expected-warning{{assignment to dependent value 'count' must be inside of a bounds-attributed group in a compound statement}} + n = n + count++; // expected-warning{{assignment to dependent value 'count' must be inside of a bounds-attributed group in a compound statement}} +}