[clang] [BoundsSafety][APINotes] Upstream API notes format for bounds-safety function parameters (PR #185257)
Connector Switch via cfe-commits
cfe-commits at lists.llvm.org
Sun Mar 8 08:42:15 PDT 2026
https://github.com/c8ef updated https://github.com/llvm/llvm-project/pull/185257
>From 19b48fcbf1a7834d8bdd15c2374addfefd611039 Mon Sep 17 00:00:00 2001
From: c8ef <c8ef at outlook.com>
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 <c8ef at outlook.com>
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 <c8ef at outlook.com>
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,
More information about the cfe-commits
mailing list