Skip to content

Commit fa71550

Browse files
authored
Don't import C++ class members that are protected or private (swiftlang#30233)
* Don't import C++ class members that are protected or private. We omit protected members in addition to private members because Swift structs can't inherit from C++ classes, so there's effectively no way to access them. * Check access specifiers centrally in importDeclImpl(). * Fix macOS build by using <stddef.h> instead of <cstddef>. Apparently, the macOS toolchain doesn't provide <cstddef>. <stddef.h> is used in test/Inputs/clang-importer-sdk/usr/include/macros.h, so I'm assuming it will be OK. (I don't unfortunately have a macOS machine to test on.) * Add comment explaining why we skip private and protected C++ class members.
1 parent e1b6eb6 commit fa71550

File tree

10 files changed

+214
-0
lines changed

10 files changed

+214
-0
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7731,6 +7731,17 @@ ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
77317731
bool &HadForwardDeclaration) {
77327732
assert(ClangDecl);
77337733

7734+
// Private and protected C++ class members should never be used, so we skip
7735+
// them entirely (instead of importing them with a corresponding Swift access
7736+
// level) to remove clutter from the module interface.
7737+
//
7738+
// We omit protected members in addition to private members because Swift
7739+
// structs can't inherit from C++ classes, so there's effectively no way to
7740+
// access them.
7741+
clang::AccessSpecifier access = ClangDecl->getAccess();
7742+
if (access == clang::AS_protected || access == clang::AS_private)
7743+
return nullptr;
7744+
77347745
bool SkippedOverTypedef = false;
77357746
Decl *Result = nullptr;
77367747
if (auto *UnderlyingDecl = canSkipOverTypedef(*this, ClangDecl,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
class PublicPrivate {
2+
public:
3+
int PublicMemberVar;
4+
static int PublicStaticMemberVar;
5+
void publicMemberFunc();
6+
7+
typedef int PublicTypedef;
8+
struct PublicStruct {};
9+
enum PublicEnum { PublicEnumValue1 };
10+
enum { PublicAnonymousEnumValue };
11+
enum PublicClosedEnum {
12+
PublicClosedEnumValue1
13+
} __attribute__((enum_extensibility(closed)));
14+
enum PublicOpenEnum {
15+
PublicOpenEnumValue1
16+
} __attribute__((enum_extensibility(open)));
17+
enum PublicFlagEnum {} __attribute__((flag_enum));
18+
19+
private:
20+
int PrivateMemberVar;
21+
static int PrivateStaticMemberVar;
22+
void privateMemberFunc() {}
23+
24+
typedef int PrivateTypedef;
25+
struct PrivateStruct {};
26+
enum PrivateEnum { PrivateEnumValue1 };
27+
enum { PrivateAnonymousEnumValue1 };
28+
enum PrivateClosedEnum {
29+
PrivateClosedEnumValue1
30+
} __attribute__((enum_extensibility(closed)));
31+
enum PrivateOpenEnum {
32+
PrivateOpenEnumValue1
33+
} __attribute__((enum_extensibility(open)));
34+
enum PrivateFlagEnum {} __attribute__((flag_enum));
35+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <stddef.h>
2+
#include <stdint.h>
3+
4+
class PrivateMemberLayout {
5+
uint32_t a;
6+
7+
public:
8+
uint32_t b;
9+
};
10+
11+
inline size_t sizeOfPrivateMemberLayout() {
12+
return sizeof(PrivateMemberLayout);
13+
}
14+
15+
inline size_t offsetOfPrivateMemberLayout_b() {
16+
return offsetof(PrivateMemberLayout, b);
17+
}

test/ClangImporter/Inputs/custom-modules/module.map

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ module script {
22
header "script.h"
33
}
44

5+
module AccessSpecifiers {
6+
header "AccessSpecifiers.h"
7+
export *
8+
}
9+
510
module AvailabilityExtras {
611
header "AvailabilityExtras.h"
712
export *
@@ -84,6 +89,11 @@ module LinkMusket {
8489
link framework "Barrel"
8590
}
8691

92+
module MemoryLayout {
93+
header "MemoryLayout.h"
94+
export *
95+
}
96+
8797
module MissingHeader {
8898
header "this-header-does-not-exist.h"
8999
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Test module interface produced for C++ access specifiers test.
2+
// In particular, we don't want any of the private members showing up here.
3+
4+
// RUN: %target-swift-ide-test -print-module -module-to-print=AccessSpecifiers -I %S/Inputs/ -source-filename=x -enable-cxx-interop | %FileCheck %s
5+
6+
// CHECK: struct PublicPrivate {
7+
// CHECK-NEXT: typealias PublicTypedef = Int32
8+
// CHECK-NEXT: struct PublicStruct {
9+
// CHECK-NEXT: init()
10+
// CHECK-NEXT: }
11+
// CHECK-NEXT: struct PublicEnum : Equatable, RawRepresentable {
12+
// CHECK-NEXT: init(_ rawValue: UInt32)
13+
// CHECK-NEXT: init(rawValue: UInt32)
14+
// CHECK-NEXT: var rawValue: UInt32
15+
// CHECK-NEXT: typealias RawValue = UInt32
16+
// CHECK-NEXT: }
17+
// CHECK-NEXT: @frozen enum PublicClosedEnum : UInt32 {
18+
// CHECK-NEXT: init?(rawValue: UInt32)
19+
// CHECK-NEXT: var rawValue: UInt32 { get }
20+
// CHECK-NEXT: typealias RawValue = UInt32
21+
// CHECK-NEXT: case value1
22+
// CHECK-NEXT: @available(swift, obsoleted: 3, renamed: "value1")
23+
// CHECK-NEXT: static var Value1: PublicPrivate.PublicClosedEnum { get }
24+
// CHECK-NEXT: }
25+
// CHECK-NEXT: enum PublicOpenEnum : UInt32 {
26+
// CHECK-NEXT: init?(rawValue: UInt32)
27+
// CHECK-NEXT: var rawValue: UInt32 { get }
28+
// CHECK-NEXT: typealias RawValue = UInt32
29+
// CHECK-NEXT: case value1
30+
// CHECK-NEXT: @available(swift, obsoleted: 3, renamed: "value1")
31+
// CHECK-NEXT: static var Value1: PublicPrivate.PublicOpenEnum { get }
32+
// CHECK-NEXT: }
33+
// CHECK-NEXT: struct PublicFlagEnum : OptionSet {
34+
// CHECK-NEXT: init(rawValue: UInt32)
35+
// CHECK-NEXT: let rawValue: UInt32
36+
// CHECK-NEXT: typealias RawValue = UInt32
37+
// CHECK-NEXT: typealias Element = PublicPrivate.PublicFlagEnum
38+
// CHECK-NEXT: typealias ArrayLiteralElement = PublicPrivate.PublicFlagEnum
39+
// CHECK-NEXT: }
40+
// CHECK-NEXT: var PublicMemberVar: Int32
41+
// CHECK-NEXT: init()
42+
// CHECK-NEXT: mutating func publicMemberFunc()
43+
// CHECK-NEXT: }
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Test that C++ access specifiers are honored, i.e. private members aren't
2+
// imported.
3+
4+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs/custom-modules/ -enable-cxx-interop
5+
6+
import AccessSpecifiers
7+
8+
var v = PublicPrivate()
9+
10+
// Can access all public members and types.
11+
12+
v.PublicMemberVar = 1
13+
// TODO: Static member variables don't appear to be imported correctly yet. Once
14+
// they are, verify that PublicStaticMemberVar is accessible.
15+
// PublicPrivate.PublicStaticMemberVar = 1
16+
v.publicMemberFunc()
17+
18+
var publicTypedefVar: PublicPrivate.PublicTypedef
19+
var publicStructVar: PublicPrivate.PublicStruct
20+
var publicEnumVar: PublicPrivate.PublicEnum
21+
// TODO: These enum values don't yet appear to be imported correctly into the
22+
// scope of PublicPrivate yet. Once they are, verify that they are accessible.
23+
// print(PublicPrivate.PublicEnumValue1)
24+
// print(PublicPrivate.PublicAnonymousEnumValue1)
25+
var publicClosedEnumVar: PublicPrivate.PublicClosedEnum
26+
var publicOpenEnumVar: PublicPrivate.PublicOpenEnum
27+
var publicFlagEnumVar: PublicPrivate.PublicFlagEnum
28+
29+
// Cannot access any private members and types.
30+
31+
v.PrivateMemberVar = 1 // expected-error {{value of type 'PublicPrivate' has no member 'PrivateMemberVar'}}
32+
// TODO: This gives the expected error, but only because static member variables
33+
// (private or public) aren't imported at all. Once that is fixed, remove this
34+
PublicPrivate.PrivateStaticMemberVar = 1 // expected-error {{'PublicPrivate' has no member 'PrivateStaticMemberVar'}}
35+
v.privateMemberFunc() // expected-error {{value of type 'PublicPrivate' has no member 'privateMemberFunc'}}
36+
37+
var privateTypedefVar: PublicPrivate.PrivateTypedef // expected-error {{'PrivateTypedef' is not a member type of 'PublicPrivate'}}
38+
var privateStructVar: PublicPrivate.PrivateStruct // expected-error {{'PrivateStruct' is not a member type of 'PublicPrivate'}}
39+
var privateEnumVar: PublicPrivate.PrivateEnum // expected-error {{'PrivateEnum' is not a member type of 'PublicPrivate'}}
40+
// TODO: PrivateEnumValue1 and PrivateAnonymousEnumValue1 give the expected
41+
// error, but only because these types of enums (private or public) aren't
42+
// currently imported at all. Once that is fixed, remove this TODO.
43+
print(PublicPrivate.PrivateEnumValue1) // expected-error {{'PublicPrivate' has no member 'PrivateEnumValue1'}}
44+
print(PublicPrivate.PrivateAnonymousEnumValue1) // expected-error {{'PublicPrivate' has no member 'PrivateAnonymousEnumValue1'}}
45+
var privateClosedEnumVar: PublicPrivate.PrivateClosedEnum // expected-error {{'PrivateClosedEnum' is not a member type of 'PublicPrivate'}}
46+
var privateOpenEnumVar: PublicPrivate.PrivateOpenEnum // expected-error {{'PrivateOpenEnum' is not a member type of 'PublicPrivate'}}
47+
var privateFlagEnumVar: PublicPrivate.PrivateFlagEnum // expected-error {{'PrivateFlagEnum' is not a member type of 'PublicPrivate'}}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift %s -I %S/Inputs/custom-modules/ -o %t/class_layout -Xfrontend -enable-cxx-interop
3+
// RUN: %target-codesign %t/class_layout
4+
// RUN: %target-run %t/class_layout 2&>1
5+
//
6+
// REQUIRES: executable_test
7+
8+
import MemoryLayout
9+
import StdlibUnittest
10+
11+
var MemoryLayoutTestSuite = TestSuite("MemoryLayout")
12+
13+
MemoryLayoutTestSuite.test("PrivateMemberLayout") {
14+
// Check that, even though the private member variable 'a' is not imported, we
15+
// still use the same memory layout as Clang.
16+
expectEqual(sizeOfPrivateMemberLayout(),
17+
MemoryLayout<PrivateMemberLayout>.size)
18+
expectEqual(sizeOfPrivateMemberLayout(),
19+
MemoryLayout<PrivateMemberLayout>.stride)
20+
expectEqual(offsetOfPrivateMemberLayout_b(),
21+
MemoryLayout<PrivateMemberLayout>.offset(of: \PrivateMemberLayout.b));
22+
}
23+
24+
runAllTests()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %target-swift-frontend -I %S/Inputs/custom-modules/ -enable-cxx-interop -emit-ir -o - %s | %FileCheck %s
2+
3+
import MemoryLayout
4+
5+
var v = PrivateMemberLayout()
6+
// Check that even though the private member variable `a` is not imported, it is
7+
// still taken into account for computing the layout of the class. In other
8+
// words, we use Clang's, not Swift's view of the class to compute the memory
9+
// layout.
10+
// The important point here is that the second index is 1, not 0.
11+
// CHECK: store i32 42, i32* getelementptr inbounds (%TSo19PrivateMemberLayoutV, %TSo19PrivateMemberLayoutV* @"$s4main1vSo19PrivateMemberLayoutVvp", i32 0, i32 1, i32 0), align 4
12+
v.b = 42
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module AccessSpecifiers {
2+
header "access_specifiers.h"
3+
}
4+
5+
module MemoryLayout {
6+
header "memory_layout.h"
7+
}

tools/swift-ide-test/swift-ide-test.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,11 @@ static llvm::cl::opt<bool>
697697
llvm::cl::desc("Disable ObjC interop."),
698698
llvm::cl::cat(Category), llvm::cl::init(false));
699699

700+
static llvm::cl::opt<bool>
701+
EnableCxxInterop("enable-cxx-interop",
702+
llvm::cl::desc("Enable C++ interop."),
703+
llvm::cl::cat(Category), llvm::cl::init(false));
704+
700705
static llvm::cl::opt<std::string>
701706
GraphVisPath("output-request-graphviz",
702707
llvm::cl::desc("Emit GraphViz output visualizing the request graph."),
@@ -3373,6 +3378,9 @@ int main(int argc, char *argv[]) {
33733378
InitInvok.getLangOptions().EnableObjCInterop =
33743379
llvm::Triple(options::Triple).isOSDarwin();
33753380
}
3381+
if (options::EnableCxxInterop) {
3382+
InitInvok.getLangOptions().EnableCXXInterop = true;
3383+
}
33763384

33773385
// We disable source location resolutions from .swiftsourceinfo files by
33783386
// default to match sourcekitd-test's and ide clients' expected behavior

0 commit comments

Comments
 (0)