Skip to content

Commit 1230045

Browse files
committed
Diagnose @preconcurrency imports as a strict safety issue
@preconcurrency imports disable Sendable checking, which can lead to data races that undermine memory safety. Diagnose such imports, and require `@safe(unchecked)` to suppress the diagnostic.
1 parent 55b186c commit 1230045

File tree

5 files changed

+51
-43
lines changed

5 files changed

+51
-43
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8119,6 +8119,10 @@ GROUPED_WARNING(reference_to_unsafe_decl,Unsafe,none,
81198119
GROUPED_WARNING(reference_to_unsafe_typed_decl,Unsafe,none,
81208120
"%select{reference|call}0 to %kindbase1 involves unsafe type %2",
81218121
(bool, const ValueDecl *, Type))
8122+
GROUPED_WARNING(preconcurrency_import_unsafe,Unsafe,none,
8123+
"@preconcurrency import is not memory-safe because it can silently "
8124+
"introduce data races; use '@safe(unchecked)' to assert that the "
8125+
"code is memory-safe", ())
81228126
NOTE(unsafe_decl_here,none,
81238127
"unsafe %kindbase0 declared here", (const ValueDecl *))
81248128
NOTE(encapsulate_unsafe_in_enclosing_context,none,

include/swift/AST/UnsafeUse.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class UnsafeUse {
4545
ReferenceToUnsafe,
4646
/// A call to an unsafe declaration.
4747
CallToUnsafe,
48+
/// A @preconcurrency import.
49+
PreconcurrencyImport,
4850
};
4951

5052
private:
@@ -77,6 +79,8 @@ class UnsafeUse {
7779
TypeBase *type;
7880
const void *location;
7981
} entity;
82+
83+
const ImportDecl *importDecl;
8084
} storage;
8185

8286
UnsafeUse(Kind kind) : kind(kind) { }
@@ -173,6 +177,12 @@ class UnsafeUse {
173177
decl, type, location);
174178
}
175179

180+
static UnsafeUse forPreconcurrencyImport(const ImportDecl *importDecl) {
181+
UnsafeUse result(PreconcurrencyImport);
182+
result.storage.importDecl = importDecl;
183+
return result;
184+
}
185+
176186
Kind getKind() const { return kind; }
177187

178188
/// The location at which this use will be diagnosed.
@@ -198,6 +208,9 @@ class UnsafeUse {
198208
case CallToUnsafe:
199209
return SourceLoc(
200210
llvm::SMLoc::getFromPointer((const char *)storage.entity.location));
211+
212+
case PreconcurrencyImport:
213+
return storage.importDecl->getLoc();
201214
}
202215
}
203216

@@ -219,6 +232,9 @@ class UnsafeUse {
219232

220233
case UnsafeConformance:
221234
return nullptr;
235+
236+
case PreconcurrencyImport:
237+
return storage.importDecl;
222238
}
223239
}
224240

@@ -246,6 +262,9 @@ class UnsafeUse {
246262

247263
case UnsafeConformance:
248264
return storage.conformance.declContext;
265+
266+
case PreconcurrencyImport:
267+
return storage.importDecl->getDeclContext();
249268
}
250269
}
251270

@@ -264,6 +283,7 @@ class UnsafeUse {
264283
case ReferenceToUnsafe:
265284
case CallToUnsafe:
266285
case UnsafeConformance:
286+
case PreconcurrencyImport:
267287
return nullptr;
268288
}
269289
}
@@ -273,6 +293,7 @@ class UnsafeUse {
273293
switch (getKind()) {
274294
case Override:
275295
case Witness:
296+
case PreconcurrencyImport:
276297
return nullptr;
277298

278299
case UnsafeConformance:
@@ -307,6 +328,7 @@ class UnsafeUse {
307328
case NonisolatedUnsafe:
308329
case ReferenceToUnsafe:
309330
case CallToUnsafe:
331+
case PreconcurrencyImport:
310332
return ProtocolConformanceRef::forInvalid();
311333
}
312334
}

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
#include "swift/AST/TypeCheckRequests.h"
5555
#include "swift/AST/TypeDifferenceVisitor.h"
5656
#include "swift/AST/TypeWalker.h"
57+
#include "swift/AST/UnsafeUse.h"
5758
#include "swift/Basic/Assertions.h"
5859
#include "swift/Basic/Defer.h"
5960
#include "swift/Basic/Statistic.h"
@@ -2462,6 +2463,15 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
24622463
inFlight.limitBehavior(DiagnosticBehavior::Warning);
24632464
}
24642465
}
2466+
2467+
// Preconcurrency imports aren't strictly memory-safe when we have strict
2468+
// concurrency checking enabled.
2469+
if (ID->preconcurrency() &&
2470+
Ctx.LangOpts.StrictConcurrencyLevel == StrictConcurrency::Complete &&
2471+
Ctx.LangOpts.hasFeature(Feature::WarnUnsafe) && !
2472+
ID->getAttrs().hasAttribute<SafeAttr>()) {
2473+
diagnoseUnsafeUse(UnsafeUse::forPreconcurrencyImport(ID));
2474+
}
24652475
}
24662476

24672477
void visitOperatorDecl(OperatorDecl *OD) {

lib/Sema/TypeCheckUnsafe.cpp

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ static bool isUnsafeUseInDefinition(const UnsafeUse &use) {
3939
case UnsafeUse::Override:
4040
case UnsafeUse::Witness:
4141
case UnsafeUse::TypeWitness:
42+
case UnsafeUse::PreconcurrencyImport:
4243
// Never part of the definition. These are always part of the interface.
4344
return false;
4445

@@ -88,48 +89,6 @@ static void suggestUnsafeMarkerOnConformance(
8889
).fixItInsert(decl->getAttributeInsertionLoc(false), "@unsafe ");
8990
}
9091

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-
13392
/// Retrieve the extra information
13493
static SourceFileExtras *getSourceFileExtrasFor(const Decl *decl) {
13594
auto dc = decl->getDeclContext();
@@ -280,6 +239,14 @@ void swift::diagnoseUnsafeUse(const UnsafeUse &use, bool asNote) {
280239

281240
return;
282241
}
242+
243+
case UnsafeUse::PreconcurrencyImport: {
244+
auto importDecl = cast<ImportDecl>(use.getDecl());
245+
importDecl->diagnose(diag::preconcurrency_import_unsafe)
246+
.fixItInsert(importDecl->getAttributeInsertionLoc(false),
247+
"@safe(unchecked) ");
248+
return;
249+
}
283250
}
284251
}
285252

test/Unsafe/unsafe_concurrency.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
// RUN: %target-typecheck-verify-swift -enable-experimental-feature WarnUnsafe -enable-experimental-feature StrictConcurrency
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module-path %t/unsafe_swift_decls.swiftmodule %S/Inputs/unsafe_swift_decls.swift -enable-experimental-feature AllowUnsafeAttribute
3+
4+
// RUN: %target-typecheck-verify-swift -enable-experimental-feature WarnUnsafe -enable-experimental-feature StrictConcurrency -I %t
25

36
// REQUIRES: concurrency
47
// REQUIRES: swift_feature_StrictConcurrency
58
// REQUIRES: swift_feature_WarnUnsafe
69

10+
@preconcurrency import unsafe_swift_decls // expected-warning{{@preconcurrency import is not memory-safe because it can silently introduce data races; use '@safe(unchecked)' to assert that the code is memory-safe}}{{1-1=@safe(unchecked) }}
11+
712
class C: @unchecked Sendable {
813
var counter: Int = 0
914
}

0 commit comments

Comments
 (0)