diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index b66d0d1da2192..8b38be6b3b932 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -300,6 +300,72 @@ inline bool operator!=(const ContextInfo &LHS, const ContextInfo &RHS) { return !(LHS == RHS); } +/* TO_UPSTREAM(BoundsSafety) ON */ +class BoundsSafetyInfo { +public: + enum class BoundsSafetyKind { + CountedBy = 0, + CountedByOrNull, + SizedBy, + SizedByOrNull, + EndedBy, + }; + +private: + /// Whether this property has been audited for nullability. + LLVM_PREFERRED_TYPE(bool) + unsigned KindAudited : 1; + + /// The kind of nullability for this property. Only valid if the nullability + /// has been audited. + LLVM_PREFERRED_TYPE(BoundsSafetyKind) + unsigned Kind : 3; + + LLVM_PREFERRED_TYPE(bool) + unsigned LevelAudited : 1; + + unsigned Level : 3; + +public: + std::string ExternalBounds; + + BoundsSafetyInfo() + : KindAudited(false), Kind(0), LevelAudited(false), Level(0), + ExternalBounds("") {} + + std::optional getKind() const { + return KindAudited ? std::optional( + static_cast(Kind)) + : std::nullopt; + } + + void setKindAudited(BoundsSafetyKind kind) { + KindAudited = true; + Kind = static_cast(kind); + } + + std::optional getLevel() const { + return LevelAudited ? std::optional(Level) : std::nullopt; + } + + void setLevelAudited(unsigned level) { + LevelAudited = true; + Level = level; + } + + friend bool operator==(const BoundsSafetyInfo &, const BoundsSafetyInfo &); + + LLVM_DUMP_METHOD void dump(llvm::raw_ostream &OS) const; +}; + +inline bool operator==(const BoundsSafetyInfo &LHS, + const BoundsSafetyInfo &RHS) { + return LHS.KindAudited == RHS.KindAudited && LHS.Kind == RHS.Kind && + LHS.LevelAudited == RHS.LevelAudited && LHS.Level == RHS.Level && + LHS.ExternalBounds == RHS.ExternalBounds; +} +/* TO_UPSTREAM(BoundsSafety) OFF */ + /// API notes for a variable/property. class VariableInfo : public CommonEntityInfo { /// Whether this property has been audited for nullability. @@ -439,10 +505,14 @@ class ParamInfo : public VariableInfo { unsigned RawRetainCountConvention : 3; public: + /* TO_UPSTREAM(BoundsSafety) ON */ + std::optional BoundsSafety; + /* TO_UPSTREAM(BoundsSafety) OFF */ + ParamInfo() : NoEscapeSpecified(false), NoEscape(false), LifetimeboundSpecified(false), Lifetimebound(false), - RawRetainCountConvention() {} + RawRetainCountConvention(), BoundsSafety(std::nullopt) {} std::optional isNoEscape() const { return NoEscapeSpecified ? std::optional(NoEscape) : std::nullopt; @@ -488,6 +558,11 @@ class ParamInfo : public VariableInfo { if (!RawRetainCountConvention) RawRetainCountConvention = RHS.RawRetainCountConvention; + /* TO_UPSTREAM(BoundsSafety) ON */ + if (!BoundsSafety) + BoundsSafety = RHS.BoundsSafety; + /* TO_UPSTREAM(BoundsSafety) OFF */ + return *this; } @@ -502,7 +577,10 @@ inline bool operator==(const ParamInfo &LHS, const ParamInfo &RHS) { LHS.NoEscape == RHS.NoEscape && LHS.LifetimeboundSpecified == RHS.LifetimeboundSpecified && LHS.Lifetimebound == RHS.Lifetimebound && - LHS.RawRetainCountConvention == RHS.RawRetainCountConvention; + LHS.RawRetainCountConvention == RHS.RawRetainCountConvention && + /* TO_UPSTREAM(BoundsSafety) ON */ + LHS.BoundsSafety == RHS.BoundsSafety; + /* TO_UPSTREAM(BoundsSafety) OFF */ } inline bool operator!=(const ParamInfo &LHS, const ParamInfo &RHS) { diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 3f04a466c9098..fe4501bd31b1b 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2021,7 +2021,7 @@ class Parser : public CodeCompletionHandler { /// Parse a __builtin_bit_cast(T, E), used to implement C++2a std::bit_cast. ExprResult ParseBuiltinBitCast(); - /* TO_UPSTREAM(BoundsSafety) ON*/ + /* TO_UPSTREAM(BoundsSafety) ON */ //===--------------------------------------------------------------------===// /// BoundsSafety: __builtin_unsafe_forge_bidi_indexable(expr, size) ExprResult ParseUnsafeForgeBidiIndexable(); @@ -3924,6 +3924,25 @@ class Parser : public CodeCompletionHandler { /// \param IncludeLoc The location at which this parse was triggered. TypeResult ParseTypeFromString(StringRef TypeStr, StringRef Context, SourceLocation IncludeLoc); + /* TO_UPSTREAM(BoundsSafety) ON */ + /// Parse the given string as an expression in the argument position for a + /// bounds safety attribute. + /// + /// This is a dangerous utility function currently employed only by API notes. + /// It is not a general entry-point for safely parsing expressions from + /// strings. + /// + /// \param ExprStr The string to be parsed as an expression. + /// \param Context The name of the context in which this string is being + /// parsed, which will be used in diagnostics. + /// \param ParentDecl If a function or method is provided, the parameters are + /// added to the current parsing context. + /// \param IncludeLoc The location at which this parse was triggered. + ExprResult ParseBoundsAttributeArgFromString(StringRef ExprStr, + StringRef Context, + Decl *ParentDecl, + SourceLocation IncludeLoc); + /* TO_UPSTREAM(BoundsSafety) OFF */ //===--------------------------------------------------------------------===// // Modules diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 2620b56c38e6c..b49c7f37afb27 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1131,6 +1131,12 @@ class Sema final : public SemaBase { std::function ParseTypeFromStringCallback; + /* TO_UPSTREAM(BoundsSafety) ON */ + /// Callback to the parser to parse a type expressed as a string. + std::function + ParseBoundsAttributeArgFromStringCallback; + /* TO_UPSTREAM(BoundsSafety) OFF */ + /// VAListTagName - The declaration name corresponding to __va_list_tag. /// This is used as part of a hack to omit that class from ADL results. DeclarationName VAListTagName; @@ -15498,7 +15504,13 @@ class Sema final : public SemaBase { llvm::SmallVectorImpl &Decls, bool CountInBytes, bool OrNull); - /* TO_UPSTREAM(BoundsSafety) ON*/ + /* TO_UPSTREAM(BoundsSafety) ON */ + void applyPtrCountedByEndedByAttr(Decl *D, unsigned Level, + AttributeCommonInfo::Kind Kind, + Expr *AttrArg, SourceLocation Loc, + SourceRange Range, StringRef DiagName, + bool OriginatesInAPINotes = false); + /// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or /// `__counted_by_or_null` pointer type \param LHSTy. /// diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 939235179c363..189240f734a3c 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -24,7 +24,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 34; // SwiftReturnOwnership +const uint16_t VERSION_MINOR = 35; // BoundsSafety const uint8_t kSwiftConforms = 1; const uint8_t kSwiftDoesNotConform = 2; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 11d2c7a4c8df6..c1e2aec7ba7f1 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -322,6 +322,31 @@ class FieldTableInfo } }; +/* TO_UPSTREAM(BoundsSafety) ON */ +/// Read serialized BoundsSafetyInfo. +void ReadBoundsSafetyInfo(const uint8_t *&Data, BoundsSafetyInfo &Info) { + uint8_t Payload = endian::readNext(Data); + + if (Payload & 0x01) { + uint8_t Level = (Payload >> 1) & 0x7; + Info.setLevelAudited(Level); + } + Payload >>= 4; + + if (Payload & 0x01) { + uint8_t Kind = (Payload >> 1) & 0x7; + assert(Kind >= (uint8_t)BoundsSafetyInfo::BoundsSafetyKind::CountedBy); + assert(Kind <= (uint8_t)BoundsSafetyInfo::BoundsSafetyKind::EndedBy); + Info.setKindAudited((BoundsSafetyInfo::BoundsSafetyKind)Kind); + } + + uint16_t ExternalBoundsLen = + endian::readNext(Data); + Info.ExternalBounds = std::string(Data, Data + ExternalBoundsLen); + Data += ExternalBoundsLen; +} +/* TO_UPSTREAM(BoundsSafety) OFF */ + /// Read serialized ParamInfo. void ReadParamInfo(const uint8_t *&Data, ParamInfo &Info) { ReadVariableInfo(Data, Info); @@ -338,7 +363,13 @@ void ReadParamInfo(const uint8_t *&Data, ParamInfo &Info) { if (Payload & 0x01) Info.setNoEscape(Payload & 0x02); Payload >>= 2; - assert(Payload == 0 && "Bad API notes"); + /* TO_UPSTREAM(BoundsSafety) ON */ + if (Payload & 0x01) { + BoundsSafetyInfo BSI; + ReadBoundsSafetyInfo(Data, BSI); + Info.BoundsSafety = BSI; + } + /* TO_UPSTREAM(BoundsSafety) OFF */ } /// Read serialized FunctionInfo. diff --git a/clang/lib/APINotes/APINotesTypes.cpp b/clang/lib/APINotes/APINotesTypes.cpp index f726faa832bcc..df7b67a21b66e 100644 --- a/clang/lib/APINotes/APINotesTypes.cpp +++ b/clang/lib/APINotes/APINotesTypes.cpp @@ -61,6 +61,34 @@ LLVM_DUMP_METHOD void ObjCPropertyInfo::dump(llvm::raw_ostream &OS) const { OS << '\n'; } +LLVM_DUMP_METHOD void BoundsSafetyInfo::dump(llvm::raw_ostream &OS) const { + if (KindAudited) { + assert((BoundsSafetyKind)Kind >= BoundsSafetyKind::CountedBy); + assert((BoundsSafetyKind)Kind <= BoundsSafetyKind::EndedBy); + switch ((BoundsSafetyKind)Kind) { + case BoundsSafetyKind::CountedBy: + OS << "[counted_by] "; + break; + case BoundsSafetyKind::CountedByOrNull: + OS << "[counted_by_or_null] "; + break; + case BoundsSafetyKind::SizedBy: + OS << "[sized_by] "; + break; + case BoundsSafetyKind::SizedByOrNull: + OS << "[sized_by_or_null] "; + break; + case BoundsSafetyKind::EndedBy: + OS << "[ended_by] "; + break; + } + } + if (LevelAudited) + OS << "Level: " << Level << " "; + OS << "ExternalBounds: " + << (ExternalBounds.empty() ? "" : ExternalBounds) << '\n'; +} + LLVM_DUMP_METHOD void ParamInfo::dump(llvm::raw_ostream &OS) const { static_cast(*this).dump(OS); if (NoEscapeSpecified) @@ -69,6 +97,8 @@ LLVM_DUMP_METHOD void ParamInfo::dump(llvm::raw_ostream &OS) const { OS << (Lifetimebound ? "[Lifetimebound] " : ""); OS << "RawRetainCountConvention: " << RawRetainCountConvention << ' '; OS << '\n'; + if (BoundsSafety) + BoundsSafety->dump(OS); } LLVM_DUMP_METHOD void FunctionInfo::dump(llvm::raw_ostream &OS) const { diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index cbe6bccb80860..b7f3fa2a467a1 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -1074,15 +1074,48 @@ void APINotesWriter::Implementation::writeGlobalVariableBlock( } } +/* TO_UPSTREAM(BoundsSafety) ON */ namespace { +void emitBoundsSafetyInfo(raw_ostream &OS, const BoundsSafetyInfo &BSI) { + llvm::support::endian::Writer writer(OS, llvm::endianness::little); + uint8_t flags = 0; + if (auto kind = BSI.getKind()) { + flags |= 0x01; // 1 bit + flags |= (uint8_t)*kind << 1; // 3 bits + } + flags <<= 4; + if (auto level = BSI.getLevel()) { + flags |= 0x01; // 1 bit + flags |= *level << 1; // 3 bits + } + + writer.write(flags); + writer.write(BSI.ExternalBounds.size()); + writer.write( + ArrayRef{BSI.ExternalBounds.data(), BSI.ExternalBounds.size()}); +} + +unsigned getBoundsSafetyInfoSize(const BoundsSafetyInfo &BSI) { + return 1 + sizeof(uint16_t) + BSI.ExternalBounds.size(); +} +/* TO_UPSTREAM(BoundsSafety) OFF */ + unsigned getParamInfoSize(const ParamInfo &PI) { - return getVariableInfoSize(PI) + 1; + unsigned BSISize = 0; + /* TO_UPSTREAM(BoundsSafety) ON */ + if (auto BSI = PI.BoundsSafety) + BSISize = getBoundsSafetyInfoSize(*BSI); + /* TO_UPSTREAM(BoundsSafety) OFF */ + return getVariableInfoSize(PI) + 1 + BSISize; } void emitParamInfo(raw_ostream &OS, const ParamInfo &PI) { emitVariableInfo(OS, PI); uint8_t flags = 0; + if (PI.BoundsSafety) + flags |= 0x01; + flags <<= 2; if (auto noescape = PI.isNoEscape()) { flags |= 0x01; if (*noescape) @@ -1100,6 +1133,10 @@ void emitParamInfo(raw_ostream &OS, const ParamInfo &PI) { llvm::support::endian::Writer writer(OS, llvm::endianness::little); writer.write(flags); + /* TO_UPSTREAM(BoundsSafety) ON */ + if (auto BSI = PI.BoundsSafety) + emitBoundsSafetyInfo(OS, *PI.BoundsSafety); + /* TO_UPSTREAM(BoundsSafety) OFF */ } /// Retrieve the serialized size of the given FunctionInfo, for use in on-disk diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index d623b9042ee1a..86939431adcd5 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -150,6 +150,33 @@ enum class APIAvailability { }; } // namespace +/* TO_UPSTREAM(BoundsSafety) ON */ +namespace { +struct BoundsSafety { + BoundsSafetyInfo::BoundsSafetyKind Kind; + unsigned Level = 0; + StringRef BoundsExpr = ""; +}; +} // namespace + +namespace llvm { +namespace yaml { +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, BoundsSafetyInfo::BoundsSafetyKind &AA) { + IO.enumCase(AA, "counted_by", + BoundsSafetyInfo::BoundsSafetyKind::CountedBy); + IO.enumCase(AA, "counted_by_or_null", + BoundsSafetyInfo::BoundsSafetyKind::CountedByOrNull); + IO.enumCase(AA, "sized_by", BoundsSafetyInfo::BoundsSafetyKind::SizedBy); + IO.enumCase(AA, "sized_by_or_null", + BoundsSafetyInfo::BoundsSafetyKind::SizedByOrNull); + IO.enumCase(AA, "ended_by", BoundsSafetyInfo::BoundsSafetyKind::EndedBy); + } +}; +} // namespace yaml +} // namespace llvm +/* TO_UPSTREAM(BoundsSafety) OFF */ + namespace llvm { namespace yaml { template <> struct ScalarEnumerationTraits { @@ -187,6 +214,9 @@ struct Param { std::optional Lifetimebound = false; std::optional Nullability; std::optional RetainCountConvention; + /* TO_UPSTREAM(BoundsSafety) ON */ + std::optional BoundsSafety; + /* TO_UPSTREAM(BoundsSafety) OFF */ StringRef Type; }; @@ -238,8 +268,21 @@ template <> struct MappingTraits { IO.mapOptional("NoEscape", P.NoEscape); IO.mapOptional("Lifetimebound", P.Lifetimebound); IO.mapOptional("Type", P.Type, StringRef("")); + /* TO_UPSTREAM(BoundsSafety) ON */ + IO.mapOptional("BoundsSafety", P.BoundsSafety); + /* TO_UPSTREAM(BoundsSafety) OFF */ } }; + +/* TO_UPSTREAM(BoundsSafety) ON */ +template <> struct MappingTraits { + static void mapping(IO &IO, BoundsSafety &BS) { + IO.mapRequired("Kind", BS.Kind); + IO.mapRequired("BoundedBy", BS.BoundsExpr); + IO.mapOptional("Level", BS.Level, 0); + } +}; +/* TO_UPSTREAM(BoundsSafety) OFF */ } // namespace yaml } // namespace llvm @@ -862,6 +905,15 @@ class YAMLConverter { PI.setLifetimebound(P.Lifetimebound); PI.setType(std::string(P.Type)); PI.setRetainCountConvention(P.RetainCountConvention); + BoundsSafetyInfo BSI; + /* TO_UPSTREAM(BoundsSafety) ON */ + if (P.BoundsSafety) { + BSI.setKindAudited(P.BoundsSafety->Kind); + BSI.setLevelAudited(P.BoundsSafety->Level); + BSI.ExternalBounds = P.BoundsSafety->BoundsExpr.str(); + } + PI.BoundsSafety = BSI; + /* TO_UPSTREAM(BoundsSafety) OFF */ if (static_cast(OutInfo.Params.size()) <= P.Position) OutInfo.Params.resize(P.Position + 1); if (P.Position == -1) diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 7c3229e0f7457..8c51ca6561cf8 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -8941,6 +8941,90 @@ TypeResult Parser::ParseTypeFromString(StringRef TypeStr, StringRef Context, return Result; } +/* TO_UPSTREAM(BoundsSafety) ON */ +ExprResult +Parser::ParseBoundsAttributeArgFromString(StringRef ExprStr, StringRef Context, + Decl *ParentDecl, + SourceLocation IncludeLoc) { + // Consume (unexpanded) tokens up to the end-of-directive. + SmallVector Tokens; + { + // Create a new buffer from which we will parse the type. + auto &SourceMgr = PP.getSourceManager(); + FileID FID = SourceMgr.createFileID( + llvm::MemoryBuffer::getMemBufferCopy(ExprStr, Context), SrcMgr::C_User, + 0, 0, IncludeLoc); + + // Form a new lexer that references the buffer. + Lexer L(FID, SourceMgr.getBufferOrFake(FID), PP); + L.setParsingPreprocessorDirective(true); + L.setIsPragmaLexer(true); + + // Lex the tokens from that buffer. + Token Tok; + do { + L.Lex(Tok); + Tokens.push_back(Tok); + } while (Tok.isNot(tok::eod)); + } + + // Replace the "eod" token with an "eof" token identifying the end of + // the provided string. + Token &EndToken = Tokens.back(); + EndToken.startToken(); + EndToken.setKind(tok::eof); + EndToken.setLocation(Tok.getLocation()); + EndToken.setEofData(ExprStr.data()); + + // Add the current token back. + Tokens.push_back(Tok); + + // Enter the tokens into the token stream. + PP.EnterTokenStream(Tokens, /*DisableMacroExpansion=*/false, + /*IsReinject=*/false); + + // Consume the current token so that we'll start parsing the tokens we + // added to the stream. + ConsumeAnyToken(); + + // Enter a new scope. + std::unique_ptr LocalScope; + bool EnterScope = ParentDecl && ParentDecl->isFunctionOrFunctionTemplate(); + if (EnterScope) { + LocalScope.reset(new ParseScope(this, Scope::FnScope | Scope::DeclScope)); + Actions.ActOnReenterFunctionContext(Actions.CurScope, ParentDecl); + } + + // Parse the expr. + using ExpressionKind = + Sema::ExpressionEvaluationContextRecord::ExpressionKind; + EnterExpressionEvaluationContext EC( + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, nullptr, + ExpressionKind::EK_AttrArgument); + + ExprResult Result( + Actions.CorrectDelayedTyposInExpr(ParseAssignmentExpression())); + + // Check if we parsed the whole thing. + if (Result.isUsable() && + (Tok.isNot(tok::eof) || Tok.getEofData() != ExprStr.data())) { + Diag(Tok.getLocation(), diag::err_type_unparsed); + } + + // There could be leftover tokens (e.g. because of an error). + // Skip through until we reach the 'end of directive' token. + while (Tok.isNot(tok::eof)) + ConsumeAnyToken(); + + // Consume the end token. + if (Tok.is(tok::eof) && Tok.getEofData() == ExprStr.data()) + ConsumeAnyToken(); + if (EnterScope) + Actions.ActOnExitFunctionContext(); + return Result; +} +/* TO_UPSTREAM(BoundsSafety) OFF */ + void Parser::DiagnoseBitIntUse(const Token &Tok) { // If the token is for _ExtInt, diagnose it as being deprecated. Otherwise, // the token is about _BitInt and gets (potentially) diagnosed as use of an diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 0ce519bbe7c58..31e6dd3361cc1 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -76,6 +76,14 @@ Parser::Parser(Preprocessor &pp, Sema &actions, bool skipFunctionBodies) [this](StringRef TypeStr, StringRef Context, SourceLocation IncludeLoc) { return this->ParseTypeFromString(TypeStr, Context, IncludeLoc); }; + /* TO_UPSTREAM(BoundsSafety) ON */ + Actions.ParseBoundsAttributeArgFromStringCallback = + [this](StringRef ExprStr, StringRef Context, Decl *Parent, + SourceLocation IncludeLoc) { + return this->ParseBoundsAttributeArgFromString(ExprStr, Context, Parent, + IncludeLoc); + }; + /* TO_UPSTREAM(BoundsSafety) OFF */ } DiagnosticBuilder Parser::Diag(SourceLocation Loc, unsigned DiagID) { @@ -468,8 +476,9 @@ Parser::ParseScopeFlags::~ParseScopeFlags() { //===----------------------------------------------------------------------===// Parser::~Parser() { - // Clear out the parse-type-from-string callback. + // Clear out the parse-*-from-string callbacks. Actions.ParseTypeFromStringCallback = nullptr; + Actions.ParseBoundsAttributeArgFromStringCallback = nullptr; // If we still have scopes active, delete the scope tree. delete getCurScope(); diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 9f09e55d0a579..913baed40a2c1 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -409,6 +409,56 @@ static void ProcessAPINotes(Sema &S, Decl *D, Metadata); } +/* TO_UPSTREAM(BoundsSafety) ON */ +static void applyBoundsSafety(Sema &S, ValueDecl *D, + const api_notes::BoundsSafetyInfo &Info, + VersionedInfoMetadata Metadata) { + using BoundsSafetyKind = api_notes::BoundsSafetyInfo::BoundsSafetyKind; + if (Metadata.IsActive && !Info.ExternalBounds.empty() && + S.ParseBoundsAttributeArgFromStringCallback) { + Decl *ScopeDecl = D; + if (auto ParmDecl = dyn_cast(ScopeDecl)) { + ScopeDecl = dyn_cast(ParmDecl->getDeclContext()); + } + auto ParsedExpr = S.ParseBoundsAttributeArgFromStringCallback( + Info.ExternalBounds, "", ScopeDecl, D->getLocation()); + if (ParsedExpr.isInvalid()) + return; + + std::string AttrName; + AttributeCommonInfo::Kind Kind; + assert(*Info.getKind() >= BoundsSafetyKind::CountedBy); + assert(*Info.getKind() <= BoundsSafetyKind::EndedBy); + switch (*Info.getKind()) { + case BoundsSafetyKind::CountedBy: + AttrName = "__counted_by"; + Kind = AttributeCommonInfo::AT_CountedBy; + break; + case BoundsSafetyKind::CountedByOrNull: + AttrName = "__counted_by_or_null"; + Kind = AttributeCommonInfo::AT_CountedByOrNull; + break; + case BoundsSafetyKind::SizedBy: + AttrName = "__sized_by"; + Kind = AttributeCommonInfo::AT_SizedBy; + break; + case BoundsSafetyKind::SizedByOrNull: + AttrName = "__sized_by_or_null"; + Kind = AttributeCommonInfo::AT_SizedByOrNull; + break; + case BoundsSafetyKind::EndedBy: + AttrName = "__ended_by"; + Kind = AttributeCommonInfo::AT_PtrEndedBy; + break; + } + + S.applyPtrCountedByEndedByAttr( + D, *Info.getLevel(), Kind, ParsedExpr.get(), D->getLocation(), + D->getSourceRange(), AttrName, /* originates in API notes */ true); + } +} +/* TO_UPSTREAM(BoundsSafety) OFF */ + /// Process API notes for a parameter. static void ProcessAPINotes(Sema &S, ParmVarDecl *D, const api_notes::ParamInfo &Info, @@ -426,6 +476,11 @@ static void ProcessAPINotes(Sema &S, ParmVarDecl *D, LifetimeBoundAttr(S.Context, getPlaceholderAttrInfo()); }); + /* TO_UPSTREAM(BoundsSafety) ON */ + if (Info.BoundsSafety.has_value()) + applyBoundsSafety(S, D, *Info.BoundsSafety, Metadata); + /* TO_UPSTREAM(BoundsSafety) OFF */ + // Retain count convention handleAPINotedRetainCountConvention(S, D, Metadata, Info.getRetainCountConvention()); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 7495ef780aa17..9b843dee89fa2 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -5382,20 +5382,23 @@ class ConstructDynamicBoundType protected: Sema &S; - const ParsedAttr &AL; + const StringRef DiagName; Expr *ArgExpr; + SourceLocation Loc; const BoundsAttributedType *ConstructedType = nullptr; unsigned Level; bool ScopeCheck; + bool AllowRedecl; bool AutoPtrAttributed = false; - bool AllowCountRedecl = false; bool AtomicErrorEmitted = false; public: explicit ConstructDynamicBoundType(Sema &S, unsigned Level, - const ParsedAttr &AL, bool ScopeCheck) - : S(S), AL(AL), ArgExpr(AL.getArgAsExpr(0)), Level(Level), - ScopeCheck(ScopeCheck) {} + const StringRef DiagName, Expr *ArgExpr, + SourceLocation Loc, bool ScopeCheck, + bool AllowRedecl) + : S(S), DiagName(DiagName), ArgExpr(ArgExpr), Loc(Loc), Level(Level), + ScopeCheck(ScopeCheck), AllowRedecl(AllowRedecl) {} QualType Visit(QualType T) { SplitQualType SQT = T.split(); @@ -5413,7 +5416,7 @@ class ConstructDynamicBoundType QualType VisitType(const Type *T) { if (const auto *PTy = T->getAs()) return VisitPointerType(PTy); - S.Diag(S.getAttrLoc(AL), diag::err_attribute_pointers_only) << AL << 0; + S.Diag(Loc, diag::err_attribute_pointers_only) << DiagName << 0; return QualType(); } @@ -5424,7 +5427,6 @@ class ConstructDynamicBoundType QualType VisitFunctionProtoType(const FunctionProtoType *FPT) { // The attribute applies to the return type. - SaveAndRestore AllowCountRedeclLocal(AllowCountRedecl, true); QualType QT = Visit(FPT->getReturnType()); if (QT.isNull()) return QualType(); @@ -5436,7 +5438,6 @@ class ConstructDynamicBoundType QualType VisitFunctionNoProtoType(const FunctionNoProtoType *FPT) { // The attribute applies to the return type. - SaveAndRestore AllowCountRedeclLocal(AllowCountRedecl, true); QualType QT = Visit(FPT->getReturnType()); if (QT.isNull()) return QualType(); @@ -5450,9 +5451,8 @@ class ConstructDynamicBoundType BoundsSafetyPointerAttributes FAttr = T->getPointerAttributes(); if (FAttr.hasUpperBound() && !AutoPtrAttributed) { - S.Diag(S.getAttrLoc(AL), - diag::err_bounds_safety_conflicting_count_bound_attributes) - << AL << (FAttr.hasLowerBound() ? 0 : 1); + S.Diag(Loc, diag::err_bounds_safety_conflicting_count_bound_attributes) + << DiagName << (FAttr.hasLowerBound() ? 0 : 1); return QualType(); } @@ -5547,7 +5547,7 @@ class ConstructDynamicBoundType QualType VisitValueTerminatedType(const ValueTerminatedType *T) { if (Level == 0 && !AutoPtrAttributed) { - S.Diag(AL.getLoc(), diag::err_bounds_safety_terminated_by_wrong_pointer_type); + S.Diag(Loc, diag::err_bounds_safety_terminated_by_wrong_pointer_type); return QualType(); } QualType NewTy = Visit(T->desugar()); @@ -5566,12 +5566,16 @@ class ConstructCountAttributedType : public: explicit ConstructCountAttributedType(Sema &S, unsigned Level, - const ParsedAttr &AL, bool CountInBytes, - bool OrNull, bool ScopeCheck = false) - : ConstructDynamicBoundType(S, Level, AL, ScopeCheck), + const StringRef DiagName, Expr *ArgE, + SourceLocation Loc, bool CountInBytes, + bool OrNull, bool AllowRedecl, + bool ScopeCheck = false) + : ConstructDynamicBoundType(S, Level, DiagName, ArgE, Loc, ScopeCheck, + AllowRedecl), CountInBytes(CountInBytes), OrNull(OrNull) { if (!ArgExpr->getType()->isIntegralOrEnumerationType()) { - S.Diag(AL.getLoc(), diag::err_attribute_argument_type_for_bounds_safety_count) << AL; + S.Diag(Loc, diag::err_attribute_argument_type_for_bounds_safety_count) + << DiagName; // Recover by using the integer literal 0 as the count. If we get to // DefaultLvalueConversion and the count is itself a __counted_by value, // clang will go down a fiery stack overflow. @@ -5602,26 +5606,26 @@ class ConstructCountAttributedType : // directly (i.e. without the macro) because it is not expected that // users will use it. int SuggestFixIt = 0; // Default don't suggest __sized_by - if (S.getAttrLoc(AL).isMacroID()) { + if (Loc.isMacroID()) { // FIXME(dliew): Use `AL.MacroII` to get the name. Unfortunately // `AL.MacroII` is not set so we can't simply check the macro name is // what we expect. So instead we have the lexer tell us the contents // of the token and check against that. // rdar://100631458 - auto MacroName = Lexer::getImmediateMacroName( - S.getAttrLoc(AL), S.SourceMgr, S.LangOpts); + auto MacroName = + Lexer::getImmediateMacroName(Loc, S.SourceMgr, S.LangOpts); if (MacroName == "__counted_by") { SuggestFixIt = 1; // Emit text to suggest __sized_by - auto MacroLoc = S.SourceMgr.getExpansionLoc(S.getAttrLoc(AL)); + auto MacroLoc = S.SourceMgr.getExpansionLoc(Loc); PD << FixItHint::CreateReplacement(MacroLoc, "__sized_by"); } else if (MacroName == "__counted_by_or_null") { SuggestFixIt = 1; // Emit text to suggest __sized_by_or_null - auto MacroLoc = S.SourceMgr.getExpansionLoc(S.getAttrLoc(AL)); + auto MacroLoc = S.SourceMgr.getExpansionLoc(Loc); PD << FixItHint::CreateReplacement(MacroLoc, "__sized_by_or_null"); } } PD << SuggestFixIt; - S.Diag(S.getAttrLoc(AL), PD); + S.Diag(Loc, PD); // Recover by assuming a byte count. CountInBytes = true; @@ -5635,7 +5639,7 @@ class ConstructCountAttributedType : } QualType DiagnoseConflictingType(const CountAttributedType *T) { - if (AllowCountRedecl) { + if (AllowRedecl) { QualType NewTy = BuildDynamicBoundType(T->desugar()); const auto *NewDCPTy = NewTy->getAs(); // We don't have a way to distinguish if '__counted_by' is conflicting or has been @@ -5653,14 +5657,23 @@ class ConstructCountAttributedType : return NewTy; } - S.Diag(S.getAttrLoc(AL), diag::err_bounds_safety_conflicting_pointer_attributes) + S.Diag(Loc, diag::err_bounds_safety_conflicting_pointer_attributes) << /* pointer */ T->isPointerType() << /* count */ 2; return QualType(); } + QualType VisitFunctionProtoType(const FunctionProtoType *FPT) { + SaveAndRestore AllowRedeclLocal(AllowRedecl, true); + return ConstructDynamicBoundType::VisitFunctionProtoType(FPT); + } + + QualType VisitFunctionNoProtoType(const FunctionNoProtoType *FPT) { + SaveAndRestore AllowRedeclLocal(AllowRedecl, true); + return ConstructDynamicBoundType::VisitFunctionNoProtoType(FPT); + } + QualType DiagnoseConflictingType(const DynamicRangePointerType *T) { - S.Diag(S.getAttrLoc(AL), - diag::err_bounds_safety_conflicting_count_range_attributes); + S.Diag(Loc, diag::err_bounds_safety_conflicting_count_range_attributes); return QualType(); } @@ -5669,7 +5682,7 @@ class ConstructCountAttributedType : if (T->hasAttr(attr::ArrayDecayDiscardsCountInParameters)) { return Visit(S.Context.getArrayDecayedType(QualType(T, 0))); } else { - S.Diag(AL.getLoc(), diag::err_bounds_safety_complete_array_with_count); + S.Diag(Loc, diag::err_bounds_safety_complete_array_with_count); return QualType(); } } @@ -5685,7 +5698,7 @@ class ConstructCountAttributedType : if (T->getPointeeType() != NewElementTy) { // Count attributes on the element of an array type are not supported yet - S.Diag(AL.getLoc(), + S.Diag(Loc, diag::err_multiple_coupled_decls_in_bounds_safety_dynamic_count); return QualType(); } @@ -5696,7 +5709,7 @@ class ConstructCountAttributedType : QualType VisitIncompleteArrayType(const IncompleteArrayType *T) { if (Level == 0) { if (CountInBytes) { - S.Diag(AL.getLoc(), diag::err_bounds_safety_sized_by_array) << AL; + S.Diag(Loc, diag::err_bounds_safety_sized_by_array) << DiagName; return QualType(); } @@ -5712,8 +5725,7 @@ class ConstructCountAttributedType : unsigned DiagIndex = CountInBytes ? 3 : 2; if (OrNull) DiagIndex += 2; - S.Diag(S.getAttrLoc(AL), - diag::err_bounds_safety_atomic_unsupported_attribute) + S.Diag(Loc, diag::err_bounds_safety_atomic_unsupported_attribute) << DiagIndex; AtomicErrorEmitted = true; } @@ -5833,9 +5845,11 @@ class ConstructDynamicRangePointerType : public: explicit ConstructDynamicRangePointerType( - Sema &S, unsigned Level, const ParsedAttr &AL, bool ScopeCheck = false, + Sema &S, unsigned Level, const StringRef DiagName, Expr *ArgExpr, + SourceLocation Loc, bool AllowRedecl, bool ScopeCheck = false, std::optional StartPtrInfo = std::nullopt) - : ConstructDynamicBoundType(S, Level, AL, ScopeCheck), + : ConstructDynamicBoundType(S, Level, DiagName, ArgExpr, Loc, ScopeCheck, + AllowRedecl), StartPtrInfo(StartPtrInfo) { assert(ArgExpr->getType()->isPointerType()); } @@ -5875,11 +5889,11 @@ class ConstructDynamicRangePointerType : } QualType VisitDynamicRangePointerType(const DynamicRangePointerType *T) { - if (Level == 0 && T->getEndPointer() == nullptr) { - // T is a started_by() pointer type. + if (Level == 0 && (AllowRedecl || T->getEndPointer() == nullptr)) { + // T could be a started_by() pointer type. Expr *StartPtr = T->getStartPointer(); auto StartPtrDecls = T->getStartPtrDecls(); - assert(StartPtr); + assert(StartPtr || AllowRedecl); assert(ConstructedType == nullptr); // Construct an ended_by() pointer type. @@ -5892,6 +5906,20 @@ class ConstructDynamicRangePointerType : Expr *EndPtr = DRPT->getEndPointer(); auto EndPtrDecls = DRPT->getEndPtrDecls(); assert(EndPtr); + if (auto OldEndPtr = T->getEndPointer()) { + assert(AllowRedecl); + llvm::FoldingSetNodeID NewID; + llvm::FoldingSetNodeID OldID; + EndPtr->Profile(NewID, S.Context, /*Canonical*/ true); + OldEndPtr->Profile(OldID, S.Context, /*Canonical*/ true); + + if (NewID != OldID) { + S.Diag(Loc, diag::err_bounds_safety_conflicting_pointer_attributes) + << /* pointer */ 1 << /* end */ 3; + ConstructedType = nullptr; + return QualType(); + } + } // ConstructType was already set while visiting the nested PointerType. // Reconstruct DRPT by merging started_by and ended_by. @@ -5905,13 +5933,12 @@ class ConstructDynamicRangePointerType : } QualType DiagnoseConflictingType(const CountAttributedType *T) { - S.Diag(S.getAttrLoc(AL), - diag::err_bounds_safety_conflicting_count_range_attributes); + S.Diag(Loc, diag::err_bounds_safety_conflicting_count_range_attributes); return QualType(); } QualType DiagnoseConflictingType(const DynamicRangePointerType *T) { - S.Diag(S.getAttrLoc(AL), diag::err_bounds_safety_conflicting_pointer_attributes) + S.Diag(Loc, diag::err_bounds_safety_conflicting_pointer_attributes) << /* pointer */ 1 << /* end */ 3; return QualType(); } @@ -5920,8 +5947,7 @@ class ConstructDynamicRangePointerType : QualType ValTy = T->getValueType(); if (ValTy->isPointerType()) { if (!AtomicErrorEmitted) { - S.Diag(S.getAttrLoc(AL), - diag::err_bounds_safety_atomic_unsupported_attribute) + S.Diag(Loc, diag::err_bounds_safety_atomic_unsupported_attribute) << /*ended_by*/ 6; AtomicErrorEmitted = true; } @@ -6357,11 +6383,11 @@ diagnoseRangeDependentDecls(Sema &S, const ValueDecl *TheDepender, return HadError; } -static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, - const ParsedAttr &AL) { - unsigned Level; - if (!S.checkUInt32Argument(AL, AL.getArgAsExpr(1), Level)) - return; +void Sema::applyPtrCountedByEndedByAttr(Decl *D, unsigned Level, + AttributeCommonInfo::Kind Kind, + Expr *AttrArg, SourceLocation Loc, + SourceRange Range, StringRef DiagName, + bool OriginatesInAPINotes) { // If the decl is invalid, the indirection Level might not exist in the type, // since the type may have not been constructed correctly. Example: // 'int (*param)[__counted_by_or_null(10)][]' @@ -6391,14 +6417,14 @@ static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, // Don't allow typedefs with __counted_by on non-function types. if (TND && (!DeclTy->isFunctionType() && !IsFPtr)) { - S.Diag(AL.getLoc(), diag::err_bounds_safety_typedef_dynamic_bound) << AL; + Diag(Loc, diag::err_bounds_safety_typedef_dynamic_bound) << DiagName; return; } bool CountInBytes = false; bool IsEndedBy = false; bool OrNull = false; - switch (AL.getKind()) { + switch (Kind) { case ParsedAttr::AT_SizedBy: CountInBytes = true; break; @@ -6431,45 +6457,42 @@ static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, // checked in attrNonNullArgCheck & handleNonNullAttr. if (auto NNAttr = D->getAttr(); NNAttr && isa(D)) { - S.Diag(AL.getLoc(), - diag::err_bounds_safety_nullable_dynamic_count_nonnullable) - << CountInBytes << NNAttr << AL.getRange() << NNAttr->getRange(); + Diag(Loc, diag::err_bounds_safety_nullable_dynamic_count_nonnullable) + << CountInBytes << NNAttr << Range << NNAttr->getRange(); return; } if (auto RNNAttr = D->getAttr()) { - S.Diag(AL.getLoc(), - diag::err_bounds_safety_nullable_dynamic_count_nonnullable) - << CountInBytes << RNNAttr << AL.getRange() << RNNAttr->getRange(); + Diag(Loc, diag::err_bounds_safety_nullable_dynamic_count_nonnullable) + << CountInBytes << RNNAttr << Range << RNNAttr->getRange(); return; } if (AttrNullability == NullabilityKind::NonNull) { - S.Diag(AL.getLoc(), - diag::warn_bounds_safety_nullable_dynamic_count_nonnullable) - << CountInBytes << AL; + Diag(Loc, diag::warn_bounds_safety_nullable_dynamic_count_nonnullable) + << CountInBytes << DiagName; } } - if (auto CountArg = AL.getArgAsExpr(0)->getIntegerConstantExpr(S.Context)) { + if (auto CountArg = AttrArg->getIntegerConstantExpr(Context)) { if (CountArg > 0 && !OrNull && AttrNullability == NullabilityKind::Nullable) - S.Diag(AL.getArgAsExpr(0)->getExprLoc(), - diag::warn_bounds_safety_nonnullable_dynamic_count_nullable) - << CountInBytes << AL.getRange(); + Diag(AttrArg->getExprLoc(), + diag::warn_bounds_safety_nonnullable_dynamic_count_nullable) + << CountInBytes << Range; } } if (VD) { const auto *FD = dyn_cast(VD); if (FD && FD->getParent()->isUnion()) { - S.Diag(AL.getLoc(), diag::err_invalid_decl_kind_bounds_safety_union_count) - << AL; + Diag(Loc, diag::err_invalid_decl_kind_bounds_safety_union_count) + << DiagName; return; } if (EffectiveLevel != 0 && (!isa(VD) || DeclTy->isBoundsAttributedType())) { - S.Diag(AL.getLoc(), diag::err_bounds_safety_nested_dynamic_bound) << AL; + Diag(Loc, diag::err_bounds_safety_nested_dynamic_bound) << DiagName; return; } @@ -6483,14 +6506,14 @@ static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, QualType TSITy = PVD->getTypeSourceInfo()->getType(); if (IsEndedBy) { if (Level == 0 && TSITy->isArrayType()) { - S.Diag(AL.getLoc(), diag::err_attribute_pointers_only) << AL << 0; + Diag(Loc, diag::err_attribute_pointers_only) << DiagName << 0; return; } } else { - const auto *ATy = S.Context.getAsArrayType(TSITy); + const auto *ATy = Context.getAsArrayType(TSITy); if (Level == 0 && ATy && !ATy->isIncompleteArrayType() && !TSITy->hasAttr(attr::ArrayDecayDiscardsCountInParameters)) { - S.Diag(AL.getLoc(), diag::err_bounds_safety_complete_array_with_count); + Diag(Loc, diag::err_bounds_safety_complete_array_with_count); return; } } @@ -6498,18 +6521,17 @@ static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, if (Ty->isArrayType() && OrNull && (FD || EffectiveLevel > 0 || (Var && Var->hasExternalStorage()))) { - auto ErrDiag = S.Diag(AL.getLoc(), diag::err_bounds_safety_nullable_fam); + auto ErrDiag = Diag(Loc, diag::err_bounds_safety_nullable_fam); // Pointers to dynamic count types are only allowed for parameters, so any // FieldDecl containing a dynamic count type is a FAM. I.e. a struct field // with type 'int(*)[__counted_by(...)]' is not valid. - ErrDiag << CountInBytes << /*is FAM?*/ !!FD << AL; + ErrDiag << CountInBytes << /*is FAM?*/ !!FD << DiagName; assert(!FD || EffectiveLevel == 0); - SourceLocation FixItLoc = - S.getSourceManager().getExpansionLoc(AL.getLoc()); + SourceLocation FixItLoc = getSourceManager().getExpansionLoc(Loc); SourceLocation EndLoc = Lexer::getLocForEndOfToken(FixItLoc, /* Don't include '(' */ -1, - S.getSourceManager(), S.getLangOpts()); + getSourceManager(), getLangOpts()); std::string Attribute = CountInBytes ? "__sized_by" : "__counted_by"; ErrDiag << FixItHint::CreateReplacement({FixItLoc, EndLoc}, Attribute); @@ -6518,9 +6540,10 @@ static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, if (Ty->isArrayType() && EffectiveLevel > 0) { auto ErrDiag = - S.Diag( - AL.getLoc(), - diag::err_bounds_safety_unsupported_address_of_incomplete_array_type) + Diag( + Loc, + diag:: + err_bounds_safety_unsupported_address_of_incomplete_array_type) << Ty; // apply attribute anyways to avoid too misleading follow-up diagnostics } @@ -6532,15 +6555,14 @@ static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, bool HadAtomicError = false; if (IsEndedBy) { - const Expr *ArgExpr = AL.getArgAsExpr(0); - if (ArgExpr && ArgExpr->getType()->isAtomicType()) { - S.Diag(AL.getLoc(), diag::err_bounds_safety_atomic_unsupported_attribute) + if (AttrArg && AttrArg->getType()->isAtomicType()) { + Diag(Loc, diag::err_bounds_safety_atomic_unsupported_attribute) << /*started_by*/ 8; return; } - if (!ArgExpr || !ArgExpr->getType()->isPointerType()) { - S.Diag(AL.getLoc(), diag::err_attribute_argument_type_for_bounds_safety_range) - << AL; + if (!AttrArg || !AttrArg->getType()->isPointerType()) { + Diag(Loc, diag::err_attribute_argument_type_for_bounds_safety_range) + << DiagName; return; } @@ -6553,13 +6575,15 @@ static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, } auto TypeConstructor = ConstructDynamicRangePointerType( - S, Level, AL, ScopeCheck, StartPtrInfo); + *this, Level, DiagName, AttrArg, Loc, OriginatesInAPINotes, ScopeCheck, + StartPtrInfo); NewDeclTy = TypeConstructor.Visit(DeclTy); HadAtomicError = TypeConstructor.hadAtomicError(); ConstructedType = TypeConstructor.getConstructedType(); } else { auto TypeConstructor = ConstructCountAttributedType( - S, Level, AL, CountInBytes, OrNull, ScopeCheck); + *this, Level, DiagName, AttrArg, Loc, CountInBytes, OrNull, + OriginatesInAPINotes, ScopeCheck); NewDeclTy = TypeConstructor.Visit(DeclTy); HadAtomicError = TypeConstructor.hadAtomicError(); ConstructedType = TypeConstructor.getConstructedType(); @@ -6568,25 +6592,25 @@ static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, if (NewDeclTy.isNull()) return; - // `NewDeclTy` is the new type of the declaration `D`, whereas `ConstructedType` is exact - // `BoundsAttributedType` that's created for this attribute. The bounds - // attribute can be added to a nested pointer or some nested type, so - // `ConstructedType` can be different from `NewDeclTy`. + // `NewDeclTy` is the new type of the declaration `D`, whereas + // `ConstructedType` is exact `BoundsAttributedType` that's created for this + // attribute. The bounds attribute can be added to a nested pointer or some + // nested type, so `ConstructedType` can be different from `NewDeclTy`. assert(ConstructedType); auto LifetimeCheck = IsFPtr ? Sema::LifetimeCheckKind::None : Sema::getLifetimeCheckKind(Var); - if (diagnoseBoundsAttrLifetimeAndScope(S, ConstructedType, ScopeCheck, + if (diagnoseBoundsAttrLifetimeAndScope(*this, ConstructedType, ScopeCheck, LifetimeCheck)) return; if (VD && !isa(VD) && !HadAtomicError) { if (const auto *BDTy = dyn_cast(ConstructedType)) { - if (!diagnoseCountDependentDecls(S, VD, BDTy, EffectiveLevel, IsFPtr)) - S.AttachDependerDeclsAttr(VD, BDTy, EffectiveLevel); + if (!diagnoseCountDependentDecls(*this, VD, BDTy, EffectiveLevel, IsFPtr)) + AttachDependerDeclsAttr(VD, BDTy, EffectiveLevel); } else if (const auto *BDTy = dyn_cast(ConstructedType)) { - diagnoseRangeDependentDecls(S, VD, BDTy, EffectiveLevel, IsFPtr); + diagnoseRangeDependentDecls(*this, VD, BDTy, EffectiveLevel, IsFPtr); } else { llvm_unreachable("Unexpected bounds attributed type"); } @@ -6596,21 +6620,34 @@ static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, // We need to create a TypeSourceInfo, as TypedefNameDecl is not a ValueDecl // and thus doesn't have setType() method. SourceLocation Loc = TND->getTypeSourceInfo()->getTypeLoc().getBeginLoc(); - TypeSourceInfo *Info = S.Context.getTrivialTypeSourceInfo(NewDeclTy, Loc); + TypeSourceInfo *Info = Context.getTrivialTypeSourceInfo(NewDeclTy, Loc); TND->setTypeSourceInfo(Info); } else { VD->setType(NewDeclTy); // Reconstruct implicit cast for initializer after variable type change. if (Var && Var->hasInit()) { Expr *Init = Var->getInit(); - ExprResult Res = removeUnusedOVEs(S, Init, Init->IgnoreImpCasts()); + ExprResult Res = removeUnusedOVEs(*this, Init, Init->IgnoreImpCasts()); if (Res.isInvalid()) return; - S.AddInitializerToDecl(Var, Res.get(), Var->isDirectInit()); + AddInitializerToDecl(Var, Res.get(), Var->isDirectInit()); } } } +static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D, + const ParsedAttr &AL) { + unsigned Level; + if (!S.checkUInt32Argument(AL, AL.getArgAsExpr(1), Level)) + return; + AttributeCommonInfo::Kind Kind = AL.getKind(); + const IdentifierInfo *AttrName = + AL.printAsMacro() ? AL.getMacroIdentifier() : AL.getAttrName(); + S.applyPtrCountedByEndedByAttr(D, Level, Kind, AL.getArgAsExpr(0), + AL.getLoc(), AL.getRange(), + ("\'" + AttrName->getName() + "\'").str()); +} + static void handleUnsafeLateConst(Sema &S, Decl *D, const ParsedAttr &AL) { D->addAttr(::new (S.Context) UnsafeLateConstAttr(S.Context, AL)); diff --git a/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.apinotes b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.apinotes new file mode 100644 index 0000000000000..5a3d628b66b6c --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.apinotes @@ -0,0 +1,135 @@ +--- +Name: BoundsUnsafe +Functions: + - Name: asdf_counted + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: len + - Name: asdf_sized + Parameters: + - Position: 0 + BoundsSafety: + Kind: sized_by + Level: 0 + BoundedBy: size + - Name: asdf_counted_n + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by_or_null + Level: 0 + BoundedBy: len + - Name: asdf_sized_n + Parameters: + - Position: 0 + BoundsSafety: + Kind: sized_by_or_null + Level: 0 + BoundedBy: size + - Name: asdf_ended + Parameters: + - Position: 0 + BoundsSafety: + Kind: ended_by + Level: 0 + BoundedBy: end + - Name: asdf_sized_mul + Parameters: + - Position: 0 + BoundsSafety: + Kind: sized_by + Level: 0 + BoundedBy: "size * count" + - Name: asdf_counted_out + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 1 + BoundedBy: "*len" + - Name: asdf_counted_const + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: 7 + - Name: asdf_counted_nullable + Parameters: + - Position: 1 + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: len + - Name: asdf_counted_noescape + Parameters: + - Position: 0 + NoEscape: true + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: len + - Name: asdf_counted_default_level + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + BoundedBy: len + - Name: asdf_counted_redundant + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + BoundedBy: len + - Name: asdf_ended_chained + Parameters: + - Position: 0 + BoundsSafety: + Kind: ended_by + Level: 0 + BoundedBy: mid + - Position: 1 + BoundsSafety: + Kind: ended_by + Level: 0 + BoundedBy: end + - Name: asdf_ended_chained_reverse + Parameters: + - Position: 1 + BoundsSafety: + Kind: ended_by + Level: 0 + BoundedBy: end + - Position: 0 + BoundsSafety: + Kind: ended_by + Level: 0 + BoundedBy: mid + - Name: asdf_ended_already_started + Parameters: + - Position: 1 + BoundsSafety: + Kind: ended_by + Level: 0 + BoundedBy: end + - Name: asdf_ended_already_ended + Parameters: + - Position: 0 + BoundsSafety: + Kind: ended_by + Level: 0 + BoundedBy: mid + - Name: asdf_ended_redundant + Parameters: + - Position: 0 + BoundsSafety: + Kind: ended_by + Level: 0 + BoundedBy: end + - Name: asdf_nterm + Parameters: + - Position: 0 + Type: "int * __attribute__((__terminated_by__(0)))" diff --git a/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h new file mode 100644 index 0000000000000..82a1ca05475d9 --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h @@ -0,0 +1,21 @@ +void asdf_counted(int * buf, int len); +void asdf_sized(int * buf, int size); +void asdf_counted_n(int * buf, int len); +void asdf_sized_n(int * buf, int size); +void asdf_ended(int * buf, int * end); + +void asdf_sized_mul(int * buf, int size, int count); +void asdf_counted_out(int ** buf, int * len); +void asdf_counted_const(int * buf); +void asdf_counted_nullable(int len, int * _Nullable buf); +void asdf_counted_noescape(int * buf, int len); +void asdf_counted_default_level(int * buf, int len); +void asdf_counted_redundant(int * __attribute__((__counted_by__(len))) buf, int len); + +void asdf_ended_chained(int * buf, int * mid, int * end); +void asdf_ended_chained_reverse(int * buf, int * mid, int * end); +void asdf_ended_already_started(int * __attribute__((__ended_by__(mid))) buf, int * mid, int * end); +void asdf_ended_already_ended(int * buf, int * mid __attribute__((__ended_by__(end))), int * end); +void asdf_ended_redundant(int * __attribute__((__ended_by__(end))) buf, int * end); + +void asdf_nterm(char * buf); diff --git a/clang/test/APINotes/Inputs/Headers/BoundsUnsafeObjC.apinotes b/clang/test/APINotes/Inputs/Headers/BoundsUnsafeObjC.apinotes new file mode 100644 index 0000000000000..d2e41dd4253e2 --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/BoundsUnsafeObjC.apinotes @@ -0,0 +1,92 @@ +--- +Name: BoundsUnsafe +Classes: + - Name: Foo + Methods: + - Selector: "asdf_counted:len:" + MethodKind: Instance + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: len + - Selector: "asdf_sized:size:" + MethodKind: Instance + Parameters: + - Position: 0 + BoundsSafety: + Kind: sized_by + Level: 0 + BoundedBy: size + - Selector: "asdf_counted_n:len:" + MethodKind: Instance + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by_or_null + Level: 0 + BoundedBy: len + - Selector: "asdf_sized_n:size:" + MethodKind: Instance + Parameters: + - Position: 0 + BoundsSafety: + Kind: sized_by_or_null + Level: 0 + BoundedBy: size + - Selector: "asdf_ended:end:" + MethodKind: Instance + Parameters: + - Position: 0 + BoundsSafety: + Kind: ended_by + Level: 0 + BoundedBy: end + - Selector: "asdf_sized_mul:size:count:" + MethodKind: Instance + Parameters: + - Position: 0 + BoundsSafety: + Kind: sized_by + Level: 0 + BoundedBy: "size * count" + - Selector: "asdf_counted_out:len:" + MethodKind: Instance + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 1 + BoundedBy: "*len" + - Selector: "asdf_counted_const:" + MethodKind: Instance + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: 7 + - Selector: "asdf_counted_nullable:buf:" + MethodKind: Instance + Parameters: + - Position: 1 + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: len + - Selector: "asdf_counted_noescape:len:" + MethodKind: Instance + Parameters: + - Position: 0 + NoEscape: true + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: len + - Selector: "asdf_nterm:" + MethodKind: Instance + Parameters: + - Position: 0 + Type: "int * __attribute__((__terminated_by__(0)))" + diff --git a/clang/test/APINotes/Inputs/Headers/BoundsUnsafeObjC.h b/clang/test/APINotes/Inputs/Headers/BoundsUnsafeObjC.h new file mode 100644 index 0000000000000..111472f51b5a7 --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/BoundsUnsafeObjC.h @@ -0,0 +1,16 @@ +@interface Foo +- (void) asdf_counted: (int *)buf len: (int)len; +- (void) asdf_sized: (int *)buf size: (int)size; +- (void) asdf_counted_n: (int *)buf len: (int)len; +- (void) asdf_sized_n: (int *)buf size: (int)size; +- (void) asdf_ended: (int *)buf end: (int *)end; + +- (void) asdf_sized_mul: (int *)buf size:(int)size count:(int)count; +- (void) asdf_counted_out: (int **)buf len:(int *)len; +- (void) asdf_counted_const: (int *)buf; +- (void) asdf_counted_nullable: (int)len buf:(int * _Nullable)buf; +- (void) asdf_counted_noescape: (int *)buf len: (int)len; + +- (void) asdf_nterm: (char *) buf; +@end + diff --git a/clang/test/APINotes/Inputs/Headers/module.modulemap b/clang/test/APINotes/Inputs/Headers/module.modulemap index 31f7d36356d83..b6fa0fe6f6ab2 100644 --- a/clang/test/APINotes/Inputs/Headers/module.modulemap +++ b/clang/test/APINotes/Inputs/Headers/module.modulemap @@ -1,3 +1,15 @@ +module BoundsUnsafe { + header "BoundsUnsafe.h" +} + +module BoundsUnsafeErrors { + header "BoundsUnsafeErrors.h" +} + +module BoundsUnsafeObjC { + header "BoundsUnsafeObjC.h" +} + module ExternCtx { header "ExternCtx.h" } diff --git a/clang/test/APINotes/boundssafety-errors.c b/clang/test/APINotes/boundssafety-errors.c new file mode 100644 index 0000000000000..72dcbfa622931 --- /dev/null +++ b/clang/test/APINotes/boundssafety-errors.c @@ -0,0 +1,135 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: split-file %s %t/Headers +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fsyntax-only -fapinotes-modules -I %t/Headers -fexperimental-bounds-safety-attributes %t/Headers/SemaErrors.c 2>&1 | FileCheck %s +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fsyntax-only -fapinotes-modules -I %t/Headers -fexperimental-bounds-safety-attributes %t/Headers/NegLevel.c 2>&1 | FileCheck %s --check-prefix NEGLEVEL +// RUN: not %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fsyntax-only -fapinotes-modules -I %t/Headers -fexperimental-bounds-safety-attributes %t/Headers/InvalidKind.c 2>&1 | FileCheck %s --check-prefix INVALIDKIND + +//--- module.modulemap +module SemaErrors { + header "SemaErrors.h" +} +module NegLevel { + header "NegLevel.h" +} +module InvalidKind { + header "InvalidKind.h" +} + +//--- SemaErrors.c +#include "SemaErrors.h" +//--- SemaErrors.apinotes +Name: OOBLevel +Functions: + - Name: oob_level + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 42 + BoundedBy: len + - Name: off_by_1_level + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 1 + BoundedBy: len + - Name: nonpointer_param + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: len + - Name: wrong_name + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: len + - Name: wrong_scope + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: static_len + - Name: mismatching_count + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: 0 + BoundedBy: apinote_len + - Name: mismatching_end + Parameters: + - Position: 0 + BoundsSafety: + Kind: ended_by + Level: 0 + BoundedBy: apinote_end +//--- SemaErrors.h +// CHECK: SemaErrors.h:{{.*}}:{{.*}}: error: __counted_by attribute only applies to pointer arguments +// CHECK-NEXT: oob_level +void oob_level(int * buf, int len); +// CHECK: SemaErrors.h:{{.*}}:{{.*}}: error: __counted_by attribute only applies to pointer arguments +// CHECK-NEXT: off_by_1_level +void off_by_1_level(int * buf, int len); +// CHECK: SemaErrors.h:{{.*}}:{{.*}}: error: __counted_by attribute only applies to pointer arguments +// CHECK-NEXT: nonpointer_param +void nonpointer_param(int buf, int len); +// CHECK: :1:1: error: use of undeclared identifier 'len'; did you mean 'len2'? +// CHECK: SemaErrors.h:{{.*}}:{{.*}}: note: 'len2' declared here +// CHECK-NEXT: wrong_name +void wrong_name(int * buf, int len2); +// CHECK: SemaErrors.h:{{.*}}:{{.*}}: error: count expression in function declaration may only reference parameters of that function +// CHECK-NEXT: wrong_scope +int static_len = 5; +void wrong_scope(int * buf); +// CHECK: SemaErrors.h:{{.*}}:{{.*}}: error: pointer cannot have more than one count attribute +// CHECK-NEXT: mismatching_count +void mismatching_count(int * __attribute__((__counted_by__(header_len))) buf, int apinote_len, int header_len); +// CHECK: SemaErrors.h:{{.*}}:{{.*}}: error: pointer cannot have more than one end attribute +// CHECK-NEXT: mismatching_end +void mismatching_end(int * __attribute__((__ended_by__(header_end))) buf, int * apinote_end, int * header_end); +// CHECK: SemaErrors.c:{{.*}}:{{.*}}: fatal error: could not build module 'SemaErrors' + + +//--- NegLevel.apinotes +Name: NegLevel +Functions: + - Name: neg_level + Parameters: + - Position: 0 + BoundsSafety: + Kind: counted_by + Level: -1 + BoundedBy: len +//--- NegLevel.h +void neg_level(int * buf, int len); +//--- NegLevel.c +#include "NegLevel.h" +// NEGLEVEL: NegLevel.apinotes:{{.*}}:{{.*}}: error: invalid number +// NEGLEVEL-NEXT: Level: -1 +// NEGLEVEL: NegLevel.c:{{.*}}:{{.*}}: fatal error: could not build module 'NegLevel' + + +//--- InvalidKind.apinotes +Name: InvalidKind +Functions: + - Name: invalid_kind + Parameters: + - Position: 0 + BoundsSafety: + Kind: __counted_by + Level: 0 + BoundedBy: len +//--- InvalidKind.h +void invalid_kind(int * buf, int len); +//--- InvalidKind.c +#include "InvalidKind.h" +// INVALIDKIND: InvalidKind.apinotes:{{.*}}:{{.*}}: error: unknown enumerated scalar +// INVALIDKIND-NEXT: Kind: __counted_by +// INVALIDKIND: InvalidKind.c:{{.*}}:{{.*}}: fatal error: could not build module 'InvalidKind' + diff --git a/clang/test/APINotes/boundssafety.c b/clang/test/APINotes/boundssafety.c new file mode 100644 index 0000000000000..3140d88dfcaeb --- /dev/null +++ b/clang/test/APINotes/boundssafety.c @@ -0,0 +1,69 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fsyntax-only -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks -fexperimental-bounds-safety-attributes %s -ast-dump -ast-dump-filter asdf | FileCheck %s + +#include "BoundsUnsafe.h" + +// CHECK: asdf_counted 'void (int * __counted_by(len), int)' +// CHECK: buf 'int * __counted_by(len)':'int *' + +// CHECK: asdf_sized 'void (int * __sized_by(size), int)' +// CHECK: buf 'int * __sized_by(size)':'int *' + +// CHECK: asdf_counted_n 'void (int * __counted_by_or_null(len), int)' +// CHECK: buf 'int * __counted_by_or_null(len)':'int *' + +// CHECK: asdf_sized_n 'void (int * __sized_by_or_null(size), int)' +// CHECK: buf 'int * __sized_by_or_null(size)':'int *' + +// CHECK: asdf_ended 'void (int * __ended_by(end), int * /* __started_by(buf) */ )' +// CHECK: buf 'int * __ended_by(end)':'int *' +// CHECK: end 'int * /* __started_by(buf) */ ':'int *' + +// CHECK: asdf_sized_mul 'void (int * __sized_by(size * count), int, int)' +// CHECK: buf 'int * __sized_by(size * count)':'int *' + +// CHECK: asdf_counted_out 'void (int * __counted_by(*len)*, int *)' +// CHECK: buf 'int * __counted_by(*len)*' + +// CHECK: asdf_counted_const 'void (int * __counted_by(7))' +// CHECK: buf 'int * __counted_by(7)':'int *' + +// CHECK: asdf_counted_nullable 'void (int, int * __counted_by(len) _Nullable)' +// CHECK: buf 'int * __counted_by(len) _Nullable':'int *' + +// CHECK: asdf_counted_noescape 'void (int * __counted_by(len), int)' +// CHECK: buf 'int * __counted_by(len)':'int *' +// CHECK-NEXT: NoEscapeAttr + +// CHECK: asdf_counted_default_level 'void (int * __counted_by(len), int)' +// CHECK: buf 'int * __counted_by(len)':'int *' + +// CHECK: asdf_counted_redundant 'void (int * __counted_by(len), int)' +// CHECK: buf 'int * __counted_by(len)':'int *' + +// CHECK: asdf_ended_chained 'void (int * __ended_by(mid), int * __ended_by(end) /* __started_by(buf) */ , int * /* __started_by(mid) */ )' +// CHECK: buf 'int * __ended_by(mid)':'int *' +// CHECK: mid 'int * __ended_by(end) /* __started_by(buf) */ ':'int *' +// CHECK: end 'int * /* __started_by(mid) */ ':'int *' + +// CHECK: asdf_ended_chained_reverse 'void (int * __ended_by(mid), int * __ended_by(end) /* __started_by(buf) */ , int * /* __started_by(mid) */ )' +// CHECK: buf 'int * __ended_by(mid)':'int *' +// CHECK: mid 'int * __ended_by(end) /* __started_by(buf) */ ':'int *' +// CHECK: end 'int * /* __started_by(mid) */ ':'int *' + +// CHECK: asdf_ended_already_started 'void (int * __ended_by(mid), int * __ended_by(end) /* __started_by(buf) */ , int * /* __started_by(mid) */ )' +// CHECK: buf 'int * __ended_by(mid)':'int *' +// CHECK: mid 'int * __ended_by(end) /* __started_by(buf) */ ':'int *' +// CHECK: end 'int * /* __started_by(mid) */ ':'int *' + +// CHECK: asdf_ended_already_ended 'void (int * __ended_by(mid), int * __ended_by(end) /* __started_by(buf) */ , int * /* __started_by(mid) */ )' +// CHECK: buf 'int * __ended_by(mid)':'int *' +// CHECK: mid 'int * __ended_by(end) /* __started_by(buf) */ ':'int *' +// CHECK: end 'int * /* __started_by(mid) */ ':'int *' + +// CHECK: asdf_ended_redundant 'void (int * __ended_by(end), int * /* __started_by(buf) */ )' +// CHECK: buf 'int * __ended_by(end)':'int *' +// CHECK: end 'int * /* __started_by(buf) */ ':'int *' + +// CHECK: asdf_nterm 'void (int * __terminated_by(0))' +// CHECK: buf 'int * __terminated_by(0)':'int *' diff --git a/clang/test/APINotes/boundssafety.m b/clang/test/APINotes/boundssafety.m new file mode 100644 index 0000000000000..19e14607c2969 --- /dev/null +++ b/clang/test/APINotes/boundssafety.m @@ -0,0 +1,55 @@ +// RUN: rm -rf %t && mkdir -p %t + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fsyntax-only -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks -fexperimental-bounds-safety-attributes %s -ast-dump -ast-dump-filter asdf | FileCheck %s + +#include "BoundsUnsafeObjC.h" + +// CHECK-LABEL: asdf_counted +// CHECK: buf 'int * __counted_by(len)':'int *' +// CHECK-NEXT: len 'int' +// CHECK-NEXT: DependerDeclsAttr {{.*}} 0 + +// CHECK-LABEL: asdf_sized +// CHECK: buf 'int * __sized_by(size)':'int *' +// CHECK-NEXT: size 'int' +// CHECK-NEXT: DependerDeclsAttr {{.*}} 0 + +// CHECK-LABEL: asdf_counted_n +// CHECK: buf 'int * __counted_by_or_null(len)':'int *' +// CHECK-NEXT: len 'int' +// CHECK-NEXT: DependerDeclsAttr {{.*}} 0 + +// CHECK-LABEL: asdf_sized_n +// CHECK: buf 'int * __sized_by_or_null(size)':'int *' +// CHECK-NEXT: size 'int' +// CHECK-NEXT: DependerDeclsAttr {{.*}} 0 + +// CHECK-LABEL: asdf_ended +// CHECK: buf 'int * __ended_by(end)':'int *' +// CHECK: end 'int * /* __started_by(buf) */ ':'int *' + +// CHECK-LABEL: asdf_sized_mul +// CHECK: buf 'int * __sized_by(size * count)':'int *' +// CHECK-NEXT: size 'int' +// CHECK-NEXT: DependerDeclsAttr {{.*}} 0 +// CHECK-NEXT: count 'int' +// CHECK-NEXT: DependerDeclsAttr {{.*}} 0 + +// CHECK-LABEL: asdf_counted_out +// CHECK: buf 'int * __counted_by(*len)*' +// CHECK-NEXT: len 'int *' +// CHECK-NEXT: DependerDeclsAttr {{.*}} IsDeref {{.*}} 1 + +// CHECK-LABEL: asdf_counted_const +// CHECK: buf 'int * __counted_by(7)':'int *' + +// CHECK-LABEL: asdf_counted_nullable +// CHECK: buf 'int * __counted_by(len) _Nullable':'int *' + +// CHECK-LABEL: asdf_counted_noescape +// CHECK: buf 'int * __counted_by(len)':'int *' +// CHECK-NEXT: NoEscapeAttr + +// CHECK-LABEL: asdf_nterm +// CHECK: buf 'int * __terminated_by(0)':'int *' +