[APINotes][unsafe-buffer-usage] Add [[clang::unsafe_buffer_usage]] support in APINotes (#189775)

Support the ``[[clang::unsafe_buffer_usage]]`` attribute in APINotes,
e.g.,
```
    Functions:
      - Name: myUnsafeFunction
        UnsafeBufferUsage: true
```

rdar://171859135
This commit is contained in:
Ziqing Luo
2026-04-21 10:48:51 -07:00
committed by GitHub
parent 0d45876e43
commit 849de61619
12 changed files with 106 additions and 2 deletions

View File

@@ -296,6 +296,15 @@ Attribute Changes in Clang
sound because any writer must hold all capabilities, so holding any one
prevents concurrent writes.
- The ``[[clang::unsafe_buffer_usage]]`` attribute is now supported in API
notes. For example:
.. code-block:: yaml
Functions:
- Name: myUnsafeFunction
UnsafeBufferUsage: true
- Added support for ``[[msvc::forceinline]]`` for functions and
``[[msvc::forceinline_calls]]`` for statements. Both are aliases to
``[[clang::always_inline]]`` with additional checks to ensure that they

View File

@@ -634,6 +634,10 @@ public:
/// A biased RetainCountConventionKind, where 0 means "unspecified".
unsigned RawRetainCountConvention : 3;
/// Whether the function has the [[clang::unsafe_buffer_usage]] attribute
LLVM_PREFERRED_TYPE(bool)
unsigned UnsafeBufferUsage : 1;
// NullabilityKindSize bits are used to encode the nullability. The info
// about the return type is stored at position 0, followed by the nullability
// of the parameters.
@@ -652,7 +656,7 @@ public:
FunctionInfo()
: NullabilityAudited(false), NumAdjustedNullable(0),
RawRetainCountConvention() {}
RawRetainCountConvention(), UnsafeBufferUsage(0) {}
static unsigned getMaxNullabilityIndex() {
return ((sizeof(NullabilityPayload) * CHAR_BIT) / NullabilityKindSize);
@@ -724,6 +728,7 @@ public:
inline bool operator==(const FunctionInfo &LHS, const FunctionInfo &RHS) {
return static_cast<const CommonEntityInfo &>(LHS) == RHS &&
LHS.NullabilityAudited == RHS.NullabilityAudited &&
LHS.UnsafeBufferUsage == RHS.UnsafeBufferUsage &&
LHS.NumAdjustedNullable == RHS.NumAdjustedNullable &&
LHS.NullabilityPayload == RHS.NullabilityPayload &&
LHS.ResultType == RHS.ResultType && LHS.Params == RHS.Params &&

View File

@@ -24,7 +24,8 @@ 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 = 39; // BoundsSafety
const uint16_t VERSION_MINOR = 40; // 39 for BoundsSafety;
// 40 for UnsafeBufferUsageAttr
const uint8_t kSwiftConforms = 1;
const uint8_t kSwiftDoesNotConform = 2;

View File

@@ -378,6 +378,9 @@ void ReadFunctionInfo(const uint8_t *&Data, FunctionInfo &Info) {
ReadCommonEntityInfo(Data, Info);
uint8_t Payload = endian::readNext<uint8_t, llvm::endianness::little>(Data);
if (Payload & 0x1)
Info.UnsafeBufferUsage = 1;
Payload >>= 0x1;
if (auto RawConvention = Payload & 0x7) {
auto Convention = static_cast<RetainCountConventionKind>(RawConvention - 1);
Info.setRetainCountConvention(Convention);

View File

@@ -117,6 +117,7 @@ LLVM_DUMP_METHOD void ParamInfo::dump(llvm::raw_ostream &OS) const {
LLVM_DUMP_METHOD void FunctionInfo::dump(llvm::raw_ostream &OS) const {
static_cast<const CommonEntityInfo &>(*this).dump(OS);
OS << (NullabilityAudited ? "[NullabilityAudited] " : "")
<< (UnsafeBufferUsage ? "[UnsafeBufferUsage] " : "")
<< "RawRetainCountConvention: " << RawRetainCountConvention << ' ';
if (!ResultType.empty())
OS << "Result Type: " << ResultType << ' ';

View File

@@ -1155,6 +1155,8 @@ void emitFunctionInfo(raw_ostream &OS, const FunctionInfo &FI) {
flags <<= 3;
if (auto RCC = FI.getRetainCountConvention())
flags |= static_cast<uint8_t>(RCC.value()) + 1;
flags <<= 0x01;
flags |= FI.UnsafeBufferUsage;
llvm::support::endian::Writer writer(OS, llvm::endianness::little);

View File

@@ -351,6 +351,7 @@ struct Function {
StringRef ResultType;
StringRef SwiftReturnOwnership;
SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
bool UnsafeBufferUsage = false;
};
typedef std::vector<Function> FunctionsSeq;
@@ -376,6 +377,7 @@ template <> struct MappingTraits<Function> {
IO.mapOptional("SwiftReturnOwnership", F.SwiftReturnOwnership,
StringRef(""));
IO.mapOptional("SwiftSafety", F.SafetyKind, SwiftSafetyKind::None);
IO.mapOptional("UnsafeBufferUsage", F.UnsafeBufferUsage, false);
}
};
} // namespace yaml
@@ -1046,6 +1048,7 @@ public:
FI.ResultType = std::string(Function.ResultType);
FI.SwiftReturnOwnership = std::string(Function.SwiftReturnOwnership);
FI.setRetainCountConvention(Function.RetainCountConvention);
FI.UnsafeBufferUsage = Function.UnsafeBufferUsage;
}
void convertTagContext(std::optional<Context> ParentContext, const Tag &T,

View File

@@ -577,6 +577,14 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc,
if (Info.NullabilityAudited)
applyNullability(S, D, Info.getReturnTypeInfo(), Metadata);
// Add [[clang::unsafe_buffer_usage]]
if (Info.UnsafeBufferUsage && !D->getAttr<UnsafeBufferUsageAttr>()) {
handleAPINotedAttribute<UnsafeBufferUsageAttr>(S, D, true, Metadata, [&]() {
return UnsafeBufferUsageAttr::Create(S.getASTContext(),
getPlaceholderAttrInfo());
});
}
// Parameters.
unsigned NumParams = FD ? FD->getNumParams() : MD->param_size();

View File

@@ -0,0 +1,10 @@
---
Name: UnsafeBufferUsage
Functions:
- Name: unsafeFunc
UnsafeBufferUsage: true
- Name: annotatedUnsafeFunc
UnsafeBufferUsage: true
- Name: falseAPINotesButAnnotatedUnsafeFunc
UnsafeBufferUsage: false
...

View File

@@ -0,0 +1,3 @@
void unsafeFunc(int *p, int n);
[[clang::unsafe_buffer_usage]] void annotatedUnsafeFunc(int *p, int n);
[[clang::unsafe_buffer_usage]] void falseAPINotesButAnnotatedUnsafeFunc(int *p, int n);

View File

@@ -65,3 +65,8 @@ module SwiftImportAs {
module SwiftReturnOwnershipForObjC {
header "SwiftReturnOwnershipForObjC.h"
}
module UnsafeBufferUsage {
header "UnsafeBufferUsage.h"
export *
}

View File

@@ -0,0 +1,54 @@
// RUN: rm -rf %t && mkdir -p %t
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules\
// RUN: -I %S/Inputs/Headers %s -x c++ -verify -Wunsafe-buffer-usage -Wno-unused-value
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules\
// RUN: -I %S/Inputs/Headers %s -x c++ -Wunused-value -ast-dump -ast-dump-filter unsafeFunc | FileCheck %s --check-prefixes=CHECK-UNSAFE
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules\
// RUN: -I %S/Inputs/Headers %s -x c++ -Wunused-value -ast-dump -ast-dump-filter annotatedUnsafeFunc | FileCheck %s --check-prefixes=CHECK-ANNO
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules\
// RUN: -I %S/Inputs/Headers %s -x c++ -Wunused-value -ast-dump -ast-dump-filter falseAPINotesButAnnotatedUnsafeFunc | FileCheck %s --check-prefixes=CHECK-ANNO-FALSE
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules\
// RUN: -I %S/Inputs/Headers %s -x c++ -Wunused-value -ast-dump -ast-dump-filter funcWithoutAnnotation | FileCheck %s --check-prefixes=CHECK-UNANNO
#include "UnsafeBufferUsage.h"
// CHECK-UNSAFE: FunctionDecl {{.+}} unsafeFunc
// CHECK-UNSAFE: UnsafeBufferUsageAttr
// If the function already has the attribute, the APINotes does
// nothing no matter how the attribute is specified in the .apinotes
// file:
// CHECK-ANNO: FunctionDecl {{.+}} annotatedUnsafeFunc
// CHECK-ANNO: UnsafeBufferUsageAttr
// CHECK-ANNO-NOT: UnsafeBufferUsageAttr
// CHECK-ANNO: FunctionDecl {{.+}} annotatedUnsafeFunc
// CHECK-ANNO-FALSE: FunctionDecl {{.+}} falseAPINotesButAnnotatedUnsafeFunc
// CHECK-ANNO-FALSE: UnsafeBufferUsageAttr
// CHECK-UNANNO: FunctionDecl {{.+}} funcWithoutAnnotation
// CHECK-UNANNO-NOT: UnsafeBufferUsageAttr
void unsafeFunc(int *p, int n) {
p[n]; // no warning
}
void annotatedUnsafeFunc(int *p, int n) {
p[n]; // no warning
}
void falseAPINotesButAnnotatedUnsafeFunc(int *p, int n) {
p[n]; // no warning
}
void funcWithoutAnnotation(int *p, int n) {
p[n]; // expected-warning{{unsafe buffer access}}
}
void caller(int *p, int n) {
unsafeFunc(p, n); // expected-warning{{function introduces unsafe buffer manipulation}}
annotatedUnsafeFunc(p, n); // expected-warning{{function introduces unsafe buffer manipulation}}
falseAPINotesButAnnotatedUnsafeFunc(p, n); // expected-warning{{function introduces unsafe buffer manipulation}}
funcWithoutAnnotation(p, n); // no warning
}