Skip to content

Commit 55b186c

Browse files
committed
Diagnose uses of @unchecked Sendable conformances, not declarations
Under strict concurrency and memory safety, uses of `@unchecked Sendable` conformances are considered unsafe. Diagnose the use sites, not the declaration site.
1 parent a86d942 commit 55b186c

File tree

6 files changed

+74
-18
lines changed

6 files changed

+74
-18
lines changed

include/swift/AST/UnsafeUse.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class UnsafeUse {
143143
ProtocolConformanceRef conformance,
144144
SourceLoc location,
145145
DeclContext *dc) {
146+
assert(subjectType);
146147
UnsafeUse result(UnsafeConformance);
147148
result.storage.conformance.type = subjectType.getPointer();
148149
result.storage.conformance.conformanceRef = conformance.getOpaqueValue();

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4109,11 +4109,12 @@ bool ExprAvailabilityWalker::diagnoseDeclRefAvailability(
41094109
// If the declaration itself is "safe" but we don't disallow unsafe uses,
41104110
// check whether it traffics in unsafe types.
41114111
ASTContext &ctx = D->getASTContext();
4112-
if (ctx.LangOpts.hasFeature(Feature::WarnUnsafe) && !D->isUnsafe()) {
4112+
if (ctx.LangOpts.hasFeature(Feature::WarnUnsafe) && !D->isUnsafe() &&
4113+
!Where.getAvailability().allowsUnsafe()) {
41134114
auto type = D->getInterfaceType();
41144115
if (auto subs = declRef.getSubstitutions())
41154116
type = type.subst(subs);
4116-
if (type->isUnsafe() && !Where.getAvailability().allowsUnsafe()) {
4117+
if (type->isUnsafe()) {
41174118
diagnoseUnsafeUse(
41184119
UnsafeUse::forReferenceToUnsafe(
41194120
D, call != nullptr && !isa<ParamDecl>(D), Where.getDeclContext(),
@@ -4767,6 +4768,23 @@ swift::diagnoseConformanceAvailability(SourceLoc loc,
47674768
}
47684769
}
47694770

4771+
// Strict memory safety checking.
4772+
if (!where.getAvailability().allowsUnsafe()) {
4773+
if (auto normalConf = dyn_cast<NormalProtocolConformance>(rootConf)) {
4774+
// @unchecked Sendable conformances are considered unsafe when complete
4775+
// checking is enabled.
4776+
if (normalConf->isUnchecked() &&
4777+
normalConf->getProtocol()->isSpecificProtocol(KnownProtocolKind::Sendable) &&
4778+
normalConf->getProtocol()->getASTContext()
4779+
.LangOpts.StrictConcurrencyLevel == StrictConcurrency::Complete) {
4780+
diagnoseUnsafeUse(
4781+
UnsafeUse::forConformance(
4782+
concreteConf->getType(), conformance, loc,
4783+
where.getDeclContext()));
4784+
}
4785+
}
4786+
}
4787+
47704788
auto maybeEmitAssociatedTypeNote = [&]() {
47714789
if (!depTy && !replacementTy)
47724790
return;

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2462,20 +2462,6 @@ checkIndividualConformance(NormalProtocolConformance *conformance) {
24622462
ComplainLoc, diag::unchecked_conformance_not_special, ProtoType);
24632463
}
24642464

2465-
// @unchecked conformances are considered unsafe in strict concurrency mode.
2466-
if (conformance->isUnchecked() &&
2467-
Context.LangOpts.hasFeature(Feature::WarnUnsafe) &&
2468-
Context.LangOpts.StrictConcurrencyLevel == StrictConcurrency::Complete) {
2469-
2470-
if (!conformance->getDeclContext()->allowsUnsafe()) {
2471-
diagnoseUnsafeUse(
2472-
UnsafeUse::forConformance(conformance->getType(),
2473-
ProtocolConformanceRef(conformance),
2474-
ComplainLoc,
2475-
conformance->getDeclContext()));
2476-
}
2477-
}
2478-
24792465
bool allowImpliedConditionalConformance = false;
24802466
if (Proto->isSpecificProtocol(KnownProtocolKind::Sendable)) {
24812467
// In -swift-version 5 mode, a conditional conformance to a protocol can imply

lib/Sema/TypeCheckUnsafe.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "swift/AST/ASTContext.h"
1818
#include "swift/AST/UnsafeUse.h"
1919
#include "swift/AST/DiagnosticsSema.h"
20+
#include "swift/AST/PackConformance.h"
2021
#include "swift/AST/SourceFile.h"
2122
#include "swift/AST/SourceFileExtras.h"
2223
#include "TypeCheckAvailability.h"
@@ -87,6 +88,48 @@ static void suggestUnsafeMarkerOnConformance(
8788
).fixItInsert(decl->getAttributeInsertionLoc(false), "@unsafe ");
8889
}
8990

91+
/// Enumerate all of the unsafe conformances within this conformance,
92+
/// returning `true` and aborting the search if any callback returns `true`.
93+
static bool forEachUnsafeConformance(
94+
ProtocolConformanceRef conformance,
95+
llvm::function_ref<bool(ProtocolConformance *conformance)> fn) {
96+
if (conformance.isInvalid() || conformance.isAbstract())
97+
return false;
98+
99+
if (conformance.isPack()) {
100+
for (auto packedConformance :
101+
conformance.getPack()->getPatternConformances()) {
102+
if (forEachUnsafeConformance(packedConformance, fn))
103+
return true;
104+
}
105+
106+
return false;
107+
}
108+
109+
// Is this an unsafe conformance?
110+
ProtocolConformance *concreteConf = conformance.getConcrete();
111+
RootProtocolConformance *rootConf = concreteConf->getRootConformance();
112+
if (auto normalConf = dyn_cast<NormalProtocolConformance>(rootConf)) {
113+
// @unchecked Sendable conformances are considered unsafe when complete
114+
// checking is enabled.
115+
if (normalConf->isUnchecked() &&
116+
normalConf->getProtocol()->isSpecificProtocol(KnownProtocolKind::Sendable) &&
117+
normalConf->getProtocol()->getASTContext()
118+
.LangOpts.StrictConcurrencyLevel == StrictConcurrency::Complete)
119+
if (fn(concreteConf))
120+
return true;
121+
}
122+
123+
// Check conformances that are part of this conformance.
124+
auto subMap = concreteConf->getSubstitutionMap();
125+
for (auto conformance : subMap.getConformances()) {
126+
if (forEachUnsafeConformance(conformance, fn))
127+
return true;
128+
}
129+
130+
return false;
131+
}
132+
90133
/// Retrieve the extra information
91134
static SourceFileExtras *getSourceFileExtrasFor(const Decl *decl) {
92135
auto dc = decl->getDeclContext();

stdlib/public/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ list(APPEND swift_stdlib_compile_flags "-enable-experimental-feature" "Macros")
318318
list(APPEND swift_stdlib_compile_flags "-enable-experimental-feature" "FreestandingMacros")
319319
list(APPEND swift_stdlib_compile_flags "-enable-experimental-feature" "Extern")
320320
list(APPEND swift_stdlib_compile_flags "-enable-experimental-feature" "BitwiseCopyable")
321+
list(APPEND swift_stdlib_compile_flags "-enable-experimental-feature" "WarnUnsafe")
321322

322323
if("${SWIFT_NATIVE_SWIFT_TOOLS_PATH}" STREQUAL "")
323324
set(swift_bin_dir "${CMAKE_BINARY_DIR}/bin")

test/Unsafe/unsafe_concurrency.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
// REQUIRES: swift_feature_StrictConcurrency
55
// REQUIRES: swift_feature_WarnUnsafe
66

7-
// expected-note@+2{{@unchecked conformance of 'C' to protocol 'Sendable' involves unsafe code}}
8-
// expected-warning@+1{{class 'C' involves unsafe code; use '@unsafe' to indicate that its use is not memory-safe}}{{1-1=@unsafe }}
97
class C: @unchecked Sendable {
108
var counter: Int = 0
119
}
1210

1311
nonisolated(unsafe) var globalCounter = 0
1412

13+
func acceptSendable<T: Sendable>(_: T) { }
14+
15+
typealias RequiresSendable<T> = T where T: Sendable
16+
1517
@available(SwiftStdlib 5.1, *)
1618
func f() async { // expected-warning{{global function 'f' involves unsafe code; use '@safe(unchecked)' to assert that the code is memory-safe}}
1719
nonisolated(unsafe) var counter = 0
@@ -21,4 +23,9 @@ func f() async { // expected-warning{{global function 'f' involves unsafe code;
2123
counter += 1 // expected-note{{reference to nonisolated(unsafe) var 'counter' is unsafe in concurrently-executing code}}
2224
print(counter) // expected-note{{reference to nonisolated(unsafe) var 'counter' is unsafe in concurrently-executing code}}
2325
print(globalCounter) // expected-note{{reference to nonisolated(unsafe) var 'globalCounter' is unsafe in concurrently-executing code}}
26+
27+
acceptSendable(C()) // expected-note{{@unchecked conformance of 'C' to protocol 'Sendable' involves unsafe code}}
2428
}
29+
30+
// expected-warning@+1{{type alias 'WeirdC' involves unsafe code; use '@unsafe' to indicate that its use is not memory-safe}}
31+
typealias WeirdC = RequiresSendable<C> // expected-note{{@unchecked conformance of 'C' to protocol 'Sendable' involves unsafe code}}

0 commit comments

Comments
 (0)