Skip to content

Commit cccf6c1

Browse files
committed
Introduce @unsafe and the ability to prohibit use of unsafe declarations
Allow any declaration to be marked with `@unsafe`, meaning that it involves unsafe code. This also extends to C declarations marked with the `swift_attr("unsafe")` attribute. Under a separate experimental flag (`DisallowUnsafe`), diagnose any attempt to use an `@unsafe` declaration or any unsafe language feature (such as `unowned(unsafe)`, `@unchecked Sendable`). This begins to define a "safe" mode in Swift that prohibits memory-unsafe constructs.
1 parent 0ce9104 commit cccf6c1

25 files changed

+334
-18
lines changed

include/swift/AST/Decl.h

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
357357
// for the inline bitfields.
358358
union { uint64_t OpaqueBits;
359359

360-
SWIFT_INLINE_BITFIELD_BASE(Decl, bitmax(NumDeclKindBits,8)+1+1+1+1+1+1+1+1+1+1+1,
360+
SWIFT_INLINE_BITFIELD_BASE(Decl, bitmax(NumDeclKindBits,8)+1+1+1+1+1+1+1+1+1+1+1+1,
361361
Kind : bitmax(NumDeclKindBits,8),
362362

363363
/// Whether this declaration is invalid.
@@ -372,10 +372,6 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
372372
/// Use getClangNode() to retrieve the corresponding Clang AST.
373373
FromClang : 1,
374374

375-
/// Whether this declaration was added to the surrounding
376-
/// DeclContext of an active #if config clause.
377-
EscapedFromIfConfig : 1,
378-
379375
/// Whether this declaration is syntactically scoped inside of
380376
/// a local context, but should behave like a top-level
381377
/// declaration for name lookup purposes. This is used by
@@ -404,7 +400,13 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
404400

405401
/// True if we're in the common case where the SPIGroupsRequest
406402
/// request returned an empty array of identifiers.
407-
NoSPIGroups : 1
403+
NoSPIGroups : 1,
404+
405+
/// True if we have computed whether this declaration is unsafe.
406+
IsUnsafeComputed : 1,
407+
408+
/// True if this declaration has been determined to be "unsafe".
409+
IsUnsafe : 1
408410
);
409411

410412
SWIFT_INLINE_BITFIELD_FULL(PatternBindingDecl, Decl, 1+1+2+16,
@@ -857,6 +859,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
857859
friend class ExpandPeerMacroRequest;
858860
friend class GlobalActorAttributeRequest;
859861
friend class SPIGroupsRequest;
862+
friend class IsUnsafeRequest;
860863

861864
private:
862865
llvm::PointerUnion<DeclContext *, ASTContext *> Context;
@@ -916,12 +919,13 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
916919
Bits.Decl.Invalid = false;
917920
Bits.Decl.Implicit = false;
918921
Bits.Decl.FromClang = false;
919-
Bits.Decl.EscapedFromIfConfig = false;
920922
Bits.Decl.Hoisted = false;
921923
Bits.Decl.LacksObjCInterfaceOrImplementation = false;
922924
Bits.Decl.NoMemberAttributeMacros = false;
923925
Bits.Decl.NoGlobalActorAttribute = false;
924926
Bits.Decl.NoSPIGroups = false;
927+
Bits.Decl.IsUnsafeComputed = false;
928+
Bits.Decl.IsUnsafe = false;
925929
}
926930

927931
/// Get the Clang node associated with this declaration.
@@ -1180,15 +1184,26 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
11801184
/// Whether this declaration predates the introduction of concurrency.
11811185
bool preconcurrency() const;
11821186

1183-
public:
1184-
bool escapedFromIfConfig() const {
1185-
return Bits.Decl.EscapedFromIfConfig;
1187+
/// Whether this declaration is considered "unsafe", i.e., should not be
1188+
/// used in a "safe" dialect.
1189+
bool isUnsafe() const;
1190+
1191+
private:
1192+
bool isUnsafeComputed() const {
1193+
return Bits.Decl.IsUnsafeComputed;
1194+
}
1195+
1196+
bool isUnsafeRaw() const {
1197+
return Bits.Decl.IsUnsafe;
11861198
}
11871199

1188-
void setEscapedFromIfConfig(bool Escaped) {
1189-
Bits.Decl.EscapedFromIfConfig = Escaped;
1200+
void setUnsafe(bool value) {
1201+
assert(!Bits.Decl.IsUnsafeComputed);
1202+
Bits.Decl.IsUnsafe = value;
1203+
Bits.Decl.IsUnsafeComputed = true;
11901204
}
11911205

1206+
public:
11921207
bool getSemanticAttrsComputed() const {
11931208
return Bits.Decl.SemanticAttrsComputed;
11941209
}

include/swift/AST/DeclAttr.def

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,13 @@ SIMPLE_DECL_ATTR(sensitive, Sensitive,
500500
OnStruct | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove,
501501
159)
502502

503-
LAST_DECL_ATTR(PreInverseGenerics)
503+
SIMPLE_DECL_ATTR(unsafe, Unsafe,
504+
OnAbstractFunction | OnSubscript | OnVar | OnMacro | OnNominalType |
505+
UserInaccessible |
506+
ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove,
507+
160)
508+
509+
LAST_DECL_ATTR(Unsafe)
504510

505511
#undef DECL_ATTR_ALIAS
506512
#undef CONTEXTUAL_DECL_ATTR_ALIAS

include/swift/AST/DiagnosticsSema.def

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7964,5 +7964,25 @@ NOTE(sending_function_result_with_sending_param_note, none,
79647964
"isolation domain through a result of an invocation of value",
79657965
())
79667966

7967+
//------------------------------------------------------------------------------
7968+
// MARK: Strict Safety Diagnostics
7969+
//------------------------------------------------------------------------------
7970+
ERROR(unsafe_attr_disabled,none,
7971+
"attribute requires '-enable-experimental-feature AllowUnsafeAttribute'", ())
7972+
ERROR(override_safe_withunsafe,none,
7973+
"override of safe %0 with unsafe %0", (DescriptiveDeclKind))
7974+
ERROR(witness_unsafe,none,
7975+
"unsafe %0 %1 cannot satisfy safe requirement",
7976+
(DescriptiveDeclKind, DeclName))
7977+
ERROR(unchecked_conformance_is_unsafe,none,
7978+
"@unchecked conformance involves unsafe code", ())
7979+
ERROR(unowned_unsafe_is_unsafe,none,
7980+
"unowned(unsafe) involves unsafe code", ())
7981+
ERROR(nonisolated_unsafe_is_unsafe,none,
7982+
"nonisolated(unsafe) involves unsafe code", ())
7983+
ERROR(reference_to_unsafe_decl,none,
7984+
"%select{reference|call}0 to unsafe %kindbase1",
7985+
(bool, const ValueDecl *))
7986+
79677987
#define UNDEFINE_DIAGNOSTIC_MACROS
79687988
#include "DefineDiagnosticMacros.h"

include/swift/AST/TypeCheckRequests.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5065,6 +5065,24 @@ class SuppressesConformanceRequest
50655065
bool isCached() const { return true; }
50665066
};
50675067

5068+
class IsUnsafeRequest
5069+
: public SimpleRequest<IsUnsafeRequest,
5070+
bool(Decl *decl),
5071+
RequestFlags::SeparatelyCached> {
5072+
public:
5073+
using SimpleRequest::SimpleRequest;
5074+
5075+
private:
5076+
friend SimpleRequest;
5077+
5078+
bool evaluate(Evaluator &evaluator, Decl *decl) const;
5079+
5080+
public:
5081+
bool isCached() const { return true; }
5082+
std::optional<bool> getCachedResult() const;
5083+
void cacheResult(bool value) const;
5084+
};
5085+
50685086
#define SWIFT_TYPEID_ZONE TypeChecker
50695087
#define SWIFT_TYPEID_HEADER "swift/AST/TypeCheckerTypeIDZone.def"
50705088
#include "swift/Basic/DefineTypeIDZone.h"

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,3 +593,6 @@ SWIFT_REQUEST(TypeChecker, CaptureInfoRequest,
593593
SWIFT_REQUEST(TypeChecker, ParamCaptureInfoRequest,
594594
CaptureInfo(ParamDecl *),
595595
SeparatelyCached, NoLocationInfo)
596+
SWIFT_REQUEST(TypeChecker, IsUnsafeRequest,
597+
bool(Decl *),
598+
SeparatelyCached, NoLocationInfo)

include/swift/Basic/Features.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,11 +396,18 @@ EXPERIMENTAL_FEATURE(ReinitializeConsumeInMultiBlockDefer, false)
396396

397397
EXPERIMENTAL_FEATURE(SE427NoInferenceOnExtension, true)
398398

399+
399400
EXPERIMENTAL_FEATURE(Extern, true)
400401

401402
// Enable trailing comma for comma-separated lists.
402403
EXPERIMENTAL_FEATURE(TrailingComma, false)
403404

405+
/// Allow the @unsafe attribute.
406+
SUPPRESSIBLE_EXPERIMENTAL_FEATURE(AllowUnsafeAttribute, true)
407+
408+
/// Disallow use of unsafe code.
409+
EXPERIMENTAL_FEATURE(DisallowUnsafe, true)
410+
404411
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
405412
#undef EXPERIMENTAL_FEATURE
406413
#undef UPCOMING_FEATURE

lib/AST/ASTPrinter.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3056,6 +3056,15 @@ suppressingFeatureBitwiseCopyable2(PrintOptions &options,
30563056
options.ExcludeAttrList.resize(originalExcludeAttrCount);
30573057
}
30583058

3059+
static void
3060+
suppressingFeatureAllowUnsafeAttribute(PrintOptions &options,
3061+
llvm::function_ref<void()> action) {
3062+
unsigned originalExcludeAttrCount = options.ExcludeAttrList.size();
3063+
options.ExcludeAttrList.push_back(DeclAttrKind::Unsafe);
3064+
action();
3065+
options.ExcludeAttrList.resize(originalExcludeAttrCount);
3066+
}
3067+
30593068
/// Suppress the printing of a particular feature.
30603069
static void suppressingFeature(PrintOptions &options, Feature feature,
30613070
llvm::function_ref<void()> action) {

lib/AST/Decl.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,13 @@ bool Decl::preconcurrency() const {
10151015
return false;
10161016
}
10171017

1018+
bool Decl::isUnsafe() const {
1019+
return evaluateOrDefault(
1020+
getASTContext().evaluator,
1021+
IsUnsafeRequest{const_cast<Decl *>(this)},
1022+
false);
1023+
}
1024+
10181025
Type AbstractFunctionDecl::getThrownInterfaceType() const {
10191026
if (!getThrownTypeRepr())
10201027
return ThrownType.getType();

lib/AST/FeatureSet.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ UNINTERESTING_FEATURE(ReinitializeConsumeInMultiBlockDefer)
226226
UNINTERESTING_FEATURE(SE427NoInferenceOnExtension)
227227
UNINTERESTING_FEATURE(TrailingComma)
228228

229+
static bool usesFeatureAllowUnsafeAttribute(Decl *decl) {
230+
return decl->getAttrs().hasAttribute<UnsafeAttr>();
231+
}
232+
233+
UNINTERESTING_FEATURE(DisallowUnsafe)
234+
229235
// ----------------------------------------------------------------------------
230236
// MARK: - FeatureSet
231237
// ----------------------------------------------------------------------------

lib/AST/TypeCheckRequests.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2597,3 +2597,20 @@ void ParamCaptureInfoRequest::cacheResult(CaptureInfo info) const {
25972597
auto *param = std::get<0>(getStorage());
25982598
param->setDefaultArgumentCaptureInfo(info);
25992599
}
2600+
2601+
//----------------------------------------------------------------------------//
2602+
// IsUnsafeRequest computation.
2603+
//----------------------------------------------------------------------------//
2604+
2605+
std::optional<bool> IsUnsafeRequest::getCachedResult() const {
2606+
auto *decl = std::get<0>(getStorage());
2607+
if (!decl->isUnsafeComputed())
2608+
return std::nullopt;
2609+
2610+
return decl->isUnsafeRaw();
2611+
}
2612+
2613+
void IsUnsafeRequest::cacheResult(bool value) const {
2614+
auto *decl = std::get<0>(getStorage());
2615+
decl->setUnsafe(value);
2616+
}

0 commit comments

Comments
 (0)