[clang] [APINotes][BoundsSafety] Upstream API notes format for bounds-safety function parameters (PR #186960)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 16 23:07:05 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Aditya Medhane (flash1729)
<details>
<summary>Changes</summary>
Related: #<!-- -->183340
Upstream from swiftlang/llvm-project@<!-- -->721e6a3 with some modifications.
Since __counted_by and related attributes aren't supported upstream yet in
function signatures, this only upstreams the format part — YAML parsing,
binary serialization, and deserialization. Semantic application is a follow-up.
Modifications from downstream:
- Renamed local YAML struct from `BoundsSafety` to `BoundsSafetyNotes` to avoid name collision with `Param::BoundsSafety` field (downstream fixed this in a subsequent commit)
- `Level` in `BoundsSafetyNotes` is now `std::optional<unsigned>` so we can actually tell apart "user didn't write Level:" from "Level: 0"
- `asdf_sized` and `asdf_sized_n` use `void *buf` — makes more sense for sized_by
- Added `asdf_counted_indirect` with `int **` and `Level: 1` to test the indirection level path
- Removed a vacuous assert (`CountedBy == 0`, always true for unsigned)
- Added `operator!=` for `BoundsSafetyInfo` to match other types in `Types.h`
- Added doc comments for `LevelAudited` and `Level`
---
Full diff: https://github.com/llvm/llvm-project/pull/186960.diff
10 Files Affected:
- (modified) clang/include/clang/APINotes/Types.h (+80-2)
- (modified) clang/lib/APINotes/APINotesFormat.h (+1-1)
- (modified) clang/lib/APINotes/APINotesReader.cpp (+28-1)
- (modified) clang/lib/APINotes/APINotesTypes.cpp (+29)
- (modified) clang/lib/APINotes/APINotesWriter.cpp (+34-1)
- (modified) clang/lib/APINotes/APINotesYAMLCompiler.cpp (+43)
- (added) clang/test/APINotes/Inputs/Headers/BoundsUnsafe.apinotes (+39)
- (added) clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h (+6)
- (modified) clang/test/APINotes/Inputs/Headers/module.modulemap (+4)
- (added) clang/test/APINotes/bounds-safety.c (+28)
``````````diff
diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h
index fb2b91a3e1750..ed6737672b378 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -338,6 +338,78 @@ inline bool operator!=(const ContextInfo &LHS, const ContextInfo &RHS) {
return !(LHS == RHS);
}
+class BoundsSafetyInfo {
+public:
+ enum class BoundsSafetyKind {
+ CountedBy = 0,
+ CountedByOrNull,
+ SizedBy,
+ SizedByOrNull,
+ EndedBy,
+ };
+
+private:
+ /// Whether the bounds safety kind has been audited.
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned KindAudited : 1;
+
+ /// The kind of bounds safety for this property. Only valid if the bounds
+ /// safety has been audited.
+ LLVM_PREFERRED_TYPE(BoundsSafetyKind)
+ unsigned Kind : 3;
+
+ /// Whether the pointer indirection level has been specified.
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned LevelAudited : 1;
+
+ /// The pointer indirection level at which the bounds annotation applies.
+ /// Only valid if LevelAudited is set.
+ 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;
+}
+
+inline bool operator!=(const BoundsSafetyInfo &LHS,
+ const BoundsSafetyInfo &RHS) {
+ return !(LHS == RHS);
+}
+
/// API notes for a variable/property.
class VariableInfo : public CommonEntityInfo {
/// Whether this property has been audited for nullability.
@@ -477,10 +549,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 +600,9 @@ class ParamInfo : public VariableInfo {
if (!RawRetainCountConvention)
RawRetainCountConvention = RHS.RawRetainCountConvention;
+ if (!BoundsSafety)
+ BoundsSafety = RHS.BoundsSafety;
+
return *this;
}
@@ -540,7 +617,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 bb423ccb2bfaf..fe485327b8130 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 7f9bb5f12cda7..086c30ea47ec2 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -330,6 +330,29 @@ class FieldTableInfo
}
};
+/// Read serialized BoundsSafetyInfo.
+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 <=
+ static_cast<uint8_t>(BoundsSafetyInfo::BoundsSafetyKind::EndedBy));
+ Info.setKindAudited(static_cast<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 +369,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..9baf06efde6ba 100644
--- a/clang/lib/APINotes/APINotesTypes.cpp
+++ b/clang/lib/APINotes/APINotesTypes.cpp
@@ -76,6 +76,33 @@ 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(static_cast<BoundsSafetyKind>(Kind) <= BoundsSafetyKind::EndedBy);
+ switch (static_cast<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 +111,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 47ed93a567c0e..daf7903c9d9ad 100644
--- a/clang/lib/APINotes/APINotesWriter.cpp
+++ b/clang/lib/APINotes/APINotesWriter.cpp
@@ -1071,14 +1071,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()) {
+ assert((uint8_t)*kind < (1 << 3));
+ flags |= 0x01; // 1 bit
+ flags |= (uint8_t)*kind << 1; // 3 bits
+ }
+ flags <<= 4;
+ if (auto level = BSI.getLevel()) {
+ assert((uint8_t)*level < (1 << 3));
+ flags |= 0x01; // 1 bit
+ flags |= (uint8_t)*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;
+ if (auto BSI = PI.BoundsSafety)
+ BSISize = getBoundsSafetyInfoSize(*BSI);
+ 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)
@@ -1096,6 +1127,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, *BSI);
}
/// 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 3be528feb325e..fdffbbac00c4c 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -49,6 +49,31 @@ enum class APIAvailability {
};
} // namespace
+namespace {
+struct BoundsSafetyNotes {
+ BoundsSafetyInfo::BoundsSafetyKind Kind;
+ std::optional<unsigned> Level;
+ 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 llvm {
namespace yaml {
template <> struct ScalarEnumerationTraits<APIAvailability> {
@@ -86,6 +111,7 @@ struct Param {
std::optional<bool> Lifetimebound = false;
std::optional<NullabilityKind> Nullability;
std::optional<RetainCountConventionKind> RetainCountConvention;
+ std::optional<BoundsSafetyNotes> BoundsSafety;
StringRef Type;
};
@@ -137,6 +163,15 @@ 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<BoundsSafetyNotes> {
+ static void mapping(IO &IO, BoundsSafetyNotes &BS) {
+ IO.mapRequired("Kind", BS.Kind);
+ IO.mapRequired("BoundedBy", BS.BoundsExpr);
+ IO.mapOptional("Level", BS.Level);
}
};
} // namespace yaml
@@ -787,6 +822,14 @@ class YAMLConverter {
PI.setLifetimebound(P.Lifetimebound);
PI.setType(std::string(P.Type));
PI.setRetainCountConvention(P.RetainCountConvention);
+ if (P.BoundsSafety) {
+ BoundsSafetyInfo BSI;
+ BSI.setKindAudited(P.BoundsSafety->Kind);
+ if (P.BoundsSafety->Level)
+ 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..27b44dae0466d
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.apinotes
@@ -0,0 +1,39 @@
+Name: BoundsUnsafe
+Functions:
+ - Name: asdf_counted
+ Parameters:
+ - Position: 0
+ BoundsSafety:
+ Kind: counted_by
+ BoundedBy: len
+ - Name: asdf_sized
+ Parameters:
+ - Position: 0
+ BoundsSafety:
+ Kind: sized_by
+ BoundedBy: size
+ - Name: asdf_counted_n
+ Parameters:
+ - Position: 0
+ BoundsSafety:
+ Kind: counted_by_or_null
+ BoundedBy: len
+ - Name: asdf_sized_n
+ Parameters:
+ - Position: 0
+ BoundsSafety:
+ Kind: sized_by_or_null
+ BoundedBy: size
+ - Name: asdf_ended
+ Parameters:
+ - Position: 0
+ BoundsSafety:
+ Kind: ended_by
+ BoundedBy: end
+ - Name: asdf_counted_indirect
+ Parameters:
+ - Position: 0
+ BoundsSafety:
+ Kind: counted_by
+ BoundedBy: len
+ Level: 1
diff --git a/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h
new file mode 100644
index 0000000000000..2122b5c9757b2
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/BoundsUnsafe.h
@@ -0,0 +1,6 @@
+void asdf_counted(int *buf, int len);
+void asdf_sized(void *buf, int size);
+void asdf_counted_n(int *buf, int len);
+void asdf_sized_n(void *buf, int size);
+void asdf_ended(int *buf, int *end);
+void asdf_counted_indirect(int **buf, int len);
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..d9c6032aeb568
--- /dev/null
+++ b/clang/test/APINotes/bounds-safety.c
@@ -0,0 +1,28 @@
+// 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 %s -ast-dump -ast-dump-filter asdf | FileCheck %s
+
+// FIXME: Bounds safety annotations are parsed and stored in the APINotes
+// format, but are not yet applied as attributes in the AST upstream since
+// __counted_by and related attributes are not yet supported in function
+// signatures. Once that support is added, update this test to verify the
+// annotations appear on the relevant declarations.
+
+#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 (void *, int)'
+// CHECK: imported in BoundsUnsafe buf 'void *'
+
+// CHECK: imported in BoundsUnsafe asdf_counted_n 'void (int *, int)'
+// CHECK: imported in BoundsUnsafe buf 'int *'
+
+// CHECK: imported in BoundsUnsafe asdf_sized_n 'void (void *, int)'
+// CHECK: imported in BoundsUnsafe buf 'void *'
+
+// CHECK: imported in BoundsUnsafe asdf_ended 'void (int *, int *)'
+// CHECK: imported in BoundsUnsafe buf 'int *'
+
+// CHECK: imported in BoundsUnsafe asdf_counted_indirect 'void (int **, int)'
+// CHECK: imported in BoundsUnsafe buf 'int **'
``````````
</details>
https://github.com/llvm/llvm-project/pull/186960
More information about the cfe-commits
mailing list