Skip to content

Patch a Huge Soundness Hole in Codable Synthesis #36060

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
379 changes: 162 additions & 217 deletions lib/Sema/DerivedConformanceCodable.cpp

Large diffs are not rendered by default.

40 changes: 15 additions & 25 deletions lib/Sema/DerivedConformances.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ bool DerivedConformance::derivesProtocolConformance(DeclContext *DC,
if (*derivableKind == KnownDerivableProtocolKind::Differentiable)
return true;

if (*derivableKind == KnownDerivableProtocolKind::Encodable) {
return canDeriveEncodable(Nominal);
}

if (*derivableKind == KnownDerivableProtocolKind::Decodable) {
return canDeriveDecodable(Nominal);
}

if (auto *enumDecl = dyn_cast<EnumDecl>(Nominal)) {
switch (*derivableKind) {
// The presence of a raw type is an explicit declaration that
Expand Down Expand Up @@ -137,31 +145,13 @@ bool DerivedConformance::derivesProtocolConformance(DeclContext *DC,
default:
return false;
}
} else if (isa<StructDecl>(Nominal) || isa<ClassDecl>(Nominal)) {
// Structs and classes can explicitly derive Encodable and Decodable
// conformance (explicitly meaning we can synthesize an implementation if
// a type conforms manually).
if (*derivableKind == KnownDerivableProtocolKind::Encodable ||
*derivableKind == KnownDerivableProtocolKind::Decodable) {
// FIXME: This is not actually correct. We cannot promise to always
// provide a witness here for all structs and classes. Unfortunately,
// figuring out whether this is actually possible requires much more
// context -- a TypeChecker and the parent decl context at least -- and is
// tightly coupled to the logic within DerivedConformance.
// This unfortunately means that we expect a witness even if one will not
// be produced, which requires DerivedConformance::deriveCodable to output
// its own diagnostics.
return true;
}

// Structs can explicitly derive Equatable conformance.
if (isa<StructDecl>(Nominal)) {
switch (*derivableKind) {
case KnownDerivableProtocolKind::Equatable:
return canDeriveEquatable(DC, Nominal);
default:
return false;
}
} else if (isa<StructDecl>(Nominal)) {
switch (*derivableKind) {
case KnownDerivableProtocolKind::Equatable:
// Structs can explicitly derive Equatable conformance.
return canDeriveEquatable(DC, Nominal);
default:
return false;
}
}
return false;
Expand Down
5 changes: 5 additions & 0 deletions lib/Sema/DerivedConformances.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@ class DerivedConformance {
/// \returns the derived member, which will also be added to the type.
ValueDecl *deriveBridgedNSError(ValueDecl *requirement);

/// Determine if \c Encodable can be derived for the given type.
static bool canDeriveEncodable(NominalTypeDecl *NTD);
/// Determine if \c Decodable can be derived for the given type.
static bool canDeriveDecodable(NominalTypeDecl *NTD);

/// Derive a CodingKey requirement for an enum type.
///
/// \returns the derived member, which will also be added to the type.
Expand Down
8 changes: 6 additions & 2 deletions lib/Sema/TypeCheckPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,8 +732,12 @@ static Type validateTypedPattern(TypedPattern *TP, TypeResolution resolution) {
Type TypeChecker::typeCheckPattern(ContextualPattern pattern) {
DeclContext *dc = pattern.getDeclContext();
ASTContext &ctx = dc->getASTContext();
return evaluateOrDefault(
ctx.evaluator, PatternTypeRequest{pattern}, ErrorType::get(ctx));
if (auto type = evaluateOrDefault(ctx.evaluator, PatternTypeRequest{pattern},
Type())) {
return type;
}

return ErrorType::get(ctx);
}

/// Apply the contextual pattern's context to the type resolution options.
Expand Down
5 changes: 2 additions & 3 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1207,9 +1207,8 @@ static Type diagnoseUnknownType(TypeResolution resolution,
return I->second;
}

diags.diagnose(L, diag::cannot_find_type_in_scope,
comp->getNameRef())
.highlight(R);
diags.diagnose(L, diag::cannot_find_type_in_scope, comp->getNameRef())
.highlight(R);

return ErrorType::get(ctx);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,10 @@ class NonConformingClass : Codable { // expected-error {{type 'NonConformingClas
// These lines have to be within the NonConformingClass type because
// CodingKeys should be private.
func foo() {
// They should not get a CodingKeys type.
let _ = NonConformingClass.CodingKeys.self // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
let _ = NonConformingClass.CodingKeys.x // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
let _ = NonConformingClass.CodingKeys.y // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
let _ = NonConformingClass.CodingKeys.z // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
let _ = NonConformingClass.CodingKeys.self
let _ = NonConformingClass.CodingKeys.x
let _ = NonConformingClass.CodingKeys.y
let _ = NonConformingClass.CodingKeys.z // expected-error {{type 'NonConformingClass.CodingKeys' has no member 'z'}}
}
}

Expand All @@ -178,4 +177,4 @@ let _ = NonConformingClass.init(from:) // expected-error {{type 'NonConformingCl
let _ = NonConformingClass.encode(to:) // expected-error {{type 'NonConformingClass' has no member 'encode(to:)'}}

// They should not get a CodingKeys type.
let _ = NonConformingClass.CodingKeys.self // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
let _ = NonConformingClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,10 @@ struct NonConformingStruct : Codable { // expected-error {{type 'NonConformingSt
// These lines have to be within the NonConformingStruct type because
// CodingKeys should be private.
func foo() {
// They should not get a CodingKeys type.
let _ = NonConformingStruct.CodingKeys.self // expected-error {{type 'NonConformingStruct' has no member 'CodingKeys'}}
let _ = NonConformingStruct.CodingKeys.x // expected-error {{type 'NonConformingStruct' has no member 'CodingKeys'}}
let _ = NonConformingStruct.CodingKeys.y // expected-error {{type 'NonConformingStruct' has no member 'CodingKeys'}}
let _ = NonConformingStruct.CodingKeys.z // expected-error {{type 'NonConformingStruct' has no member 'CodingKeys'}}
let _ = NonConformingStruct.CodingKeys.self
let _ = NonConformingStruct.CodingKeys.x
let _ = NonConformingStruct.CodingKeys.y
let _ = NonConformingStruct.CodingKeys.z // expected-error {{type 'NonConformingStruct.CodingKeys' has no member 'z'}}
}
}

Expand All @@ -178,4 +177,4 @@ let _ = NonConformingStruct.init(from:) // expected-error {{type 'NonConformingS
let _ = NonConformingStruct.encode(to:) // expected-error {{type 'NonConformingStruct' has no member 'encode(to:)'}}

// They should not get a CodingKeys type.
let _ = NonConformingStruct.CodingKeys.self // expected-error {{type 'NonConformingStruct' has no member 'CodingKeys'}}
let _ = NonConformingStruct.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,19 @@ let _ = SimpleStruct.encode(to:)
let _ = SimpleStruct.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}

// rdar://problem/59655704
struct SR_12248_1: Codable { // expected-error {{type 'SR_12248_1' does not conform to protocol 'Encodable'}}
struct SR_12248_1: Codable { // expected-error {{type 'SR_12248_1' does not conform to protocol 'Encodable'}} expected-error {{type 'SR_12248_1' does not conform to protocol 'Decodable'}}
var x: Int // expected-note {{'x' previously declared here}}
var x: Int // expected-error {{invalid redeclaration of 'x'}}
// expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'Int' does not conform to 'Encodable'}}
// expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'Int' does not conform to 'Encodable'}}
}

struct SR_12248_2: Decodable {
struct SR_12248_2: Decodable { // expected-error {{type 'SR_12248_2' does not conform to protocol 'Decodable'}}
var x: Int // expected-note {{'x' previously declared here}}
var x: Int // expected-error {{invalid redeclaration of 'x'}}
}

struct SR_12248_3: Encodable {
struct SR_12248_3: Encodable { // expected-error {{type 'SR_12248_3' does not conform to protocol 'Encodable'}}
var x: Int // expected-note {{'x' previously declared here}}
var x: Int // expected-error {{invalid redeclaration of 'x'}}
}
2 changes: 0 additions & 2 deletions test/diagnostics/pretty-printed-diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ foo(b:
// CHECK: [[#LINE]] | struct B: Decodable {
// CHECK: | ^ error: type 'B' does not conform to protocol 'Decodable'
// CHECK: [[#LINE+1]] | let a: Foo
// CHECK: | ^ note: cannot automatically synthesize 'Decodable' because 'Foo' does not conform to 'Decodable'
// CHECK: [[#LINE+2]] | }
// CHECK: Swift.Decodable:2:5
// CHECK: 1 | public protocol Decodable {
// CHECK: 2 | init(from decoder: Decoder) throws
Expand Down