https://github.com/c8ef updated https://github.com/llvm/llvm-project/pull/185257
>From 19b48fcbf1a7834d8bdd15c2374addfefd611039 Mon Sep 17 00:00:00 2001 From: c8ef <[email protected]> Date: Sun, 8 Mar 2026 15:07:08 +0800 Subject: [PATCH 1/3] implement apinotes for bound safety --- clang/include/clang/APINotes/Types.h | 75 ++++++++++++++++++- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 28 ++++++- clang/lib/APINotes/APINotesTypes.cpp | 30 ++++++++ clang/lib/APINotes/APINotesWriter.cpp | 35 ++++++++- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 45 +++++++++++ .../Inputs/Headers/BoundsUnsafe.apinotes | 38 ++++++++++ .../APINotes/Inputs/Headers/BoundsUnsafe.h | 5 ++ .../APINotes/Inputs/Headers/module.modulemap | 4 + clang/test/APINotes/bounds-safety.c | 19 +++++ 10 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 clang/test/APINotes/Inputs/Headers/BoundsUnsafe.apinotes create mode 100644 clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h create mode 100644 clang/test/APINotes/bounds-safety.c diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index fb2b91a3e1750..f6b8b855218f9 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -338,6 +338,71 @@ inline bool operator!=(const ContextInfo &LHS, const ContextInfo &RHS) { return !(LHS == RHS); } +/// API notes for bounds safety annotations +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<BoundsSafetyKind> getKind() const { + return KindAudited ? std::optional<BoundsSafetyKind>( + static_cast<BoundsSafetyKind>(Kind)) + : std::nullopt; + } + + void setKindAudited(BoundsSafetyKind kind) { + KindAudited = true; + Kind = static_cast<unsigned>(kind); + } + + std::optional<unsigned> getLevel() const { + return LevelAudited ? std::optional<unsigned>(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; +} + /// API notes for a variable/property. class VariableInfo : public CommonEntityInfo { /// Whether this property has been audited for nullability. @@ -477,10 +542,12 @@ class ParamInfo : public VariableInfo { unsigned RawRetainCountConvention : 3; public: + std::optional<BoundsSafetyInfo> BoundsSafety; + ParamInfo() : NoEscapeSpecified(false), NoEscape(false), LifetimeboundSpecified(false), Lifetimebound(false), - RawRetainCountConvention() {} + RawRetainCountConvention(), BoundsSafety(std::nullopt) {} std::optional<bool> isNoEscape() const { return NoEscapeSpecified ? std::optional<bool>(NoEscape) : std::nullopt; @@ -526,6 +593,9 @@ class ParamInfo : public VariableInfo { if (!RawRetainCountConvention) RawRetainCountConvention = RHS.RawRetainCountConvention; + if (!BoundsSafety) + BoundsSafety = RHS.BoundsSafety; + return *this; } @@ -540,7 +610,8 @@ 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 && + LHS.BoundsSafety == RHS.BoundsSafety; } inline bool operator!=(const ParamInfo &LHS, const ParamInfo &RHS) { diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 9d868a1b4da1f..6b1cc3128ca42 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 = 38; // SwiftSafety +const uint16_t VERSION_MINOR = 39; // 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 f00c7ac1d9d9b..c176f4f316a2e 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -330,6 +330,28 @@ class FieldTableInfo } }; +void ReadBoundsSafetyInfo(const uint8_t *&Data, BoundsSafetyInfo &Info) { + uint8_t Payload = endian::readNext<uint8_t, llvm::endianness::little>(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<uint16_t, llvm::endianness::little>(Data); + Info.ExternalBounds = std::string(Data, Data + ExternalBoundsLen); + Data += ExternalBoundsLen; +} + /// Read serialized ParamInfo. void ReadParamInfo(const uint8_t *&Data, ParamInfo &Info) { ReadVariableInfo(Data, Info); @@ -346,7 +368,11 @@ void ReadParamInfo(const uint8_t *&Data, ParamInfo &Info) { if (Payload & 0x01) Info.setNoEscape(Payload & 0x02); Payload >>= 2; - assert(Payload == 0 && "Bad API notes"); + if (Payload & 0x01) { + BoundsSafetyInfo BSI; + ReadBoundsSafetyInfo(Data, BSI); + Info.BoundsSafety = BSI; + } } /// Read serialized FunctionInfo. diff --git a/clang/lib/APINotes/APINotesTypes.cpp b/clang/lib/APINotes/APINotesTypes.cpp index bff4be104c6c8..9babd1d9c84dd 100644 --- a/clang/lib/APINotes/APINotesTypes.cpp +++ b/clang/lib/APINotes/APINotesTypes.cpp @@ -76,6 +76,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() ? "<missing>" : ExternalBounds) << '\n'; +} + LLVM_DUMP_METHOD void ParamInfo::dump(llvm::raw_ostream &OS) const { static_cast<const VariableInfo &>(*this).dump(OS); if (NoEscapeSpecified) @@ -84,6 +112,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 390aea57f3915..6da5c5894a7d8 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -1074,14 +1074,45 @@ void APINotesWriter::Implementation::writeGlobalVariableBlock( } 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<uint8_t>(flags); + writer.write<uint16_t>(BSI.ExternalBounds.size()); + writer.write( + ArrayRef<char>{BSI.ExternalBounds.data(), BSI.ExternalBounds.size()}); +} + +unsigned getBoundsSafetyInfoSize(const BoundsSafetyInfo &BSI) { + return 1 + sizeof(uint16_t) + BSI.ExternalBounds.size(); +} + 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) @@ -1099,6 +1130,8 @@ void emitParamInfo(raw_ostream &OS, const ParamInfo &PI) { llvm::support::endian::Writer writer(OS, llvm::endianness::little); writer.write<uint8_t>(flags); + if (auto BSI = PI.BoundsSafety) + emitBoundsSafetyInfo(OS, *PI.BoundsSafety); } /// 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 3b82fdda68af1..7cc3fc2fe14bb 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -79,6 +79,31 @@ template <> struct ScalarEnumerationTraits<MethodKind> { } // namespace yaml } // namespace llvm +namespace { +struct BoundsSafety { + BoundsSafetyInfo::BoundsSafetyKind Kind; + unsigned Level = 0; + StringRef BoundsExpr = ""; +}; +} // namespace + +namespace llvm { +namespace yaml { +template <> struct ScalarEnumerationTraits<BoundsSafetyInfo::BoundsSafetyKind> { + 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 + namespace { struct Param { int Position; @@ -86,6 +111,7 @@ struct Param { std::optional<bool> Lifetimebound = false; std::optional<NullabilityKind> Nullability; std::optional<RetainCountConventionKind> RetainCountConvention; + std::optional<BoundsSafety> BoundsSafety; StringRef Type; }; @@ -137,8 +163,18 @@ template <> struct MappingTraits<Param> { IO.mapOptional("NoEscape", P.NoEscape); IO.mapOptional("Lifetimebound", P.Lifetimebound); IO.mapOptional("Type", P.Type, StringRef("")); + IO.mapOptional("BoundsSafety", P.BoundsSafety); + } +}; + +template <> struct MappingTraits<BoundsSafety> { + static void mapping(IO &IO, BoundsSafety &BS) { + IO.mapRequired("Kind", BS.Kind); + IO.mapRequired("BoundedBy", BS.BoundsExpr); + IO.mapOptional("Level", BS.Level, 0); } }; + } // namespace yaml } // namespace llvm @@ -787,6 +823,15 @@ class YAMLConverter { PI.setLifetimebound(P.Lifetimebound); PI.setType(std::string(P.Type)); PI.setRetainCountConvention(P.RetainCountConvention); + + BoundsSafetyInfo BSI; + if (P.BoundsSafety) { + BSI.setKindAudited(P.BoundsSafety->Kind); + BSI.setLevelAudited(P.BoundsSafety->Level); + BSI.ExternalBounds = P.BoundsSafety->BoundsExpr.str(); + } + PI.BoundsSafety = BSI; + if (static_cast<int>(OutInfo.Params.size()) <= P.Position) OutInfo.Params.resize(P.Position + 1); if (P.Position == -1) diff --git a/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.apinotes b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.apinotes new file mode 100644 index 0000000000000..045e98f92d51b --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.apinotes @@ -0,0 +1,38 @@ +--- +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 diff --git a/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h new file mode 100644 index 0000000000000..1bf8dd50083cc --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h @@ -0,0 +1,5 @@ +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); diff --git a/clang/test/APINotes/Inputs/Headers/module.modulemap b/clang/test/APINotes/Inputs/Headers/module.modulemap index bedb7d505f794..a83844c117fcf 100644 --- a/clang/test/APINotes/Inputs/Headers/module.modulemap +++ b/clang/test/APINotes/Inputs/Headers/module.modulemap @@ -1,3 +1,7 @@ +module BoundsUnsafe { + header "BoundsUnsafe.h" +} + module ExternCtx { header "ExternCtx.h" } diff --git a/clang/test/APINotes/bounds-safety.c b/clang/test/APINotes/bounds-safety.c new file mode 100644 index 0000000000000..ccd934ba37762 --- /dev/null +++ b/clang/test/APINotes/bounds-safety.c @@ -0,0 +1,19 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fapinotes-modules -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter asdf | FileCheck %s + +#include "BoundsUnsafe.h" + +// CHECK: imported in BoundsUnsafe asdf_counted 'void (int *, int)' +// CHECK: imported in BoundsUnsafe buf 'int *' + +// CHECK: imported in BoundsUnsafe asdf_sized 'void (int *, int)' +// CHECK: imported in BoundsUnsafe buf 'int *' + +// CHECK: imported in BoundsUnsafe asdf_counted_n 'void (int *, int)' +// CHECK: imported in BoundsUnsafe buf 'int *' + +// CHECK: imported in BoundsUnsafe asdf_sized_n 'void (int *, int)' +// CHECK: imported in BoundsUnsafe buf 'int *' + +// CHECK: imported in BoundsUnsafe asdf_ended 'void (int *, int *)' +// CHECK: imported in BoundsUnsafe buf 'int *' >From 4f0387629dbd7839cabda17ffb7d226190d98a36 Mon Sep 17 00:00:00 2001 From: c8ef <[email protected]> Date: Sun, 8 Mar 2026 15:08:37 +0800 Subject: [PATCH 2/3] implement apinotes for bound safety --- clang/lib/APINotes/APINotesWriter.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 6da5c5894a7d8..35f540a433dcd 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -1099,10 +1099,8 @@ unsigned getBoundsSafetyInfoSize(const BoundsSafetyInfo &BSI) { unsigned getParamInfoSize(const ParamInfo &PI) { unsigned BSISize = 0; - /* TO_UPSTREAM(BoundsSafety) ON */ if (auto BSI = PI.BoundsSafety) BSISize = getBoundsSafetyInfoSize(*BSI); - /* TO_UPSTREAM(BoundsSafety) OFF */ return getVariableInfoSize(PI) + 1 + BSISize; } >From 5b40f69e3c4acac9496c0893b1fa57502fc5e39e Mon Sep 17 00:00:00 2001 From: c8ef <[email protected]> Date: Sun, 8 Mar 2026 23:42:03 +0800 Subject: [PATCH 3/3] address review comments --- clang/include/clang/APINotes/Types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index f6b8b855218f9..7f910034b0cf5 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -342,7 +342,7 @@ inline bool operator!=(const ContextInfo &LHS, const ContextInfo &RHS) { class BoundsSafetyInfo { public: enum class BoundsSafetyKind { - CountedBy = 0, + CountedBy, CountedByOrNull, SizedBy, SizedByOrNull, _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
