Skip to content

Commit c0fb9f9

Browse files
committed
[Strict memory safety] Infer safe/unsafe for imported C types
1 parent 4313f67 commit c0fb9f9

File tree

7 files changed

+236
-28
lines changed

7 files changed

+236
-28
lines changed

include/swift/ClangImporter/ClangImporterRequests.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ namespace swift {
3131
class Decl;
3232
class DeclName;
3333
class EnumDecl;
34+
enum class ExplicitSafety;
3435

3536
/// The input type for a clang direct lookup request.
3637
struct ClangDirectLookupDescriptor final {
@@ -564,6 +565,25 @@ class ClangTypeEscapability
564565
void simple_display(llvm::raw_ostream &out, EscapabilityLookupDescriptor desc);
565566
SourceLoc extractNearestSourceLoc(EscapabilityLookupDescriptor desc);
566567

568+
/// Determine the safety of the given Clang declaration.
569+
class ClangDeclExplicitSafety
570+
: public SimpleRequest<ClangDeclExplicitSafety,
571+
ExplicitSafety(SafeUseOfCxxDeclDescriptor),
572+
RequestFlags::Cached> {
573+
public:
574+
using SimpleRequest::SimpleRequest;
575+
576+
// Source location
577+
SourceLoc getNearestLoc() const { return SourceLoc(); };
578+
bool isCached() const;
579+
580+
private:
581+
friend SimpleRequest;
582+
583+
// Evaluation.
584+
ExplicitSafety evaluate(Evaluator &evaluator, SafeUseOfCxxDeclDescriptor desc) const;
585+
};
586+
567587
#define SWIFT_TYPEID_ZONE ClangImporter
568588
#define SWIFT_TYPEID_HEADER "swift/ClangImporter/ClangImporterTypeIDZone.def"
569589
#include "swift/Basic/DefineTypeIDZone.h"

include/swift/ClangImporter/ClangImporterTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,6 @@ SWIFT_REQUEST(ClangImporter, CustomRefCountingOperation,
4545
SWIFT_REQUEST(ClangImporter, ClangTypeEscapability,
4646
CxxEscapability(EscapabilityLookupDescriptor), Cached,
4747
NoLocationInfo)
48+
SWIFT_REQUEST(ClangImporter, ClangDeclExplicitSafety,
49+
ExplicitSafety(SafeUseOfCxxDeclDescriptor), Cached,
50+
NoLocationInfo)

lib/AST/Decl.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,14 @@ ExplicitSafety Decl::getExplicitSafety() const {
11501150
if (auto safety = getExplicitSafetyFromAttrs(this))
11511151
return *safety;
11521152

1153+
// If this declaration is from C, ask the Clang importer.
1154+
if (auto clangDecl = getClangDecl()) {
1155+
ASTContext &ctx = getASTContext();
1156+
return evaluateOrDefault(
1157+
ctx.evaluator, ClangDeclExplicitSafety({clangDecl, ctx}),
1158+
ExplicitSafety::Unspecified);
1159+
}
1160+
11531161
// Inference: Check the enclosing context.
11541162
if (auto enclosingDC = getDeclContext()) {
11551163
// Is this an extension with @safe or @unsafe on it?

lib/ClangImporter/ClangImporter.cpp

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8215,6 +8215,106 @@ CustomRefCountingOperationResult CustomRefCountingOperation::evaluate(
82158215
return {CustomRefCountingOperationResult::tooManyFound, nullptr, name};
82168216
}
82178217

8218+
/// Check whether the given Clang type involves an unsafe type.
8219+
static bool hasUnsafeType(
8220+
Evaluator &evaluator, ASTContext &swiftContext, clang::QualType clangType
8221+
) {
8222+
// Handle pointers.
8223+
auto pointeeType = clangType->getPointeeType();
8224+
if (!pointeeType.isNull()) {
8225+
// Function pointers are okay.
8226+
if (pointeeType->isFunctionType())
8227+
return false;
8228+
8229+
// Pointers to record types are okay if they come in as foreign reference
8230+
// types.
8231+
if (auto recordDecl = pointeeType->getAsRecordDecl()) {
8232+
if (hasImportAsRefAttr(recordDecl))
8233+
return false;
8234+
}
8235+
8236+
// All other pointers are considered unsafe.
8237+
return true;
8238+
}
8239+
8240+
// Handle records recursively.
8241+
if (auto recordDecl = clangType->getAsTagDecl()) {
8242+
auto safety = evaluateOrDefault(
8243+
evaluator,
8244+
ClangDeclExplicitSafety({recordDecl, swiftContext}),
8245+
ExplicitSafety::Unspecified);
8246+
switch (safety) {
8247+
case ExplicitSafety::Unsafe:
8248+
return true;
8249+
8250+
case ExplicitSafety::Safe:
8251+
case ExplicitSafety::Unspecified:
8252+
return false;
8253+
}
8254+
}
8255+
8256+
// Everything else is safe.
8257+
return false;
8258+
}
8259+
8260+
ExplicitSafety ClangDeclExplicitSafety::evaluate(
8261+
Evaluator &evaluator,
8262+
SafeUseOfCxxDeclDescriptor desc
8263+
) const {
8264+
// FIXME: Somewhat duplicative with importAsUnsafe.
8265+
// FIXME: Also similar to hasPointerInSubobjects
8266+
// FIXME: should probably also subsume IsSafeUseOfCxxDecl
8267+
8268+
// Explicitly unsafe.
8269+
auto decl = desc.decl;
8270+
if (hasUnsafeAPIAttr(decl) || hasSwiftAttribute(decl, "unsafe"))
8271+
return ExplicitSafety::Unsafe;
8272+
8273+
// Explicitly safe.
8274+
if (hasSwiftAttribute(decl, "safe"))
8275+
return ExplicitSafety::Safe;
8276+
8277+
// Enums are always safe.
8278+
if (isa<clang::EnumDecl>(decl))
8279+
return ExplicitSafety::Safe;
8280+
8281+
// If it's not a record, leave it unspecified.
8282+
auto recordDecl = dyn_cast<clang::RecordDecl>(decl);
8283+
if (!recordDecl)
8284+
return ExplicitSafety::Unspecified;
8285+
8286+
// Escapable and non-escapable annotations imply that the declaration is
8287+
// safe.
8288+
if (hasNonEscapableAttr(recordDecl) || hasEscapableAttr(recordDecl))
8289+
return ExplicitSafety::Safe;
8290+
8291+
// If we don't have a definition, leave it unspecified.
8292+
recordDecl = recordDecl->getDefinition();
8293+
if (!recordDecl)
8294+
return ExplicitSafety::Unspecified;
8295+
8296+
// If this is a C++ class, check its bases.
8297+
if (auto cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(recordDecl)) {
8298+
for (auto base : cxxRecordDecl->bases()) {
8299+
if (hasUnsafeType(evaluator, desc.ctx, base.getType()))
8300+
return ExplicitSafety::Unsafe;
8301+
}
8302+
}
8303+
8304+
// Check the fields.
8305+
for (auto field : recordDecl->fields()) {
8306+
if (hasUnsafeType(evaluator, desc.ctx, field->getType()))
8307+
return ExplicitSafety::Unsafe;
8308+
}
8309+
8310+
// Okay, call it safe.
8311+
return ExplicitSafety::Safe;
8312+
}
8313+
8314+
bool ClangDeclExplicitSafety::isCached() const {
8315+
return isa<clang::RecordDecl>(std::get<0>(getStorage()).decl);
8316+
}
8317+
82188318
void ClangImporter::withSymbolicFeatureEnabled(
82198319
llvm::function_ref<void(void)> callback) {
82208320
llvm::SaveAndRestore<bool> oldImportSymbolicCXXDecls(

test/Interop/Cxx/class/safe-interop-mode.swift

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,20 @@ import Test
7171
import CoreFoundation
7272
import CxxStdlib
7373

74-
// expected-warning@+3{{global function 'useUnsafeParam' has an interface that involves unsafe types}}
75-
// expected-note@+2{{add '@unsafe' to indicate that this declaration is unsafe to use}}{{1-1=@unsafe }}
76-
// expected-note@+1{{add '@safe' to indicate that this declaration is memory-safe to use}}{{1-1=@safe }}
77-
func useUnsafeParam(x: Unannotated) { // expected-note{{reference to unsafe struct 'Unannotated'}}
74+
func useUnsafeParam(x: Unannotated) {
75+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
76+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
7877
}
7978

80-
// expected-warning@+4{{global function 'useUnsafeParam2' has an interface that involves unsafe types}}
81-
// expected-note@+2{{add '@unsafe' to indicate that this declaration is unsafe to use}}{{1-1=@unsafe }}
82-
// expected-note@+1{{add '@safe' to indicate that this declaration is memory-safe to use}}{{1-1=@safe }}
8379
@available(SwiftStdlib 5.8, *)
84-
func useUnsafeParam2(x: UnsafeReference) { // expected-note{{reference to unsafe class 'UnsafeReference'}}
80+
func useUnsafeParam2(x: UnsafeReference) {
81+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
82+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
8583
}
8684

87-
// expected-warning@+3{{global function 'useUnsafeParam3' has an interface that involves unsafe types}}
88-
// expected-note@+2{{add '@unsafe' to indicate that this declaration is unsafe to use}}{{1-1=@unsafe }}
89-
// expected-note@+1{{add '@safe' to indicate that this declaration is memory-safe to use}}{{1-1=@safe }}
90-
func useUnsafeParam3(x: UnknownEscapabilityAggregate) { // expected-note{{reference to unsafe struct 'UnknownEscapabilityAggregate'}}
85+
func useUnsafeParam3(x: UnknownEscapabilityAggregate) {
86+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
87+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
9188
}
9289

9390
func useSafeParams(x: Owner, y: View, z: SafeEscapableAggregate, c: MyContainer) {
@@ -101,10 +98,9 @@ func useCfType(x: CFArray) {
10198
func useString(x: std.string) {
10299
}
103100

104-
// expected-warning@+3{{global function 'useVecOfPtr' has an interface}}
105-
// expected-note@+2{{add '@unsafe' to indicate that this declaration is unsafe to use}}{{1-1=@unsafe }}
106-
// expected-note@+1{{add '@safe' to indicate that this declaration is memory-safe to use}}{1-1=@safe }}
107-
func useVecOfPtr(x: VecOfPtr) { // expected-note{{reference to unsafe type alias 'VecOfPtr'}}
101+
func useVecOfPtr(x: VecOfPtr) {
102+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
103+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
108104
}
109105

110106
func useVecOfInt(x: VecOfInt) {
@@ -113,22 +109,19 @@ func useVecOfInt(x: VecOfInt) {
113109
func useSafeTuple(x: SafeTuple) {
114110
}
115111

116-
// expected-warning@+3{{global function 'useUnsafeTuple' has an interface that involves unsafe types}}
117-
// expected-note@+2{{add '@unsafe' to indicate that this declaration is unsafe to use}}{{1-1=@unsafe }}
118-
// expected-note@+1{{add '@safe' to indicate that this declaration is memory-safe to use}}{1-1=@safe }}
119-
func useUnsafeTuple(x: UnsafeTuple) { // expected-note{{reference to unsafe type alias 'UnsafeTuple'}}
112+
func useUnsafeTuple(x: UnsafeTuple) {
113+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
114+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
120115
}
121116

122-
// expected-warning@+3{{global function 'useCppSpan' has an interface that involves unsafe types}}
123-
// expected-note@+2{{add '@unsafe' to indicate that this declaration is unsafe to use}}{{1-1=@unsafe }}
124-
// expected-note@+1{{add '@safe' to indicate that this declaration is memory-safe to use}}{1-1=@safe }}
125-
func useCppSpan(x: SpanOfInt) { // expected-note{{reference to unsafe type alias 'SpanOfInt'}}
117+
func useCppSpan(x: SpanOfInt) {
118+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
119+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
126120
}
127121

128-
// expected-warning@+3{{global function 'useCppSpan2' has an interface that involves unsafe types}}
129-
// expected-note@+2{{add '@unsafe' to indicate that this declaration is unsafe to use}}{{1-1=@unsafe }}
130-
// expected-note@+1{{add '@safe' to indicate that this declaration is memory-safe to use}}{1-1=@safe }}
131-
func useCppSpan2(x: SpanOfIntAlias) { // expected-note{{reference to unsafe type alias 'SpanOfIntAlias'}}
122+
func useCppSpan2(x: SpanOfIntAlias) {
123+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
124+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
132125
}
133126

134127
func useSafeLifetimeAnnotated(v: View) {

test/Unsafe/Inputs/unsafe_decls.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,62 @@ struct __attribute__((swift_attr("unsafe"))) UnsafeType {
55
};
66

77
void print_ints(int *ptr, int count);
8+
9+
#define _CXX_INTEROP_STRINGIFY(_x) #_x
10+
11+
#define SWIFT_SHARED_REFERENCE(_retain, _release) \
12+
__attribute__((swift_attr("import_reference"))) \
13+
__attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(retain:_retain)))) \
14+
__attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(release:_release))))
15+
16+
#define SWIFT_SAFE __attribute__((swift_attr("@safe")))
17+
#define SWIFT_UNSAFE __attribute__((swift_attr("@unsafe")))
18+
19+
struct NoPointers {
20+
float x, y, z;
21+
};
22+
23+
union NoPointersUnion {
24+
float x;
25+
double y;
26+
};
27+
28+
struct NoPointersUnsafe {
29+
float x, y, z;
30+
} SWIFT_UNSAFE;
31+
32+
struct HasPointers {
33+
float *numbers;
34+
};
35+
36+
37+
union HasPointersUnion {
38+
float *numbers;
39+
double x;
40+
};
41+
42+
struct HasPointersSafe {
43+
float *numbers;
44+
} SWIFT_SAFE;
45+
46+
struct RefCountedType {
47+
void *ptr;
48+
} SWIFT_SHARED_REFERENCE(RCRetain, RCRelease);
49+
50+
struct RefCountedType *RCRetain(struct RefCountedType *object);
51+
void RCRelease(struct RefCountedType *object);
52+
53+
struct HasRefCounted {
54+
struct RefCountedType *ref;
55+
};
56+
57+
struct ListNode {
58+
double data;
59+
struct ListNode *next;
60+
};
61+
62+
enum SomeColor {
63+
SCRed,
64+
SCGreen,
65+
SCBlue
66+
};

test/Unsafe/unsafe_c_imports.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -I %S/Inputs
2+
3+
// REQUIRES: swift_feature_AllowUnsafeAttribute
4+
// REQUIRES: swift_feature_WarnUnsafe
5+
6+
import unsafe_decls
7+
8+
// expected-warning@+3{{struct 'StoreAllTheThings' has storage involving unsafe types}}
9+
// expected-note@+2{{add '@unsafe' if this type is also unsafe to use}}
10+
// expected-note@+1{{add '@safe' if this type encapsulates the unsafe storage in a safe interface}}
11+
struct StoreAllTheThings {
12+
let np: NoPointers
13+
let npu: NoPointersUnion
14+
let npu2: NoPointersUnsafe // expected-note{{property 'npu2' involves unsafe type 'NoPointersUnsafe'}}
15+
16+
let hp: HasPointers // expected-note{{property 'hp' involves unsafe type 'HasPointers'}}
17+
let hpu: HasPointersUnion // expected-note{{property 'hpu' involves unsafe type 'HasPointersUnion'}}
18+
let nps: HasPointersSafe
19+
20+
let hrc: HasRefCounted
21+
22+
let ln: ListNode // expected-note{{property 'ln' involves unsafe type 'ListNode'}}
23+
24+
let sc: SomeColor
25+
};

0 commit comments

Comments
 (0)