From a980452e593bff7b2169d7018c82fd90571b582d Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 4 Apr 2024 21:25:10 +0200 Subject: [PATCH 1/4] Add the experimental attribute `@sensitive` for struct declarations The attribute declares that a struct contains "sensitive" data. It enforces that the contents of such a struct value is zeroed out at the end of its lifetime. In other words: the content of such a value is not observable in memory after the value's lifetime. Also add an experimental feature `Sensitive` with which the attribute can be enabled. --- include/swift/AST/DeclAttr.def | 9 +++++++++ include/swift/AST/DiagnosticsSema.def | 4 ++++ include/swift/Basic/Features.def | 2 ++ include/swift/SIL/SILType.h | 8 +++++++- lib/AST/FeatureSet.cpp | 4 ++++ lib/ASTGen/Sources/ASTGen/DeclAttrs.swift | 2 ++ lib/Sema/TypeCheckAttr.cpp | 9 +++++++++ lib/Sema/TypeCheckDeclOverride.cpp | 1 + 8 files changed, 38 insertions(+), 1 deletion(-) diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index caaa98459cad8..59fbc89343bd1 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -494,6 +494,15 @@ DECL_ATTR_ALIAS(_disallowFeatureSuppression, AllowFeatureSuppression) SIMPLE_DECL_ATTR(_preInverseGenerics, PreInverseGenerics, OnAbstractFunction | OnSubscript | OnVar | OnExtension | UserInaccessible | ABIBreakingToAdd | ABIBreakingToRemove | APIStableToAdd | APIStableToRemove, 158) + +// Declares that a struct contains "sensitive" data. It enforces that the contents of such a struct value +// is zeroed out at the end of its lifetime. In other words: the content of such a value is not observable +// in memory after the value's lifetime. +// TODO: enable @sensitive also for other nominal types than structs, e.g. for enums +SIMPLE_DECL_ATTR(sensitive, Sensitive, + OnStruct | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove, + 159) + LAST_DECL_ATTR(PreInverseGenerics) #undef DECL_ATTR_ALIAS diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 29a9b2f775e23..771cd8f75057f 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -1983,6 +1983,10 @@ ERROR(attr_static_exclusive_only_mutating,none, ERROR(attr_extractConstantsFromMembers_experimental,none, "@extractConstantsFromMembers requires '-enable-experimental-feature ExtractConstantsFromMembers'", ()) +// @sensitive +ERROR(attr_sensitive_experimental,none, + "@sensitive requires '-enable-experimental-feature Sensitive'", ()) + ERROR(c_func_variadic, none, "cannot declare variadic argument %0 in %kind1", (DeclName, const ValueDecl *)) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 82e9ac4273373..f6415aa5b23ee 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -384,6 +384,8 @@ EXPERIMENTAL_FEATURE(ObjCImplementation, true) // Enable @implementation on @_cdecl functions. EXPERIMENTAL_FEATURE(CImplementation, true) +// Enable @sensitive attribute. +EXPERIMENTAL_FEATURE(Sensitive, true) #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE diff --git a/include/swift/SIL/SILType.h b/include/swift/SIL/SILType.h index ec32b913c3f4d..379271406905f 100644 --- a/include/swift/SIL/SILType.h +++ b/include/swift/SIL/SILType.h @@ -481,7 +481,13 @@ class SILType { bool hasParameterizedExistential() const { return getASTType()->hasParameterizedExistential(); } - + + bool isSensitive() const { + if (auto *nom = getNominalOrBoundGenericNominal()) + return nom->getAttrs().hasAttribute(); + return false; + } + /// Returns the representation used by an existential type. If the concrete /// type is provided, this may return a specialized representation kind that /// can be used for that type. Otherwise, returns the most general diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index eabcc42e76884..471ce29a44338 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -694,6 +694,10 @@ static bool usesFeatureGlobalActorIsolatedTypesUsability(Decl *decl) { UNINTERESTING_FEATURE(ObjCImplementation) UNINTERESTING_FEATURE(CImplementation) +static bool usesFeatureSensitive(Decl *decl) { + return decl->getAttrs().hasAttribute(); +} + // ---------------------------------------------------------------------------- // MARK: - FeatureSet // ---------------------------------------------------------------------------- diff --git a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift index b970e7e511688..367f4e2743503 100644 --- a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift @@ -167,6 +167,8 @@ extension ASTGenVisitor { return self.generateSectionAttr(attribute: node)?.asDeclAttribute case .semantics: return self.generateSemanticsAttr(attribute: node)?.asDeclAttribute + case .sensitive: + fatalError("unimplemented") case .silGenName: return self.generateSILGenNameAttr(attribute: node)?.asDeclAttribute case .specialize: diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 2a6fff72c2a89..c78b796adfc73 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -343,6 +343,8 @@ class AttributeChecker : public AttributeVisitor { void visitExtractConstantsFromMembersAttr(ExtractConstantsFromMembersAttr *attr); + void visitSensitiveAttr(SensitiveAttr *attr); + void visitUnavailableFromAsyncAttr(UnavailableFromAsyncAttr *attr); void visitUnsafeInheritExecutorAttr(UnsafeInheritExecutorAttr *attr); @@ -451,6 +453,13 @@ void AttributeChecker::visitExtractConstantsFromMembersAttr(ExtractConstantsFrom } } +void AttributeChecker::visitSensitiveAttr(SensitiveAttr *attr) { + if (!Ctx.LangOpts.hasFeature(Feature::Sensitive)) { + diagnoseAndRemoveAttr(attr, + diag::attr_sensitive_experimental); + } +} + void AttributeChecker::visitTransparentAttr(TransparentAttr *attr) { DeclContext *dc = D->getDeclContext(); // Protocol declarations cannot be transparent. diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index c8760522eaba1..b5e9782eb0bc8 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1627,6 +1627,7 @@ namespace { UNINTERESTING_ATTR(CompilerInitialized) UNINTERESTING_ATTR(AlwaysEmitConformanceMetadata) UNINTERESTING_ATTR(ExtractConstantsFromMembers) + UNINTERESTING_ATTR(Sensitive) UNINTERESTING_ATTR(EagerMove) UNINTERESTING_ATTR(NoEagerMove) From 3097cceba0588884b4840919670ea8e58cefb42d Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 4 Apr 2024 21:26:50 +0200 Subject: [PATCH 2/4] clang importer: import `__attribute__((swift_attr("sensitive")))` on C structs as `@sensitive` attributes --- lib/ClangImporter/ImportDecl.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index e4d94f74d692a..b2a83bf8bc336 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -8075,6 +8075,14 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) { continue; } + if (swiftAttr->getAttribute() == "sensitive") { + if (!SwiftContext.LangOpts.hasFeature(Feature::Sensitive)) + continue; + auto attr = new (SwiftContext) SensitiveAttr(/*implicit=*/true); + MappedDecl->getAttrs().add(attr); + continue; + } + // Dig out a buffer with the attribute text. unsigned bufferID = getClangSwiftAttrSourceBuffer( swiftAttr->getAttribute()); From ce33d47a4c3994052e57f8d6eaccda2700225c77 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 5 Apr 2024 16:48:18 +0200 Subject: [PATCH 3/4] stdlib: add the `swift_clearSensitive` runtime function --- include/swift/AST/FeatureAvailability.def | 1 + include/swift/Runtime/Heap.h | 3 +++ include/swift/Runtime/RuntimeFunctions.def | 7 +++++++ lib/IRGen/IRGenModule.cpp | 8 ++++++++ stdlib/public/core/EmbeddedRuntime.swift | 13 +++++++++++++ stdlib/public/runtime/Heap.cpp | 8 ++++++++ test/abi/macOS/arm64/stdlib.swift | 1 + test/abi/macOS/x86_64/stdlib-asserts.swift | 2 ++ 8 files changed, 43 insertions(+) diff --git a/include/swift/AST/FeatureAvailability.def b/include/swift/AST/FeatureAvailability.def index c0a32d3851b58..64f304a7f2931 100644 --- a/include/swift/AST/FeatureAvailability.def +++ b/include/swift/AST/FeatureAvailability.def @@ -74,6 +74,7 @@ FEATURE(IsolatedAny, (5, 11)) FEATURE(TaskExecutor, FUTURE) FEATURE(Differentiation, FUTURE) FEATURE(InitRawStructMetadata, FUTURE) +FEATURE(ClearSensitive, FUTURE) #undef FEATURE #undef FUTURE diff --git a/include/swift/Runtime/Heap.h b/include/swift/Runtime/Heap.h index ea3e8e0667d7d..bad411ca4efb3 100644 --- a/include/swift/Runtime/Heap.h +++ b/include/swift/Runtime/Heap.h @@ -42,6 +42,9 @@ void *swift_slowAllocTyped(size_t bytes, size_t alignMask, MallocTypeId typeId); SWIFT_RUNTIME_EXPORT void swift_slowDealloc(void *ptr, size_t bytes, size_t alignMask); +SWIFT_RUNTIME_EXPORT +void swift_clearSensitive(void *ptr, size_t bytes); + /// Allocate and construct an instance of type \c T. /// /// \param args The arguments to pass to the constructor for \c T. diff --git a/include/swift/Runtime/RuntimeFunctions.def b/include/swift/Runtime/RuntimeFunctions.def index f3f62f5f7750b..39f4ecd590d21 100644 --- a/include/swift/Runtime/RuntimeFunctions.def +++ b/include/swift/Runtime/RuntimeFunctions.def @@ -2825,6 +2825,13 @@ FUNCTION(ExceptionPersonality, EFFECT(NoEffect), UNKNOWN_MEMEFFECTS) +FUNCTION(ClearSensitive, swift_clearSensitive, C_CC, ClearSensitiveAvailability, + RETURNS(VoidTy), + ARGS(PtrTy, SizeTy), + ATTRS(NoUnwind), + EFFECT(NoEffect), + UNKNOWN_MEMEFFECTS) + #undef RETURNS #undef ARGS #undef ATTRS diff --git a/lib/IRGen/IRGenModule.cpp b/lib/IRGen/IRGenModule.cpp index 40938503a800c..1a4de14758938 100644 --- a/lib/IRGen/IRGenModule.cpp +++ b/lib/IRGen/IRGenModule.cpp @@ -969,6 +969,14 @@ namespace RuntimeConstants { return RuntimeAvailability::AlwaysAvailable; } + RuntimeAvailability ClearSensitiveAvailability(ASTContext &Context) { + auto featureAvailability = Context.getClearSensitiveAvailability(); + if (!isDeploymentAvailabilityContainedIn(Context, featureAvailability)) { + return RuntimeAvailability::ConditionallyAvailable; + } + return RuntimeAvailability::AlwaysAvailable; + } + } // namespace RuntimeConstants // We don't use enough attributes to justify generalizing the diff --git a/stdlib/public/core/EmbeddedRuntime.swift b/stdlib/public/core/EmbeddedRuntime.swift index 888e19e9c27dd..1f9cd50627eaa 100644 --- a/stdlib/public/core/EmbeddedRuntime.swift +++ b/stdlib/public/core/EmbeddedRuntime.swift @@ -338,3 +338,16 @@ func arc4random_buf(buf: UnsafeMutableRawPointer, nbytes: Int) public func swift_stdlib_random(_ buf: UnsafeMutableRawPointer, _ nbytes: Int) { arc4random_buf(buf: buf, nbytes: nbytes) } + +@_cdecl("swift_clearSensitive") +@inline(never) +public func swift_clearSensitive(buf: UnsafeMutableRawPointer, nbytes: Int) { + // TODO: use memset_s if available + // Though, it shouldn't make too much difference because the `@inline(never)` should prevent + // the optimizer from removing the loop below. + let bytePtr = buf.assumingMemoryBound(to: UInt8.self) + for i in 0.. #include +#include #if defined(__APPLE__) && SWIFT_STDLIB_HAS_DARWIN_LIBMALLOC #include "swift/Basic/Lazy.h" #include @@ -146,3 +147,10 @@ static void swift_slowDeallocImpl(void *ptr, size_t alignMask) { void swift::swift_slowDealloc(void *ptr, size_t bytes, size_t alignMask) { swift_slowDeallocImpl(ptr, alignMask); } + +void swift::swift_clearSensitive(void *ptr, size_t bytes) { + // TODO: use memset_s if available + // Though, it shouldn't make too much difference because the optimizer cannot remove + // the following memset without inlining this library function. + memset(ptr, 0, bytes); +} diff --git a/test/abi/macOS/arm64/stdlib.swift b/test/abi/macOS/arm64/stdlib.swift index adf45eb569c58..8b56d6a76139c 100644 --- a/test/abi/macOS/arm64/stdlib.swift +++ b/test/abi/macOS/arm64/stdlib.swift @@ -562,6 +562,7 @@ Added: __swift_exceptionPersonality Added: _swift_willThrowTypedImpl Added: __swift_willThrowTypedImpl Added: __swift_enableSwizzlingOfAllocationAndRefCountingFunctions_forInstrumentsOnly +Added: _swift_clearSensitive // Runtime bincompat functions for Concurrency runtime to detect legacy mode Added: _swift_bincompat_useLegacyNonCrashingExecutorChecks diff --git a/test/abi/macOS/x86_64/stdlib-asserts.swift b/test/abi/macOS/x86_64/stdlib-asserts.swift index 0cef137688679..b28f765f10ec8 100644 --- a/test/abi/macOS/x86_64/stdlib-asserts.swift +++ b/test/abi/macOS/x86_64/stdlib-asserts.swift @@ -60,3 +60,5 @@ Added: _OBJC_CLASS_$__TtCs20__StaticArrayStorage Added: _OBJC_METACLASS_$__TtCs20__StaticArrayStorage // Runtime Symbols +Added: _swift_clearSensitive + From 1b1d5ed020c43f4412456741155ef3c8dde5b9a0 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 4 Apr 2024 21:29:53 +0200 Subject: [PATCH 4/4] IRGen: support the `@sensitive` attribute Call `swift_clearSensitive` after destroying or taking "sensitive" struct types. Also, support calling C-functions with "sensitive" parameters or return values. In SIL, sensitive types are address-only and so are sensitive parameters/return values. Though, (small) sensitive C-structs are passed directly to/from C-functions. We need re-abstract such parameter and return values for C-functions. --- include/swift/Runtime/RuntimeFunctions.def | 7 + lib/IRGen/CallEmission.h | 16 +++ lib/IRGen/FixedTypeInfo.h | 3 +- lib/IRGen/GenBuiltin.cpp | 3 +- lib/IRGen/GenCall.cpp | 92 ++++++++++--- lib/IRGen/GenClangType.cpp | 5 +- lib/IRGen/GenEnum.cpp | 27 ++-- lib/IRGen/GenEnum.h | 3 +- lib/IRGen/GenExistential.cpp | 6 +- lib/IRGen/GenFunc.cpp | 3 +- lib/IRGen/GenHeap.cpp | 2 +- lib/IRGen/GenInit.cpp | 4 + lib/IRGen/GenKeyPath.cpp | 4 +- lib/IRGen/GenRecord.h | 33 +++-- lib/IRGen/GenStruct.cpp | 17 +-- lib/IRGen/GenType.cpp | 11 +- lib/IRGen/GenValueWitness.cpp | 3 +- lib/IRGen/IRGenFunction.cpp | 18 ++- lib/IRGen/IRGenFunction.h | 2 + lib/IRGen/IRGenSIL.cpp | 15 ++- lib/IRGen/IndirectTypeInfo.h | 6 +- lib/IRGen/NonFixedTypeInfo.h | 3 +- lib/IRGen/Outlining.cpp | 2 +- lib/IRGen/ResilientTypeInfo.h | 3 +- lib/IRGen/StructLayout.cpp | 18 ++- lib/IRGen/TypeInfo.h | 3 +- lib/IRGen/TypeLayout.cpp | 6 +- lib/SIL/IR/SILFunctionType.cpp | 3 + lib/SIL/IR/TypeLowering.cpp | 7 + test/IRGen/Inputs/sensitive.h | 25 ++++ test/IRGen/Inputs/sensitive_c_functions.c | 47 +++++++ test/IRGen/sensitive.sil | 47 +++++++ test/IRGen/sensitive.swift | 150 +++++++++++++++++++++ test/embedded/sensitive.swift | 59 ++++++++ 34 files changed, 571 insertions(+), 82 deletions(-) create mode 100644 test/IRGen/Inputs/sensitive.h create mode 100644 test/IRGen/Inputs/sensitive_c_functions.c create mode 100644 test/IRGen/sensitive.sil create mode 100644 test/IRGen/sensitive.swift create mode 100644 test/embedded/sensitive.swift diff --git a/include/swift/Runtime/RuntimeFunctions.def b/include/swift/Runtime/RuntimeFunctions.def index 39f4ecd590d21..435f45f841138 100644 --- a/include/swift/Runtime/RuntimeFunctions.def +++ b/include/swift/Runtime/RuntimeFunctions.def @@ -2832,6 +2832,13 @@ FUNCTION(ClearSensitive, swift_clearSensitive, C_CC, ClearSensitiveAvailability, EFFECT(NoEffect), UNKNOWN_MEMEFFECTS) +FUNCTION(MemsetS, memset_s, C_CC, AlwaysAvailable, + RETURNS(Int32Ty), + ARGS(PtrTy, SizeTy, Int32Ty, SizeTy), + ATTRS(NoUnwind), + EFFECT(NoEffect), + UNKNOWN_MEMEFFECTS) + #undef RETURNS #undef ARGS #undef ATTRS diff --git a/lib/IRGen/CallEmission.h b/lib/IRGen/CallEmission.h index 946b596c8f116..5f189f4c4c705 100644 --- a/lib/IRGen/CallEmission.h +++ b/lib/IRGen/CallEmission.h @@ -57,6 +57,15 @@ class CallEmission { /// The function we're going to call. Callee CurCallee; + /// Only valid if the SIL function has indirect return values. + /// If the function has multiple indirect return values, this is the address + /// of the first indirect return value. + Address indirectReturnAddress; + + /// For C-functions: true if the return is indirect in SIL, but direct for a C-function. + /// That can happen for "sensitive" structs. + bool convertDirectToIndirectReturn = false; + unsigned LastArgWritten; /// Whether this is a coroutine invocation. @@ -94,6 +103,11 @@ class CallEmission { virtual llvm::CallBase *createCall(const FunctionPointer &fn, ArrayRef args) = 0; + void externalizeArguments(IRGenFunction &IGF, const Callee &callee, + Explosion &in, Explosion &out, + TemporarySet &temporaries, + bool isOutlined); + CallEmission(IRGenFunction &IGF, llvm::Value *selfValue, Callee &&callee) : IGF(IGF), selfValue(selfValue), CurCallee(std::move(callee)) {} @@ -119,6 +133,8 @@ class CallEmission { void addFnAttribute(llvm::Attribute::AttrKind Attr); + void setIndirectReturnAddress(Address addr) { indirectReturnAddress = addr; } + void addParamAttribute(unsigned ParamIndex, llvm::Attribute::AttrKind Attr); void emitToMemory(Address addr, const LoadableTypeInfo &substResultTI, diff --git a/lib/IRGen/FixedTypeInfo.h b/lib/IRGen/FixedTypeInfo.h index 6dc418c68faad..0786ea7a4cbad 100644 --- a/lib/IRGen/FixedTypeInfo.h +++ b/lib/IRGen/FixedTypeInfo.h @@ -91,7 +91,8 @@ class FixedTypeInfo : public TypeInfo { // We can give these reasonable default implementations. void initializeWithTake(IRGenFunction &IGF, Address destAddr, Address srcAddr, - SILType T, bool isOutlined) const override; + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override; llvm::Value *getSize(IRGenFunction &IGF, SILType T) const override; llvm::Value *getAlignmentMask(IRGenFunction &IGF, SILType T) const override; diff --git a/lib/IRGen/GenBuiltin.cpp b/lib/IRGen/GenBuiltin.cpp index 6cfcb74e94eee..00aedd0032a17 100644 --- a/lib/IRGen/GenBuiltin.cpp +++ b/lib/IRGen/GenBuiltin.cpp @@ -1229,7 +1229,8 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin, case BuiltinValueKind::TakeArrayNoAlias: case BuiltinValueKind::TakeArrayFrontToBack: case BuiltinValueKind::TakeArrayBackToFront: - elemTI.initializeWithTake(IGF, destAddr, srcAddr, elemTy, isOutlined); + elemTI.initializeWithTake(IGF, destAddr, srcAddr, elemTy, isOutlined, + /*zeroizeIfSensitive=*/ true); break; case BuiltinValueKind::AssignCopyArrayNoAlias: case BuiltinValueKind::AssignCopyArrayFrontToBack: diff --git a/lib/IRGen/GenCall.cpp b/lib/IRGen/GenCall.cpp index 2b713ad88ae89..b06534faec1bf 100644 --- a/lib/IRGen/GenCall.cpp +++ b/lib/IRGen/GenCall.cpp @@ -1474,7 +1474,7 @@ void SignatureExpansion::expandExternalSignatureTypes() { bool formalIndirectResult = FnType->getNumResults() > 0 && FnType->getSingleResult().isFormalIndirect(); assert( - (cxxCtorDecl || !formalIndirectResult || returnInfo.isIndirect()) && + (cxxCtorDecl || !formalIndirectResult || returnInfo.isIndirect() || SILResultTy.isSensitive()) && "swift and clang disagree on whether the result is returned indirectly"); #endif @@ -2392,10 +2392,6 @@ std::pair irgen::getAsyncFunctionAndSize( return {fn, size}; } -static void externalizeArguments(IRGenFunction &IGF, const Callee &callee, - Explosion &in, Explosion &out, - TemporarySet &temporaries, bool isOutlined); - namespace { class SyncCallEmission final : public CallEmission { @@ -2634,11 +2630,19 @@ class SyncCallEmission final : public CallEmission { return; } - // Get the natural IR type in the body of the function that makes - // the call. This may be different than the IR type returned by the - // call itself due to ABI type coercion. - auto resultType = - fnConv.getSILResultType(IGF.IGM.getMaximalTypeExpansionContext()); + SILType resultType; + if (convertDirectToIndirectReturn) { + resultType = SILType::getPrimitiveObjectType( + origFnType->getSingleResult().getReturnValueType( + IGF.IGM.getSILModule(), origFnType, TypeExpansionContext::minimal())); + } else { + // Get the natural IR type in the body of the function that makes + // the call. This may be different than the IR type returned by the + // call itself due to ABI type coercion. + resultType = + fnConv.getSILResultType(IGF.IGM.getMaximalTypeExpansionContext()); + } + auto &nativeSchema = IGF.IGM.getTypeInfo(resultType).nativeReturnValueSchema(IGF.IGM); // For ABI reasons the result type of the call might not actually match the @@ -2654,6 +2658,10 @@ class SyncCallEmission final : public CallEmission { result = IGF.coerceValue(result, expectedNativeResultType, IGF.IGM.DataLayout); } + if (convertDirectToIndirectReturn) { + IGF.Builder.CreateStore(result, indirectReturnAddress); + return; + } // Gather the values. Explosion nativeExplosion; @@ -3983,7 +3991,7 @@ Address getForwardableAlloca(const TypeInfo &TI, bool isForwardableArgument, return TI.getAddressForPointer(alloca); } -static void externalizeArguments(IRGenFunction &IGF, const Callee &callee, +void CallEmission::externalizeArguments(IRGenFunction &IGF, const Callee &callee, Explosion &in, Explosion &out, TemporarySet &temporaries, bool isOutlined) { @@ -4020,11 +4028,26 @@ static void externalizeArguments(IRGenFunction &IGF, const Callee &callee, bool formalIndirectResult = fnType->getNumResults() > 0 && fnType->getSingleResult().isFormalIndirect(); - - // If clang returns directly and swift returns indirectly, this must be a c++ - // constructor call. In that case, skip the "self" param. - if (!FI.getReturnInfo().isIndirect() && formalIndirectResult) - firstParam += 1; + if (!FI.getReturnInfo().isIndirect() && formalIndirectResult) { + // clang returns directly and swift returns indirectly + + SILType returnTy = SILType::getPrimitiveObjectType( + fnType->getSingleResult().getReturnValueType( + IGF.IGM.getSILModule(), fnType, TypeExpansionContext::minimal())); + + if (returnTy.isSensitive()) { + // Sensitive return types are represented as indirect return value in SIL, + // but are returned as values (if small) in LLVM IR. + assert(out.size() == 1 && "expect a single address for the return value"); + llvm::Value *returnAddr = out.claimNext(); + out.reset(); + assert(returnAddr == indirectReturnAddress.getAddress()); + convertDirectToIndirectReturn = true; + } else { + // This must be a constructor call. In that case, skip the "self" param. + firstParam += 1; + } + } for (unsigned i = firstParam; i != paramEnd; ++i) { auto clangParamTy = FI.arg_begin()[i].type; @@ -4039,8 +4062,9 @@ static void externalizeArguments(IRGenFunction &IGF, const Callee &callee, if (auto *padType = AI.getPaddingType()) out.add(llvm::UndefValue::get(padType)); + const SILParameterInfo ¶mInfo = params[i - firstParam]; SILType paramType = silConv.getSILType( - params[i - firstParam], IGF.IGM.getMaximalTypeExpansionContext()); + paramInfo, IGF.IGM.getMaximalTypeExpansionContext()); bool isForwardableArgument = IGF.isForwardableArgument(i - firstParam); @@ -4060,6 +4084,35 @@ static void externalizeArguments(IRGenFunction &IGF, const Callee &callee, continue; } + bool passIndirectToDirect = paramInfo.isIndirectInGuaranteed() && paramType.isSensitive(); + if (passIndirectToDirect) { + llvm::Value *ptr = in.claimNext(); + + if (AI.getKind() == clang::CodeGen::ABIArgInfo::Indirect) { + // It's a large struct which is also passed indirectl in LLVM IR. + // The C function (= the callee) is allowed to modify the memory used + // for passing arguments, therefore we need to copy the argument value + // to a temporary. + // TODO: avoid the temporary if the SIL parameter value in memory is + // not used anymore after the call. + auto &ti = cast(IGF.getTypeInfo(paramType)); + auto temp = ti.allocateStack(IGF, paramType, "indirect-temporary"); + Address tempAddr = temp.getAddress(); + temporaries.add({temp, paramType}); + Address paramAddr = ti.getAddressForPointer(ptr); + ti.initializeWithCopy(IGF, tempAddr, paramAddr, paramType, isOutlined); + + out.add(tempAddr.getAddress()); + continue; + } + + auto &ti = cast(IGF.getTypeInfo(paramType)); + Explosion loadedValue; + ti.loadAsCopy(IGF, ti.getAddressForPointer(ptr), loadedValue); + in.transferInto(loadedValue, in.size()); + in = std::move(loadedValue); + } + switch (AI.getKind()) { case clang::CodeGen::ABIArgInfo::Extend: { bool signExt = clangParamTy->hasSignedIntegerRepresentation(); @@ -4072,7 +4125,7 @@ static void externalizeArguments(IRGenFunction &IGF, const Callee &callee, auto toTy = AI.getCoerceToType(); // Indirect parameters are bridged as Clang pointer types. - if (silConv.isSILIndirect(params[i - firstParam])) { + if (silConv.isSILIndirect(params[i - firstParam]) && !passIndirectToDirect) { assert(paramType.isAddress() && "SIL type is not an address?"); auto addr = in.claimNext(); @@ -4111,7 +4164,8 @@ static void externalizeArguments(IRGenFunction &IGF, const Callee &callee, // preceeding the apply. if (isForwardableArgument && forwardFromAddr.isValid()) { ti.initializeWithTake(IGF, addr, forwardFromAddr, - paramType.getAddressType(), isOutlined); + paramType.getAddressType(), isOutlined, + /*zeroizeIfSensitive=*/ true); (void)in.claim(ti.getSchema().size()); } else { ti.initialize(IGF, in, addr, isOutlined); diff --git a/lib/IRGen/GenClangType.cpp b/lib/IRGen/GenClangType.cpp index 11e7fb34c4833..70a0551a58992 100644 --- a/lib/IRGen/GenClangType.cpp +++ b/lib/IRGen/GenClangType.cpp @@ -50,7 +50,10 @@ clang::CanQualType IRGenModule::getClangType(SILParameterInfo params, if (params.isIndirectMutating()) { return getClangASTContext().getPointerType(clangType); } - if (params.isFormalIndirect()) { + if (params.isFormalIndirect() && + // Sensitive return types are represented as indirect return value in SIL, + // but are returned as values (if small) in LLVM IR. + !paramTy.isSensitive()) { auto constTy = getClangASTContext().getCanonicalType(clangType.withConst()); return getClangASTContext().getPointerType(constTy); diff --git a/lib/IRGen/GenEnum.cpp b/lib/IRGen/GenEnum.cpp index b518af4a81e15..f8e1c1de0a425 100644 --- a/lib/IRGen/GenEnum.cpp +++ b/lib/IRGen/GenEnum.cpp @@ -187,7 +187,7 @@ void EnumImplStrategy::initializeFromParams(IRGenFunction &IGF, if (TIK >= Loadable) return initialize(IGF, params, dest, isOutlined); Address src = TI->getAddressForPointer(params.claimNext()); - TI->initializeWithTake(IGF, dest, src, T, isOutlined); + TI->initializeWithTake(IGF, dest, src, T, isOutlined, /*zeroizeIfSensitive=*/ true); } bool EnumImplStrategy::isReflectable() const { return true; } @@ -619,7 +619,8 @@ namespace { } void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { if (!getSingleton()) return; if (!ElementsAreABIAccessible) { emitInitializeWithTakeCall(IGF, T, dest, src); @@ -627,7 +628,7 @@ namespace { dest = getSingletonAddress(IGF, dest); src = getSingletonAddress(IGF, src); getSingleton()->initializeWithTake( - IGF, dest, src, getSingletonType(IGF.IGM, T), isOutlined); + IGF, dest, src, getSingletonType(IGF.IGM, T), isOutlined, zeroizeIfSensitive); } else { callOutlinedCopy(IGF, dest, src, T, IsInitialization, IsTake); } @@ -1110,7 +1111,8 @@ namespace { void emitScalarFixLifetime(IRGenFunction &IGF, llvm::Value *value) const {} void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { // No-payload enums are always POD, so we can always initialize by // primitive copy. llvm::Value *val = IGF.Builder.CreateLoad(src); @@ -3277,7 +3279,8 @@ namespace { } void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { if (!ElementsAreABIAccessible) { emitInitializeWithTakeCall(IGF, T, dest, src); } else if (isOutlined || T.hasParameterizedExistential()) { @@ -5109,7 +5112,7 @@ namespace { if (isTake) payloadTI.initializeWithTake(IGF, destData, srcData, PayloadT, - isOutlined); + isOutlined, /*zeroizeIfSensitive=*/ true); else payloadTI.initializeWithCopy(IGF, destData, srcData, PayloadT, isOutlined); @@ -5183,7 +5186,8 @@ namespace { } void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { if (!ElementsAreABIAccessible) { emitInitializeWithTakeCall(IGF, T, dest, src); } else if (isOutlined || T.hasParameterizedExistential()) { @@ -6121,7 +6125,8 @@ namespace { } void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { emitInitializeWithTakeCall(IGF, T, dest, src); } @@ -6571,8 +6576,10 @@ namespace { return Strategy.initializeWithCopy(IGF, dest, src, T, isOutlined); } void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { - return Strategy.initializeWithTake(IGF, dest, src, T, isOutlined); + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { + return Strategy.initializeWithTake(IGF, dest, src, T, isOutlined, + zeroizeIfSensitive); } void collectMetadataForOutlining(OutliningMetadataCollector &collector, SILType T) const override { diff --git a/lib/IRGen/GenEnum.h b/lib/IRGen/GenEnum.h index 14bd5b0ee5b97..210d37a70fa10 100644 --- a/lib/IRGen/GenEnum.h +++ b/lib/IRGen/GenEnum.h @@ -408,7 +408,8 @@ class EnumImplStrategy { virtual void initializeWithCopy(IRGenFunction &IGF, Address dest, Address src, SILType T, bool isOutlined) const = 0; virtual void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const = 0; + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const = 0; virtual void initializeMetadata(IRGenFunction &IGF, llvm::Value *metadata, diff --git a/lib/IRGen/GenExistential.cpp b/lib/IRGen/GenExistential.cpp index c88215d0c4d1b..fc74d2e2da6d7 100644 --- a/lib/IRGen/GenExistential.cpp +++ b/lib/IRGen/GenExistential.cpp @@ -291,7 +291,8 @@ namespace { } void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { if (isOutlined || T.hasLocalArchetype()) { Address destValue = projectValue(IGF, dest); Address srcValue = projectValue(IGF, src); @@ -976,7 +977,8 @@ class OpaqueExistentialTypeInfo final : } void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { if (isOutlined || T.hasLocalArchetype()) { // memcpy the existential container. This is safe because: either the // value is stored inline and is therefore by convention bitwise takable diff --git a/lib/IRGen/GenFunc.cpp b/lib/IRGen/GenFunc.cpp index b91cae426794e..37e4cf848b776 100644 --- a/lib/IRGen/GenFunc.cpp +++ b/lib/IRGen/GenFunc.cpp @@ -2309,7 +2309,8 @@ std::optional irgen::emitFunctionPartialApplication( auto addr = fieldLayout.getType().getAddressForPointer(args.claimNext()); fieldLayout.getType().initializeWithTake(IGF, fieldAddr, addr, - fieldTy, isOutlined); + fieldTy, isOutlined, + /*zeroizeIfSensitive=*/ true); } break; } diff --git a/lib/IRGen/GenHeap.cpp b/lib/IRGen/GenHeap.cpp index ad9bfc50ba2e7..7fd2b51670d35 100644 --- a/lib/IRGen/GenHeap.cpp +++ b/lib/IRGen/GenHeap.cpp @@ -82,7 +82,7 @@ namespace { } \ void initializeWithTake(IRGenFunction &IGF, Address destAddr, \ Address srcAddr, SILType T, \ - bool isOutlined) const override { \ + bool isOutlined, bool zeroizeIfSensitive) const override { \ IGF.emit##Nativeness##Name##TakeInit(destAddr, srcAddr); \ } \ void assignWithCopy(IRGenFunction &IGF, Address destAddr, Address srcAddr, \ diff --git a/lib/IRGen/GenInit.cpp b/lib/IRGen/GenInit.cpp index a1a4ecdffdede..0b2831e3ceab3 100644 --- a/lib/IRGen/GenInit.cpp +++ b/lib/IRGen/GenInit.cpp @@ -114,5 +114,9 @@ void TemporarySet::destroyAll(IRGenFunction &IGF) const { void Temporary::destroy(IRGenFunction &IGF) const { auto &ti = IGF.getTypeInfo(Type); + if (Type.isSensitive()) { + llvm::Value *size = ti.getSize(IGF, Type); + IGF.emitClearSensitive(Addr.getAddress(), size); + } ti.deallocateStack(IGF, Addr, Type); } diff --git a/lib/IRGen/GenKeyPath.cpp b/lib/IRGen/GenKeyPath.cpp index 76103cba05f33..7fa650f992695 100644 --- a/lib/IRGen/GenKeyPath.cpp +++ b/lib/IRGen/GenKeyPath.cpp @@ -536,7 +536,7 @@ getInitializerForComputedComponent(IRGenModule &IGM, // buffer. if (&component == operands[index.Operand].LastUser) { ti.initializeWithTake(IGF, destAddr, srcAddresses[index.Operand], ty, - false); + false, /*zeroizeIfSensitive=*/ true); } else { ti.initializeWithCopy(IGF, destAddr, srcAddresses[index.Operand], ty, false); @@ -1364,7 +1364,7 @@ std::pair irgen::emitKeyPathArgument( IGF.Builder.CreateBitCast(ptr, ti.getStorageType()->getPointerTo())); if (operandTy.isAddress()) { ti.initializeWithTake(IGF, addr, ti.getAddressForPointer(indiceValues.claimNext()), - operandTy, false); + operandTy, false, /*zeroizeIfSensitive=*/ true); } else { cast(ti).initialize(IGF, indiceValues, addr, false); } diff --git a/lib/IRGen/GenRecord.h b/lib/IRGen/GenRecord.h index c86d51f1a8061..7ab9cff1db5fe 100644 --- a/lib/IRGen/GenRecord.h +++ b/lib/IRGen/GenRecord.h @@ -141,6 +141,13 @@ class RecordTypeInfoImpl : public Base, this->template getTrailingObjects()); } + void fillWithZerosIfSensitive(IRGenFunction &IGF, Address address, SILType T) const { + if (T.isSensitive()) { + llvm::Value *size = asImpl().getSize(IGF, T); + IGF.emitClearSensitive(address, size); + } + } + public: /// Allocate and initialize a type info of this type. template @@ -206,6 +213,7 @@ class RecordTypeInfoImpl : public Base, field.getTypeInfo().assignWithTake( IGF, destField, srcField, field.getType(IGF.IGM, T), isOutlined); } + fillWithZerosIfSensitive(IGF, src, T); } else { this->callOutlinedCopy(IGF, dest, src, T, IsNotInitialization, IsTake); } @@ -242,22 +250,19 @@ class RecordTypeInfoImpl : public Base, } void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { // If we're bitwise-takable, use memcpy. if (this->isBitwiseTakable(ResilienceExpansion::Maximal)) { IGF.Builder.CreateMemCpy( dest.getAddress(), llvm::MaybeAlign(dest.getAlignment().getValue()), src.getAddress(), llvm::MaybeAlign(src.getAlignment().getValue()), asImpl().Impl::getSize(IGF, T)); - return; - } - - // If the fields are not ABI-accessible, use the value witness table. - if (!AreFieldsABIAccessible) { + } else if (!AreFieldsABIAccessible) { + // If the fields are not ABI-accessible, use the value witness table. return emitInitializeWithTakeCall(IGF, T, dest, src); - } - if (isOutlined || T.hasParameterizedExistential()) { + } else if (isOutlined || T.hasParameterizedExistential()) { auto offsets = asImpl().getNonFixedOffsets(IGF, T); for (auto &field : getFields()) { if (field.isEmpty()) @@ -266,11 +271,14 @@ class RecordTypeInfoImpl : public Base, Address destField = field.projectAddress(IGF, dest, offsets); Address srcField = field.projectAddress(IGF, src, offsets); field.getTypeInfo().initializeWithTake( - IGF, destField, srcField, field.getType(IGF.IGM, T), isOutlined); + IGF, destField, srcField, field.getType(IGF.IGM, T), isOutlined, + zeroizeIfSensitive); } } else { this->callOutlinedCopy(IGF, dest, src, T, IsInitialization, IsTake); } + if (zeroizeIfSensitive) + fillWithZerosIfSensitive(IGF, src, T); } void destroy(IRGenFunction &IGF, Address addr, SILType T, @@ -283,12 +291,15 @@ class RecordTypeInfoImpl : public Base, if (isOutlined || T.hasParameterizedExistential()) { auto offsets = asImpl().getNonFixedOffsets(IGF, T); for (auto &field : getFields()) { - if (field.isTriviallyDestroyable()) + SILType fieldType = field.getType(IGF.IGM, T); + if (field.isTriviallyDestroyable() && + !((bool)fieldType && fieldType.isSensitive())) { continue; + } field.getTypeInfo().destroy(IGF, field.projectAddress(IGF, addr, offsets), - field.getType(IGF.IGM, T), isOutlined); + fieldType, isOutlined); } } else { this->callOutlinedDestroy(IGF, addr, T); diff --git a/lib/IRGen/GenStruct.cpp b/lib/IRGen/GenStruct.cpp index b71a0bde82f23..7c3746ed4c706 100644 --- a/lib/IRGen/GenStruct.cpp +++ b/lib/IRGen/GenStruct.cpp @@ -271,12 +271,11 @@ namespace { bool isOutlined) const override { // If the struct has a deinit declared, then call it to destroy the // value. - if (tryEmitDestroyUsingDeinit(IGF, address, T)) { - return; + if (!tryEmitDestroyUsingDeinit(IGF, address, T)) { + // Otherwise, perform elementwise destruction of the value. + super::destroy(IGF, address, T, isOutlined); } - - // Otherwise, perform elementwise destruction of the value. - return super::destroy(IGF, address, T, isOutlined); + super::fillWithZerosIfSensitive(IGF, address, T); } void verify(IRGenTypeVerifierFunction &IGF, @@ -519,7 +518,8 @@ namespace { } void initializeWithTake(IRGenFunction &IGF, Address dst, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { emitCopyWithCopyFunction(IGF, T, src, dst); destroy(IGF, src, T, isOutlined); } @@ -801,7 +801,8 @@ namespace { } void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { if (auto moveConstructor = findMoveConstructor()) { emitCopyWithCopyConstructor(IGF, T, moveConstructor, src.getAddress(), @@ -820,7 +821,7 @@ namespace { StructTypeInfoBase::initializeWithTake(IGF, dest, src, T, - isOutlined); + isOutlined, zeroizeIfSensitive); } void assignWithTake(IRGenFunction &IGF, Address dest, Address src, SILType T, diff --git a/lib/IRGen/GenType.cpp b/lib/IRGen/GenType.cpp index 66f63db3c4dd0..b282cbe1a4079 100644 --- a/lib/IRGen/GenType.cpp +++ b/lib/IRGen/GenType.cpp @@ -90,7 +90,7 @@ void TypeInfo::assign(IRGenFunction &IGF, Address dest, Address src, void TypeInfo::initialize(IRGenFunction &IGF, Address dest, Address src, IsTake_t isTake, SILType T, bool isOutlined) const { if (isTake) { - initializeWithTake(IGF, dest, src, T, isOutlined); + initializeWithTake(IGF, dest, src, T, isOutlined, /*zeroizeIfSensitive=*/ true); } else { initializeWithCopy(IGF, dest, src, T, isOutlined); } @@ -149,7 +149,8 @@ TypeInfo::nativeParameterValueSchema(IRGenModule &IGM) const { /// move-initialization, except the old object will not be destroyed. void FixedTypeInfo::initializeWithTake(IRGenFunction &IGF, Address destAddr, Address srcAddr, SILType T, - bool isOutlined) const { + bool isOutlined, + bool zeroizeIfSensitive) const { assert(isBitwiseTakable(ResilienceExpansion::Maximal) && "non-bitwise-takable type must override default initializeWithTake"); @@ -175,7 +176,8 @@ void LoadableTypeInfo::initializeWithCopy(IRGenFunction &IGF, Address destAddr, bool isOutlined) const { // Use memcpy if that's legal. if (isTriviallyDestroyable(ResilienceExpansion::Maximal)) { - return initializeWithTake(IGF, destAddr, srcAddr, T, isOutlined); + return initializeWithTake(IGF, destAddr, srcAddr, T, isOutlined, + /*zeroizeIfSensitive=*/ false); } // Otherwise explode and re-implode. @@ -1297,7 +1299,8 @@ namespace { void initializeWithTake(IRGenFunction &IGF, Address destAddr, Address srcAddr, SILType T, - bool isOutlined) const override { + bool isOutlined, + bool zeroizeIfSensitive) const override { llvm_unreachable("cannot opaquely manipulate immovable types!"); } diff --git a/lib/IRGen/GenValueWitness.cpp b/lib/IRGen/GenValueWitness.cpp index 32f0d43f9287c..25f274f2b09ad 100644 --- a/lib/IRGen/GenValueWitness.cpp +++ b/lib/IRGen/GenValueWitness.cpp @@ -555,7 +555,8 @@ static void buildValueWitnessFunction(IRGenModule &IGM, conditionallyGetTypeLayoutEntry(IGM, concreteType)) { typeLayoutEntry->initWithTake(IGF, dest, src); } else { - type.initializeWithTake(IGF, dest, src, concreteType, true); + type.initializeWithTake(IGF, dest, src, concreteType, true, + /*zeroizeIfSensitive=*/ true); } dest = IGF.Builder.CreateElementBitCast(dest, IGF.IGM.OpaqueTy); IGF.Builder.CreateRet(dest.getAddress()); diff --git a/lib/IRGen/IRGenFunction.cpp b/lib/IRGen/IRGenFunction.cpp index 47e4828bffb4b..e1d15258e70bd 100644 --- a/lib/IRGen/IRGenFunction.cpp +++ b/lib/IRGen/IRGenFunction.cpp @@ -817,7 +817,7 @@ void IRGenFunction::emitResumeAsyncContinuationReturning( Address destAddr = valueTI.getAddressForPointer(destPtr); valueTI.initializeWithTake(*this, destAddr, srcAddr, valueTy, - /*outlined*/ false); + /*outlined*/ false, /*zeroizeIfSensitive=*/ true); auto call = Builder.CreateCall( throwing ? IGM.getContinuationThrowingResumeFunctionPointer() @@ -834,3 +834,19 @@ void IRGenFunction::emitResumeAsyncContinuationThrowing( {continuation, error}); call->setCallingConv(IGM.SwiftCC); } + +void IRGenFunction::emitClearSensitive(Address address, llvm::Value *size) { + // If our deployment target doesn't contain the new swift_clearSensitive, + // fall back to memset_s + auto deploymentAvailability = AvailabilityContext::forDeploymentTarget(IGM.Context); + auto clearSensitiveAvail = IGM.Context.getClearSensitiveAvailability(); + if (!deploymentAvailability.isContainedIn(clearSensitiveAvail)) { + Builder.CreateCall(IGM.getMemsetSFunctionPointer(), + {address.getAddress(), size, + llvm::ConstantInt::get(IGM.Int32Ty, 0), size}); + return; + } + Builder.CreateCall(IGM.getClearSensitiveFunctionPointer(), + {address.getAddress(), size}); +} + diff --git a/lib/IRGen/IRGenFunction.h b/lib/IRGen/IRGenFunction.h index 6eb00f7eb37f5..f193db6660e85 100644 --- a/lib/IRGen/IRGenFunction.h +++ b/lib/IRGen/IRGenFunction.h @@ -203,6 +203,8 @@ class IRGenFunction { void emitResumeAsyncContinuationThrowing(llvm::Value *continuation, llvm::Value *error); + void emitClearSensitive(Address address, llvm::Value *size); + FunctionPointer getFunctionPointerForResumeIntrinsic(llvm::Value *resumeIntrinsic); diff --git a/lib/IRGen/IRGenSIL.cpp b/lib/IRGen/IRGenSIL.cpp index 7d404ab2fe19e..f022439b34d5d 100644 --- a/lib/IRGen/IRGenSIL.cpp +++ b/lib/IRGen/IRGenSIL.cpp @@ -3704,6 +3704,10 @@ void IRGenSILFunction::visitFullApplySite(FullApplySite site) { *this, origCalleeType, substCalleeType, calleeLV, selfValue, site.getSubstitutionMap(), &witnessMetadata); + if (site.hasIndirectSILResults()) { + emission->setIndirectReturnAddress(getLoweredAddress(site.getIndirectSILResults()[0])); + } + emission->begin(); // Lower the arguments and return value in the callee's generic context. @@ -5422,7 +5426,7 @@ void IRGenSILFunction::visitStoreInst(swift::StoreInst *i) { // the lifetime of the alloca. IRBuilder::SavedInsertionPointRAII insertRAII(this->Builder, insertPt); addrTI.initializeWithTake(*this, dest, forwardAddr, i->getDest()->getType(), - false); + false, /*zeroizeIfSensitive=*/ true); (void)source.claimAll(); return; } @@ -7321,7 +7325,8 @@ void IRGenSILFunction::visitKeyPathInst(swift::KeyPathInst *I) { Builder.CreateBitCast(ptr, ti.getStorageType()->getPointerTo())); if (operand->getType().isAddress()) { ti.initializeWithTake(*this, addr, getLoweredAddress(operand), - operand->getType(), false); + operand->getType(), false, + /*zeroizeIfSensitive=*/ true); } else { Explosion operandValue = getLoweredExplosion(operand); cast(ti).initialize(*this, operandValue, addr, false); @@ -7743,7 +7748,8 @@ void IRGenSILFunction::visitCopyAddrInst(swift::CopyAddrInst *i) { Address dest = loweredDest.getAnyAddress(); if (i->isInitializationOfDest()) { if (i->isTakeOfSrc()) { - addrTI.initializeWithTake(*this, dest, src, addrTy, false); + addrTI.initializeWithTake(*this, dest, src, addrTy, false, + /*zeroizeIfSensitive=*/ true); } else { addrTI.initializeWithCopy(*this, dest, src, addrTy, false); } @@ -7766,7 +7772,8 @@ void IRGenSILFunction::visitExplicitCopyAddrInst( Address dest = loweredDest.getAnyAddress(); if (i->isInitializationOfDest()) { if (i->isTakeOfSrc()) { - addrTI.initializeWithTake(*this, dest, src, addrTy, false); + addrTI.initializeWithTake(*this, dest, src, addrTy, false, + /*zeroizeIfSensitive=*/ true); } else { addrTI.initializeWithCopy(*this, dest, src, addrTy, false); } diff --git a/lib/IRGen/IndirectTypeInfo.h b/lib/IRGen/IndirectTypeInfo.h index a00cde7ced215..19d73dbe1f5f5 100644 --- a/lib/IRGen/IndirectTypeInfo.h +++ b/lib/IRGen/IndirectTypeInfo.h @@ -53,13 +53,15 @@ class IndirectTypeInfo : public Base { void initializeFromParams(IRGenFunction &IGF, Explosion ¶ms, Address dest, SILType T, bool isOutlined) const override { Address src = this->getAddressForPointer(params.claimNext()); - asDerived().Derived::initializeWithTake(IGF, dest, src, T, isOutlined); + asDerived().Derived::initializeWithTake(IGF, dest, src, T, isOutlined, + /*zeroizeIfSensitive=*/ true); } void assignWithTake(IRGenFunction &IGF, Address dest, Address src, SILType T, bool isOutlined) const override { asDerived().Derived::destroy(IGF, dest, T, isOutlined); - asDerived().Derived::initializeWithTake(IGF, dest, src, T, isOutlined); + asDerived().Derived::initializeWithTake(IGF, dest, src, T, isOutlined, + /*zeroizeIfSensitive=*/ true); } }; diff --git a/lib/IRGen/NonFixedTypeInfo.h b/lib/IRGen/NonFixedTypeInfo.h index d07173b8bcf97..cb3837a520edb 100644 --- a/lib/IRGen/NonFixedTypeInfo.h +++ b/lib/IRGen/NonFixedTypeInfo.h @@ -152,7 +152,8 @@ class BitwiseCopyableTypeInfo } void initializeWithTake(IRGenFunction &IGF, Address destAddr, Address srcAddr, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { bitwiseCopy(IGF, destAddr, srcAddr, T, isOutlined); } diff --git a/lib/IRGen/Outlining.cpp b/lib/IRGen/Outlining.cpp index 9ae04f8a8ddf5..d4efbbd1500ee 100644 --- a/lib/IRGen/Outlining.cpp +++ b/lib/IRGen/Outlining.cpp @@ -402,7 +402,7 @@ llvm::Constant *IRGenModule::getOrCreateOutlinedInitializeWithTakeFunction( const TypeInfo &ti) { if (!IGF.outliningCanCallValueWitnesses() || T.hasArchetype() || !canUseValueWitnessForValueOp(*this, T)) { - ti.initializeWithTake(IGF, dest, src, T, true); + ti.initializeWithTake(IGF, dest, src, T, true, /*zeroizeIfSensitive=*/ true); } else { emitInitializeWithTakeCall(IGF, T, dest, src); } diff --git a/lib/IRGen/ResilientTypeInfo.h b/lib/IRGen/ResilientTypeInfo.h index e002722117fe6..4b95102164c48 100644 --- a/lib/IRGen/ResilientTypeInfo.h +++ b/lib/IRGen/ResilientTypeInfo.h @@ -104,7 +104,8 @@ class ResilientTypeInfo : public WitnessSizedTypeInfo { } void initializeWithTake(IRGenFunction &IGF, Address dest, Address src, - SILType T, bool isOutlined) const override { + SILType T, bool isOutlined, + bool zeroizeIfSensitive) const override { emitInitializeWithTakeCall(IGF, T, dest, src); } diff --git a/lib/IRGen/StructLayout.cpp b/lib/IRGen/StructLayout.cpp index bf5d309c5909c..66a780886e24d 100644 --- a/lib/IRGen/StructLayout.cpp +++ b/lib/IRGen/StructLayout.cpp @@ -67,10 +67,16 @@ StructLayout::StructLayout(IRGenModule &IGM, std::optional type, builder.addHeapHeader(); } - auto deinit = (decl && decl->getValueTypeDestructor()) + auto triviallyDestroyable = (decl && decl->getValueTypeDestructor()) ? IsNotTriviallyDestroyable : IsTriviallyDestroyable; auto copyable = (decl && !decl->canBeCopyable()) ? IsNotCopyable : IsCopyable; + IsBitwiseTakable_t bitwiseTakable = IsBitwiseTakable; + + if (decl && decl->getAttrs().hasAttribute()) { + triviallyDestroyable = IsNotTriviallyDestroyable; + bitwiseTakable = IsNotBitwiseTakable; + } // Handle a raw layout specification on a struct. RawLayoutAttr *rawLayout = nullptr; @@ -79,8 +85,8 @@ StructLayout::StructLayout(IRGenModule &IGM, std::optional type, } if (rawLayout && type) { auto sd = cast(decl); - IsKnownTriviallyDestroyable = deinit; - IsKnownBitwiseTakable = IsBitwiseTakable; + IsKnownTriviallyDestroyable = triviallyDestroyable; + IsKnownBitwiseTakable = bitwiseTakable; SpareBits.clear(); assert(!copyable); IsKnownCopyable = copyable; @@ -178,7 +184,7 @@ StructLayout::StructLayout(IRGenModule &IGM, std::optional type, SpareBits.clear(); IsFixedLayout = true; IsLoadable = true; - IsKnownTriviallyDestroyable = deinit & builder.isTriviallyDestroyable(); + IsKnownTriviallyDestroyable = triviallyDestroyable & builder.isTriviallyDestroyable(); IsKnownBitwiseTakable = builder.isBitwiseTakable(); IsKnownAlwaysFixedSize = builder.isAlwaysFixedSize(); IsKnownCopyable = copyable & builder.isCopyable(); @@ -190,8 +196,8 @@ StructLayout::StructLayout(IRGenModule &IGM, std::optional type, SpareBits = builder.getSpareBits(); IsFixedLayout = builder.isFixedLayout(); IsLoadable = builder.isLoadable(); - IsKnownTriviallyDestroyable = deinit & builder.isTriviallyDestroyable(); - IsKnownBitwiseTakable = builder.isBitwiseTakable(); + IsKnownTriviallyDestroyable = triviallyDestroyable & builder.isTriviallyDestroyable(); + IsKnownBitwiseTakable = bitwiseTakable & builder.isBitwiseTakable(); IsKnownAlwaysFixedSize = builder.isAlwaysFixedSize(); IsKnownCopyable = copyable & builder.isCopyable(); if (typeToFill) { diff --git a/lib/IRGen/TypeInfo.h b/lib/IRGen/TypeInfo.h index db6c5c54cb80f..dd44fb9f250b2 100644 --- a/lib/IRGen/TypeInfo.h +++ b/lib/IRGen/TypeInfo.h @@ -375,7 +375,8 @@ class TypeInfo { /// the old object is actually no longer permitted to be destroyed. virtual void initializeWithTake(IRGenFunction &IGF, Address destAddr, Address srcAddr, SILType T, - bool isOutlined) const = 0; + bool isOutlined, + bool zeroizeIfSensitive) const = 0; /// Perform a copy-initialization from the given object. virtual void initializeWithCopy(IRGenFunction &IGF, Address destAddr, diff --git a/lib/IRGen/TypeLayout.cpp b/lib/IRGen/TypeLayout.cpp index 6327b4e0a2c21..bada4a8e1032b 100644 --- a/lib/IRGen/TypeLayout.cpp +++ b/lib/IRGen/TypeLayout.cpp @@ -1471,7 +1471,8 @@ void ScalarTypeLayoutEntry::initWithTake(IRGenFunction &IGF, Address dest, storageTy, alignment); src = Address(Builder.CreateBitCast(src.getAddress(), addressType), storageTy, alignment); - typeInfo.initializeWithTake(IGF, dest, src, representative, true); + typeInfo.initializeWithTake(IGF, dest, src, representative, true, + /*zeroizeIfSensitive=*/ true); } llvm::Value *ScalarTypeLayoutEntry::getEnumTagSinglePayload( @@ -3775,7 +3776,8 @@ void TypeInfoBasedTypeLayoutEntry::initWithTake(IRGenFunction &IGF, Address(Builder.CreateBitCast(src.getAddress(), addressType->getPointerTo()), addressType, alignment); - typeInfo.initializeWithTake(IGF, dest, src, representative, true); + typeInfo.initializeWithTake(IGF, dest, src, representative, true, + /*zeroizeIfSensitive=*/ true); } llvm::Value *TypeInfoBasedTypeLayoutEntry::getEnumTagSinglePayload( diff --git a/lib/SIL/IR/SILFunctionType.cpp b/lib/SIL/IR/SILFunctionType.cpp index 08065b0991574..7a2de7bb17cdd 100644 --- a/lib/SIL/IR/SILFunctionType.cpp +++ b/lib/SIL/IR/SILFunctionType.cpp @@ -3561,6 +3561,9 @@ class CFunctionTypeConventions : public Conventions { } } } + SILType silTy = SILType::getPrimitiveObjectType(type.getType()); + if (silTy.isSensitive()) + return ParameterConvention::Indirect_In_Guaranteed; } return getIndirectCParameterConvention(getParamType(index)); } diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index 4ae75cd22aeac..a5412040238b2 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2400,6 +2400,13 @@ namespace { return handleMoveOnlyAddressOnly(structType, properties); } + if (D->getAttrs().hasAttribute()) { + properties.setAddressOnly(); + properties.setNonTrivial(); + properties.setLexical(IsLexical); + return handleAddressOnly(structType, properties); + } + auto subMap = structType->getContextSubstitutionMap(&TC.M, D); // Classify the type according to its stored properties. diff --git a/test/IRGen/Inputs/sensitive.h b/test/IRGen/Inputs/sensitive.h new file mode 100644 index 0000000000000..3efc6f411bdc2 --- /dev/null +++ b/test/IRGen/Inputs/sensitive.h @@ -0,0 +1,25 @@ + +struct __attribute__((swift_attr("sensitive"))) SmallCStruct { + unsigned a; + unsigned b; + unsigned c; +}; + +struct __attribute__((swift_attr("sensitive"))) LargeCStruct { + unsigned a; + unsigned b; + unsigned c; + unsigned d; + unsigned e; + unsigned f; + unsigned g; +}; + +struct SmallCStruct getSmallStruct(int x); +struct LargeCStruct getLargeStruct(int x); + +void printSmallStruct(int x, struct SmallCStruct s, int y); +struct SmallCStruct forwardSmallStruct(struct SmallCStruct s); +void printLargeStruct(int x, struct LargeCStruct s, int y); +struct LargeCStruct forwardLargeStruct(struct LargeCStruct s); + diff --git a/test/IRGen/Inputs/sensitive_c_functions.c b/test/IRGen/Inputs/sensitive_c_functions.c new file mode 100644 index 0000000000000..c4c9354fe3202 --- /dev/null +++ b/test/IRGen/Inputs/sensitive_c_functions.c @@ -0,0 +1,47 @@ + +#include "sensitive.h" + +#include + +struct SmallCStruct getSmallStruct(int x) { + printf("x = %d\n", x); + + struct SmallCStruct s; + s.a = 0xdeadbeaf; + s.b = 0xdeadbeaf; + s.c = 0xdeadbeaf; + return s; +} + +struct LargeCStruct getLargeStruct(int x) { + printf("x = %d\n", x); + + struct LargeCStruct s; + s.a = 0xdeadbeaf; + s.b = 0xdeadbeaf; + s.c = 0xdeadbeaf; + s.d = 0xdeadbeaf; + s.e = 0xdeadbeaf; + s.f = 0xdeadbeaf; + s.g = 0xdeadbeaf; + return s; +} + +void printSmallStruct(int x, struct SmallCStruct s, int y) { + printf("x = %d, y = %d\n", x, y); + printf("s = (%u, %u, %u)\n", s.a, s.b, s.c); +} + +struct SmallCStruct forwardSmallStruct(struct SmallCStruct s) { + return s; +} + +void printLargeStruct(int x, struct LargeCStruct s, int y) { + printf("x = %d, y = %d\n", x, y); + printf("s = (%u, %u, %u, %u, %u, %u, %u)\n", s.a, s.b, s.c, s.e, s.e, s.f, s.g); +} + +struct LargeCStruct forwardLargeStruct(struct LargeCStruct s) { + return s; +} + diff --git a/test/IRGen/sensitive.sil b/test/IRGen/sensitive.sil new file mode 100644 index 0000000000000..202381b235ab2 --- /dev/null +++ b/test/IRGen/sensitive.sil @@ -0,0 +1,47 @@ +// RUN: %target-swift-frontend -enable-experimental-feature Sensitive -emit-ir %s | %FileCheck %s +// RUN: %target-swift-frontend -target %module-target-future -enable-experimental-feature Sensitive -emit-ir %s | %FileCheck %s + +sil_stage canonical + +import Builtin +import Swift + +@sensitive struct SensitiveStruct { + @_hasStorage @_hasInitialValue var a: Int64 + @_hasStorage @_hasInitialValue var b: Int64 + @_hasStorage @_hasInitialValue var c: Int64 +} + + +// CHECK-LABEL: define{{.*}}void @testDestroy +// CHECK: call {{(i[0-9]+ @memset_s|void @swift_clearSensitive)\(ptr %0, i[0-9]+ 24}} +// CHECK: ret void +sil @testDestroy : $@convention(thin) (@in SensitiveStruct) -> () { +bb0(%0 : $*SensitiveStruct): + destroy_addr %0 : $*SensitiveStruct + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: define{{.*}}void @testCopyAddrTake +// CHECK: call void @llvm.memcpy +// CHECK-NEXT: call {{(i[0-9]+ @memset_s|void @swift_clearSensitive)\(ptr %1, i[0-9]+ 24}} +// CHECK: ret void +sil @testCopyAddrTake : $@convention(thin) (@in SensitiveStruct) -> @out SensitiveStruct { +bb0(%0 : $*SensitiveStruct, %1 : $*SensitiveStruct): + copy_addr [take] %1 to [init] %0 : $*SensitiveStruct + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: define{{.*}}void @testCopyAddrCopy +// CHECK-NOT: memset_s +// CHECK-NOT: swift_clearSensitive +// CHECK: ret void +sil @testCopyAddrCopy : $@convention(thin) (@in_guaranteed SensitiveStruct) -> @out SensitiveStruct { +bb0(%0 : $*SensitiveStruct, %1 : $*SensitiveStruct): + copy_addr %1 to [init] %0 : $*SensitiveStruct + %r = tuple () + return %r : $() +} + diff --git a/test/IRGen/sensitive.swift b/test/IRGen/sensitive.swift new file mode 100644 index 0000000000000..125e38abb30d8 --- /dev/null +++ b/test/IRGen/sensitive.swift @@ -0,0 +1,150 @@ +// RUN: %empty-directory(%t) +// +// RUN: %target-clang -x c %S/Inputs/sensitive_c_functions.c -c -o %t/sensitive_c_functions.o +// +// RUN: %target-build-swift -target %module-target-future -module-name=test -enable-experimental-feature Sensitive -parse-as-library -Onone -import-objc-header %S/Inputs/sensitive.h %s %t/sensitive_c_functions.o -o %t/a.out +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out | %FileCheck %s +// +// RUN: %target-build-swift -target %module-target-future -module-name=test -enable-experimental-feature Sensitive -parse-as-library -O -import-objc-header %S/Inputs/sensitive.h %s %t/sensitive_c_functions.o -o %t/ao.out +// RUN: %target-codesign %t/ao.out +// RUN: %target-run %t/ao.out | %FileCheck %s +// +// Test with memset_s which uses the compiler if of swift_clearSensitive is not available: +// RUN: %target-build-swift -module-name=test -enable-experimental-feature Sensitive -parse-as-library -Onone -import-objc-header %S/Inputs/sensitive.h %s %t/sensitive_c_functions.o -o %t/a.out +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out | %FileCheck %s +// +// REQUIRES: executable_test +// UNSUPPORTED: use_os_stdlib + +var checkBuffer: UnsafeBufferPointer? + +@inline(never) +func checkLater(_ t: inout T) { + withUnsafePointer(to: &t) { + let size = MemoryLayout.size / MemoryLayout.size + $0.withMemoryRebound(to: UInt32.self, capacity: size) { + checkBuffer = UnsafeBufferPointer(start: $0, count: size) + } + } +} + +@inline(never) +func printit(_ t: inout T) { + print(t) +} + +@inline(never) +func consumeAndPrint(_ t: consuming T) { + print(t) +} + +@inline(never) +func check() { + for b in checkBuffer! { + precondition(b != 0xdeadbeaf) + } +} + +@inline(never) +func printSensitive(_ t: T) { + do { + var x: T = t + checkLater(&x) + printit(&x) + } + check() + print() // to prevent tail call of `check()` +} + +@inline(never) +func printConsumed(_ t: T) { + do { + var x: T = t + checkLater(&x) + consumeAndPrint(x) + } + check() + print() // to prevent tail call of `check()` +} + + +@inline(never) +func checkSmallCStructs() { + do { + var x = getSmallStruct(27) + checkLater(&x) + x = forwardSmallStruct(x); + printSmallStruct(27, x, 28) + } + check() + print() // to prevent tail call of `check()` +} + +@inline(never) +func checkLargeCStructs() { + do { + var x = getLargeStruct(29) + checkLater(&x) + x = forwardLargeStruct(x); + printLargeStruct(30, x, 31) + } + check() + print() // to prevent tail call of `check()` +} + +protocol P {} + +@sensitive +struct SensitiveStruct { + var a = 0xdeadbeaf + var b = 0xdeadbeaf + var c = 0xdeadbeaf +} + +// A struct which would be address-only also without @sensitive +@sensitive +struct NonLoadableSensitiveStruct { + var a = 0xdeadbeaf + var b = 0xdeadbeaf + var c = 0xdeadbeaf + let p: P +} + +struct X : P {} + +struct Container { + var x = 123 + let t: T + var y = 456 +} + +@main struct Main { + static func main() { + // CHECK: SensitiveStruct(a: 3735928495, b: 3735928495, c: 3735928495) + printSensitive(SensitiveStruct()) + + // CHECK: SensitiveStruct(a: 3735928495, b: 3735928495, c: 3735928495) + printConsumed(SensitiveStruct()) + + // CHECK: Optional(test.SensitiveStruct(a: 3735928495, b: 3735928495, c: 3735928495)) + printSensitive(Optional(SensitiveStruct())) + + // CHECK: NonLoadableSensitiveStruct(a: 3735928495, b: 3735928495, c: 3735928495, p: test.X()) + printSensitive(NonLoadableSensitiveStruct(p: X())) + + // CHECK: Container(x: 123, t: test.SensitiveStruct(a: 3735928495, b: 3735928495, c: 3735928495), y: 456) + printSensitive(Container(t: SensitiveStruct())) + + // CHECK: x = 27 + // CHECK-NEXT: x = 27, y = 28 + // CHECK-NEXT: s = (3735928495, 3735928495, 3735928495) + checkSmallCStructs(); + + // CHECK: x = 29 + // CHECK-NEXT: x = 30, y = 31 + // CHECK-NEXT: s = (3735928495, 3735928495, 3735928495, 3735928495, 3735928495, 3735928495, 3735928495) + checkLargeCStructs(); + } +} diff --git a/test/embedded/sensitive.swift b/test/embedded/sensitive.swift new file mode 100644 index 0000000000000..d2b09579c68f3 --- /dev/null +++ b/test/embedded/sensitive.swift @@ -0,0 +1,59 @@ +// RUN: %target-run-simple-swift( -parse-as-library -enable-experimental-feature Sensitive -enable-experimental-feature Embedded -wmo -Xfrontend -disable-access-control -runtime-compatibility-version none) +// RUN: %target-run-simple-swift(-O -parse-as-library -enable-experimental-feature Sensitive -enable-experimental-feature Embedded -wmo -Xfrontend -disable-access-control -runtime-compatibility-version none) +// RUN: %target-run-simple-swift(-target %module-target-future -parse-as-library -enable-experimental-feature Sensitive -enable-experimental-feature Embedded -wmo -Xfrontend -disable-access-control -runtime-compatibility-version none) + +// REQUIRES: swift_in_compiler +// REQUIRES: executable_test +// REQUIRES: optimized_stdlib +// REQUIRES: OS=macosx || OS=linux-gnu + +var checkBuffer: UnsafeBufferPointer? + +@inline(never) +func checkLater(_ t: inout T) { + withUnsafePointer(to: &t) { + let size = MemoryLayout.size / MemoryLayout.size + $0.withMemoryRebound(to: UInt32.self, capacity: size) { + checkBuffer = UnsafeBufferPointer(start: $0, count: size) + } + } +} + +@inline(never) +func check() { + for b in checkBuffer! { + precondition(b != 0xdeadbeaf) + } +} + +@inline(never) +func testSensitive(_ t: T) { + do { + var x: T = t + checkLater(&x) + } + check() + print(0) // to prevent tail call of `check()` +} + +@sensitive +struct SensitiveStruct { + var a = 0xdeadbeaf + var b = 0xdeadbeaf + var c = 0xdeadbeaf +} + +struct Container { + var x = 123 + let t: T + var y = 456 +} + +@main struct Main { + static func main() { + testSensitive(SensitiveStruct()) + testSensitive(Optional(SensitiveStruct())) + testSensitive(Container(t: SensitiveStruct())) + } +} +