[APINotes] Support annotating safety of APIs (#157506)

This commit is contained in:
Gábor Horváth
2025-09-15 12:30:29 +02:00
committed by GitHub
parent e1d60f7f3c
commit cdedc81c33
11 changed files with 190 additions and 8 deletions

View File

@@ -229,6 +229,20 @@ declaration kind), all of which are optional:
- Name: vector
SwiftConformsTo: Cxx.CxxSequence
:SwiftSafety:
Import a declaration as ``@safe`` or ``@unsafe`` to Swift.
::
Tags:
- Name: UnsafeType
SwiftSafety: unsafe
- Name: span
Methods:
- Name: size
SwiftSafety: safe
:Availability, AvailabilityMsg:
A value of "nonswift" is equivalent to ``NS_SWIFT_UNAVAILABLE``. A value of

View File

@@ -46,6 +46,8 @@ enum class SwiftNewTypeKind {
Enum,
};
enum class SwiftSafetyKind { Unspecified, Safe, Unsafe, None };
/// Describes API notes data for any entity.
///
/// This is used as the base of all API notes.
@@ -71,13 +73,19 @@ private:
LLVM_PREFERRED_TYPE(bool)
unsigned SwiftPrivate : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned SwiftSafetyAudited : 1;
LLVM_PREFERRED_TYPE(SwiftSafetyKind)
unsigned SwiftSafety : 2;
public:
/// Swift name of this entity.
std::string SwiftName;
CommonEntityInfo()
: Unavailable(0), UnavailableInSwift(0), SwiftPrivateSpecified(0),
SwiftPrivate(0) {}
SwiftPrivate(0), SwiftSafetyAudited(0), SwiftSafety(0) {}
std::optional<bool> isSwiftPrivate() const {
return SwiftPrivateSpecified ? std::optional<bool>(SwiftPrivate)
@@ -89,6 +97,17 @@ public:
SwiftPrivate = Private.value_or(0);
}
std::optional<SwiftSafetyKind> getSwiftSafety() const {
return SwiftSafetyAudited ? std::optional<SwiftSafetyKind>(
static_cast<SwiftSafetyKind>(SwiftSafety))
: std::nullopt;
}
void setSwiftSafety(SwiftSafetyKind Safety) {
SwiftSafetyAudited = 1;
SwiftSafety = static_cast<unsigned>(Safety);
}
friend bool operator==(const CommonEntityInfo &, const CommonEntityInfo &);
CommonEntityInfo &operator|=(const CommonEntityInfo &RHS) {
@@ -108,6 +127,9 @@ public:
if (!SwiftPrivateSpecified)
setSwiftPrivate(RHS.isSwiftPrivate());
if (!SwiftSafetyAudited && RHS.SwiftSafetyAudited)
setSwiftSafety(*RHS.getSwiftSafety());
if (SwiftName.empty())
SwiftName = RHS.SwiftName;
@@ -123,7 +145,9 @@ inline bool operator==(const CommonEntityInfo &LHS,
LHS.Unavailable == RHS.Unavailable &&
LHS.UnavailableInSwift == RHS.UnavailableInSwift &&
LHS.SwiftPrivateSpecified == RHS.SwiftPrivateSpecified &&
LHS.SwiftPrivate == RHS.SwiftPrivate && LHS.SwiftName == RHS.SwiftName;
LHS.SwiftPrivate == RHS.SwiftPrivate &&
LHS.SwiftSafetyAudited == RHS.SwiftSafetyAudited &&
LHS.SwiftSafety == RHS.SwiftSafety && LHS.SwiftName == RHS.SwiftName;
}
inline bool operator!=(const CommonEntityInfo &LHS,

View File

@@ -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 = 37; // SwiftDestroyOp
const uint16_t VERSION_MINOR = 38; // SwiftSafety
const uint8_t kSwiftConforms = 1;
const uint8_t kSwiftDoesNotConform = 2;

View File

@@ -94,11 +94,14 @@ public:
/// Read serialized CommonEntityInfo.
void ReadCommonEntityInfo(const uint8_t *&Data, CommonEntityInfo &Info) {
uint8_t UnavailableBits = *Data++;
Info.Unavailable = (UnavailableBits >> 1) & 0x01;
Info.UnavailableInSwift = UnavailableBits & 0x01;
if ((UnavailableBits >> 2) & 0x01)
Info.setSwiftPrivate(static_cast<bool>((UnavailableBits >> 3) & 0x01));
uint8_t EncodedBits = *Data++;
Info.Unavailable = (EncodedBits >> 1) & 0x01;
Info.UnavailableInSwift = EncodedBits & 0x01;
if ((EncodedBits >> 2) & 0x01)
Info.setSwiftPrivate(static_cast<bool>((EncodedBits >> 3) & 0x01));
if ((EncodedBits >> 4) & 0x01)
Info.setSwiftSafety(
static_cast<SwiftSafetyKind>((EncodedBits >> 5) & 0x03));
unsigned MsgLength =
endian::readNext<uint16_t, llvm::endianness::little>(Data);

View File

@@ -18,6 +18,21 @@ LLVM_DUMP_METHOD void CommonEntityInfo::dump(llvm::raw_ostream &OS) const {
OS << "[UnavailableInSwift] ";
if (SwiftPrivateSpecified)
OS << (SwiftPrivate ? "[SwiftPrivate] " : "");
if (SwiftSafetyAudited) {
switch (*getSwiftSafety()) {
case SwiftSafetyKind::Safe:
OS << "[Safe] ";
break;
case SwiftSafetyKind::Unsafe:
OS << "[Unsafe] ";
break;
case SwiftSafetyKind::Unspecified:
OS << "[Unspecified] ";
break;
case SwiftSafetyKind::None:
break;
}
}
if (!SwiftName.empty())
OS << "Swift Name: " << SwiftName << ' ';
OS << '\n';

View File

@@ -507,6 +507,12 @@ void emitCommonEntityInfo(raw_ostream &OS, const CommonEntityInfo &CEI) {
llvm::support::endian::Writer writer(OS, llvm::endianness::little);
uint8_t payload = 0;
if (auto safety = CEI.getSwiftSafety()) {
payload = static_cast<unsigned>(*safety);
payload <<= 1;
payload |= 0x01;
}
payload <<= 2;
if (auto swiftPrivate = CEI.isSwiftPrivate()) {
payload |= 0x01;
if (*swiftPrivate)

View File

@@ -29,6 +29,18 @@
using namespace clang;
using namespace api_notes;
namespace llvm {
namespace yaml {
template <> struct ScalarEnumerationTraits<SwiftSafetyKind> {
static void enumeration(IO &IO, SwiftSafetyKind &SK) {
IO.enumCase(SK, "unspecified", SwiftSafetyKind::Unspecified);
IO.enumCase(SK, "safe", SwiftSafetyKind::Safe);
IO.enumCase(SK, "unsafe", SwiftSafetyKind::Unsafe);
}
};
} // namespace yaml
} // namespace llvm
namespace {
enum class APIAvailability {
Available = 0,
@@ -163,6 +175,7 @@ struct Method {
bool Required = false;
StringRef ResultType;
StringRef SwiftReturnOwnership;
SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
};
typedef std::vector<Method> MethodsSeq;
@@ -199,6 +212,7 @@ template <> struct MappingTraits<Method> {
IO.mapOptional("ResultType", M.ResultType, StringRef(""));
IO.mapOptional("SwiftReturnOwnership", M.SwiftReturnOwnership,
StringRef(""));
IO.mapOptional("SwiftSafety", M.SafetyKind, SwiftSafetyKind::None);
}
};
} // namespace yaml
@@ -214,6 +228,7 @@ struct Property {
StringRef SwiftName;
std::optional<bool> SwiftImportAsAccessors;
StringRef Type;
SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
};
typedef std::vector<Property> PropertiesSeq;
@@ -235,6 +250,7 @@ template <> struct MappingTraits<Property> {
IO.mapOptional("SwiftName", P.SwiftName, StringRef(""));
IO.mapOptional("SwiftImportAsAccessors", P.SwiftImportAsAccessors);
IO.mapOptional("Type", P.Type, StringRef(""));
IO.mapOptional("SwiftSafety", P.SafetyKind, SwiftSafetyKind::None);
}
};
} // namespace yaml
@@ -254,6 +270,7 @@ struct Class {
std::optional<std::string> SwiftConformance;
MethodsSeq Methods;
PropertiesSeq Properties;
SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
};
typedef std::vector<Class> ClassesSeq;
@@ -279,6 +296,7 @@ template <> struct MappingTraits<Class> {
IO.mapOptional("SwiftConformsTo", C.SwiftConformance);
IO.mapOptional("Methods", C.Methods);
IO.mapOptional("Properties", C.Properties);
IO.mapOptional("SwiftSafety", C.SafetyKind, SwiftSafetyKind::None);
}
};
} // namespace yaml
@@ -297,6 +315,7 @@ struct Function {
StringRef Type;
StringRef ResultType;
StringRef SwiftReturnOwnership;
SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
};
typedef std::vector<Function> FunctionsSeq;
@@ -321,6 +340,7 @@ template <> struct MappingTraits<Function> {
IO.mapOptional("ResultType", F.ResultType, StringRef(""));
IO.mapOptional("SwiftReturnOwnership", F.SwiftReturnOwnership,
StringRef(""));
IO.mapOptional("SwiftSafety", F.SafetyKind, SwiftSafetyKind::None);
}
};
} // namespace yaml
@@ -334,6 +354,7 @@ struct GlobalVariable {
std::optional<bool> SwiftPrivate;
StringRef SwiftName;
StringRef Type;
SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
};
typedef std::vector<GlobalVariable> GlobalVariablesSeq;
@@ -353,6 +374,7 @@ template <> struct MappingTraits<GlobalVariable> {
IO.mapOptional("SwiftPrivate", GV.SwiftPrivate);
IO.mapOptional("SwiftName", GV.SwiftName, StringRef(""));
IO.mapOptional("Type", GV.Type, StringRef(""));
IO.mapOptional("SwiftSafety", GV.SafetyKind, SwiftSafetyKind::None);
}
};
} // namespace yaml
@@ -364,6 +386,7 @@ struct EnumConstant {
AvailabilityItem Availability;
std::optional<bool> SwiftPrivate;
StringRef SwiftName;
SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
};
typedef std::vector<EnumConstant> EnumConstantsSeq;
@@ -381,6 +404,7 @@ template <> struct MappingTraits<EnumConstant> {
IO.mapOptional("AvailabilityMsg", EC.Availability.Msg, StringRef(""));
IO.mapOptional("SwiftPrivate", EC.SwiftPrivate);
IO.mapOptional("SwiftName", EC.SwiftName, StringRef(""));
IO.mapOptional("SwiftSafety", EC.SafetyKind, SwiftSafetyKind::None);
}
};
} // namespace yaml
@@ -424,6 +448,7 @@ struct Field {
std::optional<bool> SwiftPrivate;
StringRef SwiftName;
StringRef Type;
SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
};
typedef std::vector<Field> FieldsSeq;
@@ -443,6 +468,7 @@ template <> struct MappingTraits<Field> {
IO.mapOptional("SwiftPrivate", F.SwiftPrivate);
IO.mapOptional("SwiftName", F.SwiftName, StringRef(""));
IO.mapOptional("Type", F.Type, StringRef(""));
IO.mapOptional("SwiftSafety", F.SafetyKind, SwiftSafetyKind::None);
}
};
} // namespace yaml
@@ -470,6 +496,7 @@ struct Tag {
std::optional<EnumConvenienceAliasKind> EnumConvenienceKind;
std::optional<bool> SwiftCopyable;
std::optional<bool> SwiftEscapable;
SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
FunctionsSeq Methods;
FieldsSeq Fields;
@@ -515,6 +542,7 @@ template <> struct MappingTraits<Tag> {
IO.mapOptional("Methods", T.Methods);
IO.mapOptional("Fields", T.Fields);
IO.mapOptional("Tags", T.Tags);
IO.mapOptional("SwiftSafety", T.SafetyKind, SwiftSafetyKind::None);
}
};
} // namespace yaml
@@ -530,6 +558,7 @@ struct Typedef {
std::optional<StringRef> NSErrorDomain;
std::optional<SwiftNewTypeKind> SwiftType;
std::optional<std::string> SwiftConformance;
const SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
};
typedef std::vector<Typedef> TypedefsSeq;
@@ -602,6 +631,7 @@ struct Namespace {
StringRef SwiftName;
std::optional<bool> SwiftPrivate;
TopLevelItems Items;
const SwiftSafetyKind SafetyKind = SwiftSafetyKind::None;
};
} // namespace
@@ -797,6 +827,8 @@ public:
StringRef APIName) {
convertAvailability(Common.Availability, Info, APIName);
Info.setSwiftPrivate(Common.SwiftPrivate);
if (Common.SafetyKind != SwiftSafetyKind::None)
Info.setSwiftSafety(Common.SafetyKind);
Info.SwiftName = std::string(Common.SwiftName);
}
@@ -956,6 +988,8 @@ public:
void convertFunction(const Function &Function, FuncOrMethodInfo &FI) {
convertAvailability(Function.Availability, FI, Function.Name);
FI.setSwiftPrivate(Function.SwiftPrivate);
if (Function.SafetyKind != SwiftSafetyKind::None)
FI.setSwiftSafety(Function.SafetyKind);
FI.SwiftName = std::string(Function.SwiftName);
std::optional<ParamInfo> This;
convertParams(Function.Params, FI, This);

View File

@@ -13,6 +13,7 @@
#include "CheckExprLifetime.h"
#include "TypeLocBuilder.h"
#include "clang/APINotes/APINotesReader.h"
#include "clang/APINotes/Types.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
@@ -291,6 +292,29 @@ static void ProcessAPINotes(Sema &S, Decl *D,
});
}
// swift_safety
if (auto SafetyKind = Info.getSwiftSafety()) {
bool Addition = *SafetyKind != api_notes::SwiftSafetyKind::Unspecified;
handleAPINotedAttribute<SwiftAttrAttr>(
S, D, Addition, Metadata,
[&] {
return SwiftAttrAttr::Create(
S.Context, *SafetyKind == api_notes::SwiftSafetyKind::Safe
? "safe"
: "unsafe");
},
[](const Decl *D) {
return llvm::find_if(D->attrs(), [](const Attr *attr) {
if (const auto *swiftAttr = dyn_cast<SwiftAttrAttr>(attr)) {
if (swiftAttr->getAttribute() == "safe" ||
swiftAttr->getAttribute() == "unsafe")
return true;
}
return false;
});
});
}
// swift_name
if (!Info.SwiftName.empty()) {
handleAPINotedAttribute<SwiftNameAttr>(

View File

@@ -35,6 +35,14 @@ Tags:
- Name: NoncopyableWithDestroyType
SwiftCopyable: false
SwiftDestroyOp: NCDDestroy
- Name: ImportAsUnsafeStruct
SwiftSafety: unsafe
- Name: StructWithUnsafeMethod
Methods:
- Name: ImportAsUnsafeMethod
SwiftSafety: unsafe
- Name: ImportAsUnsafeMethodActuallySafe
SwiftSafety: safe
Functions:
- Name: functionReturningFrt__
@@ -42,7 +50,20 @@ Functions:
SwiftReturnOwnership: unretained
- Name: functionReturningFrt_returns_retained
SwiftReturnOwnership: retained
- Name: ImportAsUnsafe
SwiftSafety: unsafe
- Name: ImportAsUnsafeAlreadyAnnotated
SwiftSafety: unspecified
Typedefs:
- Name: WrappedOptions
SwiftWrapper: struct
SwiftConformsTo: Swift.OptionSet
SwiftVersions:
- Version: 3.0
Functions:
- Name: ImportAsUnsafeVersioned
SwiftSafety: unsafe
- Version: 6.0
Functions:
- Name: ImportAsUnsafeVersioned
SwiftSafety: safe

View File

@@ -36,3 +36,14 @@ struct NoncopyableWithDestroyType {
};
void NCDDestroy(NoncopyableWithDestroyType instance);
void ImportAsUnsafe();
struct ImportAsUnsafeStruct {
};
struct StructWithUnsafeMethod {
void ImportAsUnsafeMethod();
void ImportAsUnsafeMethodActuallySafe();
};
void ImportAsUnsafeAlreadyAnnotated() __attribute__((swift_attr("unsafe")));
void ImportAsUnsafeVersioned();

View File

@@ -16,6 +16,7 @@
// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter methodReturningFrt_returns_retained | FileCheck -check-prefix=CHECK-METHOD-RETURNING-FRT-RETAINED %s
// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter WrappedOptions | FileCheck -check-prefix=CHECK-WRAPPED-OPTIONS %s
// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter NoncopyableWithDestroyType | FileCheck -check-prefix=CHECK-NONCOPYABLE-WITH-DESTROY %s
// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter ImportAsUnsafe | FileCheck -check-prefix=CHECK-IMPORT-AS-UNSAFE %s
#include <SwiftImportAs.h>
@@ -103,3 +104,32 @@
// CHECK-NONCOPYABLE-WITH-DESTROY: RecordDecl {{.*}}struct NoncopyableWithDestroyType
// CHECK-NONCOPYABLE-WITH-DESTROY: SwiftAttrAttr {{.+}} "destroy:NCDDestroy"
// CHECK-NONCOPYABLE-WITH-DESTROY: SwiftAttrAttr {{.+}} "~Copyable"
// CHECK-IMPORT-AS-UNSAFE: Dumping ImportAsUnsafe:
// CHECK-IMPORT-AS-UNSAFE: FunctionDecl {{.+}} ImportAsUnsafe
// CHECK-IMPORT-AS-UNSAFE: SwiftAttrAttr {{.+}} "unsafe"
// CHECK-IMPORT-AS-UNSAFE: Dumping ImportAsUnsafeStruct:
// CHECK-IMPORT-AS-UNSAFE: CXXRecordDecl {{.+}} ImportAsUnsafeStruct
// CHECK-IMPORT-AS-UNSAFE: SwiftAttrAttr {{.+}} "unsafe"
// CHECK-IMPORT-AS-UNSAFE: Dumping StructWithUnsafeMethod::ImportAsUnsafeMethod:
// CHECK-IMPORT-AS-UNSAFE: CXXMethodDecl {{.+}} ImportAsUnsafeMethod
// CHECK-IMPORT-AS-UNSAFE: SwiftAttrAttr {{.+}} "unsafe"
// CHECK-IMPORT-AS-UNSAFE: Dumping StructWithUnsafeMethod::ImportAsUnsafeMethodActuallySafe:
// CHECK-IMPORT-AS-UNSAFE: CXXMethodDecl {{.+}} ImportAsUnsafeMethodActuallySafe
// CHECK-IMPORT-AS-UNSAFE: SwiftAttrAttr {{.+}} "safe"
// CHECK-IMPORT-AS-UNSAFE: Dumping ImportAsUnsafeAlreadyAnnotated:
// CHECK-IMPORT-AS-UNSAFE: FunctionDecl {{.+}} ImportAsUnsafeAlreadyAnnotated
// CHECK-IMPORT-AS-UNSAFE: SwiftVersionedAdditionAttr {{.+}} IsReplacedByActive
// CHECK-IMPORT-AS-UNSAFE: SwiftAttrAttr {{.+}} "unsafe"
// CHECK-IMPORT-AS-UNSAFE-EMPTY:
// CHECK-IMPORT-AS-UNSAFE: Dumping ImportAsUnsafeVersioned:
// CHECK-IMPORT-AS-UNSAFE: FunctionDecl {{.+}} ImportAsUnsafeVersioned
// CHECK-IMPORT-AS-UNSAFE: SwiftVersionedAdditionAttr {{.+}} 3.0
// CHECK-IMPORT-AS-UNSAFE: SwiftAttrAttr {{.+}} "unsafe"
// CHECK-IMPORT-AS-UNSAFE: SwiftVersionedAdditionAttr {{.+}} 6.0
// CHECK-IMPORT-AS-UNSAFE: SwiftAttrAttr {{.+}} "safe"