From 1fd3a7966a2873ebc482a569f4ce04ffdb10ed99 Mon Sep 17 00:00:00 2001 From: Gabor Horvath Date: Mon, 8 Sep 2025 17:21:45 +0100 Subject: [PATCH] [APINotes] Support annotating safety of APIs Swift supports a strictly memory safe mode where every expressions using an unsafe feature or API need to be annotatated with the unsafe effect. This sometimes requires adding safety annotations to the foreign APIs. This PR adds support to inject such safety annotations. --- clang/docs/APINotes.rst | 14 ++++++++ clang/include/clang/APINotes/Types.h | 28 +++++++++++++-- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 13 ++++--- clang/lib/APINotes/APINotesTypes.cpp | 15 ++++++++ clang/lib/APINotes/APINotesWriter.cpp | 6 ++++ clang/lib/APINotes/APINotesYAMLCompiler.cpp | 34 +++++++++++++++++++ clang/lib/Sema/SemaAPINotes.cpp | 24 +++++++++++++ .../Inputs/Headers/SwiftImportAs.apinotes | 21 ++++++++++++ .../APINotes/Inputs/Headers/SwiftImportAs.h | 11 ++++++ clang/test/APINotes/swift-import-as.cpp | 30 ++++++++++++++++ 11 files changed, 190 insertions(+), 8 deletions(-) diff --git a/clang/docs/APINotes.rst b/clang/docs/APINotes.rst index dec4b186ff72f..e142cfa62e5a2 100644 --- a/clang/docs/APINotes.rst +++ b/clang/docs/APINotes.rst @@ -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 diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 71625715bda19..fb2b91a3e1750 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -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 @@ class CommonEntityInfo { 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 isSwiftPrivate() const { return SwiftPrivateSpecified ? std::optional(SwiftPrivate) @@ -89,6 +97,17 @@ class CommonEntityInfo { SwiftPrivate = Private.value_or(0); } + std::optional getSwiftSafety() const { + return SwiftSafetyAudited ? std::optional( + static_cast(SwiftSafety)) + : std::nullopt; + } + + void setSwiftSafety(SwiftSafetyKind Safety) { + SwiftSafetyAudited = 1; + SwiftSafety = static_cast(Safety); + } + friend bool operator==(const CommonEntityInfo &, const CommonEntityInfo &); CommonEntityInfo &operator|=(const CommonEntityInfo &RHS) { @@ -108,6 +127,9 @@ class CommonEntityInfo { 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, diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 69d180e7b3eb5..bb423ccb2bfaf 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 = 37; // SwiftDestroyOp +const uint16_t VERSION_MINOR = 38; // SwiftSafety 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 573356f97ff73..7f9bb5f12cda7 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -94,11 +94,14 @@ class VersionedTableInfo { /// 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((UnavailableBits >> 3) & 0x01)); + uint8_t EncodedBits = *Data++; + Info.Unavailable = (EncodedBits >> 1) & 0x01; + Info.UnavailableInSwift = EncodedBits & 0x01; + if ((EncodedBits >> 2) & 0x01) + Info.setSwiftPrivate(static_cast((EncodedBits >> 3) & 0x01)); + if ((EncodedBits >> 4) & 0x01) + Info.setSwiftSafety( + static_cast((EncodedBits >> 5) & 0x03)); unsigned MsgLength = endian::readNext(Data); diff --git a/clang/lib/APINotes/APINotesTypes.cpp b/clang/lib/APINotes/APINotesTypes.cpp index f726faa832bcc..bff4be104c6c8 100644 --- a/clang/lib/APINotes/APINotesTypes.cpp +++ b/clang/lib/APINotes/APINotesTypes.cpp @@ -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'; diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index cf88d118d0979..47ed93a567c0e 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -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(*safety); + payload <<= 1; + payload |= 0x01; + } + payload <<= 2; if (auto swiftPrivate = CEI.isSwiftPrivate()) { payload |= 0x01; if (*swiftPrivate) diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index a91a1eea03d81..8e91d48b4ba62 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -29,6 +29,18 @@ using namespace clang; using namespace api_notes; +namespace llvm { +namespace yaml { +template <> struct ScalarEnumerationTraits { + 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 MethodsSeq; @@ -199,6 +212,7 @@ template <> struct MappingTraits { 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 SwiftImportAsAccessors; StringRef Type; + SwiftSafetyKind SafetyKind = SwiftSafetyKind::None; }; typedef std::vector PropertiesSeq; @@ -235,6 +250,7 @@ template <> struct MappingTraits { 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 SwiftConformance; MethodsSeq Methods; PropertiesSeq Properties; + SwiftSafetyKind SafetyKind = SwiftSafetyKind::None; }; typedef std::vector ClassesSeq; @@ -279,6 +296,7 @@ template <> struct MappingTraits { 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 FunctionsSeq; @@ -321,6 +340,7 @@ template <> struct MappingTraits { 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 SwiftPrivate; StringRef SwiftName; StringRef Type; + SwiftSafetyKind SafetyKind = SwiftSafetyKind::None; }; typedef std::vector GlobalVariablesSeq; @@ -353,6 +374,7 @@ template <> struct MappingTraits { 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 SwiftPrivate; StringRef SwiftName; + SwiftSafetyKind SafetyKind = SwiftSafetyKind::None; }; typedef std::vector EnumConstantsSeq; @@ -381,6 +404,7 @@ template <> struct MappingTraits { 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 SwiftPrivate; StringRef SwiftName; StringRef Type; + SwiftSafetyKind SafetyKind = SwiftSafetyKind::None; }; typedef std::vector FieldsSeq; @@ -443,6 +468,7 @@ template <> struct MappingTraits { 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 EnumConvenienceKind; std::optional SwiftCopyable; std::optional SwiftEscapable; + SwiftSafetyKind SafetyKind = SwiftSafetyKind::None; FunctionsSeq Methods; FieldsSeq Fields; @@ -515,6 +542,7 @@ template <> struct MappingTraits { 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 NSErrorDomain; std::optional SwiftType; std::optional SwiftConformance; + const SwiftSafetyKind SafetyKind = SwiftSafetyKind::None; }; typedef std::vector TypedefsSeq; @@ -602,6 +631,7 @@ struct Namespace { StringRef SwiftName; std::optional SwiftPrivate; TopLevelItems Items; + const SwiftSafetyKind SafetyKind = SwiftSafetyKind::None; }; } // namespace @@ -797,6 +827,8 @@ class YAMLConverter { 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 @@ class YAMLConverter { 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 This; convertParams(Function.Params, FI, This); diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 4cc1b76264340..99a29add8211d 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -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( + 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(attr)) { + if (swiftAttr->getAttribute() == "safe" || + swiftAttr->getAttribute() == "unsafe") + return true; + } + return false; + }); + }); + } + // swift_name if (!Info.SwiftName.empty()) { handleAPINotedAttribute( diff --git a/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes b/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes index 15c806842d08f..7e9cac32df3a5 100644 --- a/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes +++ b/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes @@ -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 diff --git a/clang/test/APINotes/Inputs/Headers/SwiftImportAs.h b/clang/test/APINotes/Inputs/Headers/SwiftImportAs.h index 978b4fbbb3b00..272e3865ab2ba 100644 --- a/clang/test/APINotes/Inputs/Headers/SwiftImportAs.h +++ b/clang/test/APINotes/Inputs/Headers/SwiftImportAs.h @@ -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(); diff --git a/clang/test/APINotes/swift-import-as.cpp b/clang/test/APINotes/swift-import-as.cpp index f5d08df7c6a1b..20d38b5a0968d 100644 --- a/clang/test/APINotes/swift-import-as.cpp +++ b/clang/test/APINotes/swift-import-as.cpp @@ -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 @@ -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"