Skip to content

fix: Make field types invariant for soundness #2539

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 1 commit into from
Oct 20, 2022
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
11 changes: 9 additions & 2 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3331,8 +3331,15 @@ export class Resolver extends DiagnosticEmitter {
}
}

// assignability
if (!fieldType.isStrictlyAssignableTo(existingField.type)) {
// assignability (to guarantee soundness, field types must be invariant)
// see also Wasm GC, where mutable fields are invariant for this reason
//
// class Animal { sibling: Animal; }
// class Cat extends Animal { sibling: Cat; } // covariance
// class Dog extends Animal { sibling: Dog; } // is unsound
// (<Animal>new Cat()).sibling = new Dog(); // → Cat with Dog sibling
//
if (fieldType != existingField.type) {
this.errorRelated(
DiagnosticCode.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2,
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
Expand Down
3 changes: 2 additions & 1 deletion tests/compiler/duplicate-field-errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"TS2325: Property 'protPriv' is private in type 'duplicate-field-errors/B' but not in type 'duplicate-field-errors/A'.",
"TS2325: Property 'pubPriv' is private in type 'duplicate-field-errors/B' but not in type 'duplicate-field-errors/A'.",
"TS2444: Property 'pubProt' is protected in type 'duplicate-field-errors/B' but public in type 'duplicate-field-errors/A'.",
"TS2300: Duplicate identifier 'method'."
"TS2300: Duplicate identifier 'method'.",
"Property 'sibling' in type 'duplicate-field-errors/Cat' is not assignable to the same property in base type 'duplicate-field-errors/Animal'."
]
}
8 changes: 8 additions & 0 deletions tests/compiler/duplicate-field-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,15 @@ class B extends A {
public method: i32;
}

class Animal {
sibling: Animal | null;
}
class Cat extends Animal {
sibling: Cat | null; // covariance is unsound
}

export function test(): void {
new A();
new B();
new Cat();
}
113 changes: 32 additions & 81 deletions tests/compiler/duplicate-fields.debug.wat
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
(type $i32_=>_none (func (param i32)))
(type $none_=>_none (func))
(type $i32_i32_i32_=>_none (func (param i32 i32 i32)))
(type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
(type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32)))
(type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
(type $none_=>_i32 (func (result i32)))
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
(global $~lib/rt/itcms/total (mut i32) (i32.const 0))
Expand All @@ -26,9 +26,9 @@
(global $duplicate-fields/foo (mut i32) (i32.const 0))
(global $duplicate-fields/raz (mut i32) (i32.const 0))
(global $~lib/rt/__rtti_base i32 (i32.const 480))
(global $~lib/memory/__data_end i32 (i32.const 572))
(global $~lib/memory/__stack_pointer (mut i32) (i32.const 16956))
(global $~lib/memory/__heap_base i32 (i32.const 16956))
(global $~lib/memory/__data_end i32 (i32.const 564))
(global $~lib/memory/__stack_pointer (mut i32) (i32.const 16948))
(global $~lib/memory/__heap_base i32 (i32.const 16948))
(memory $0 1)
(data (i32.const 12) "<\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00(\00\00\00A\00l\00l\00o\00c\00a\00t\00i\00o\00n\00 \00t\00o\00o\00 \00l\00a\00r\00g\00e\00\00\00\00\00")
(data (i32.const 76) "<\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00 \00\00\00~\00l\00i\00b\00/\00r\00t\00/\00i\00t\00c\00m\00s\00.\00t\00s\00\00\00\00\00\00\00\00\00\00\00\00\00")
Expand All @@ -39,7 +39,7 @@
(data (i32.const 320) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
(data (i32.const 348) "<\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\1e\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
(data (i32.const 412) "<\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00&\00\00\00d\00u\00p\00l\00i\00c\00a\00t\00e\00-\00f\00i\00e\00l\00d\00s\00.\00t\00s\00\00\00\00\00\00\00")
(data (i32.const 480) "\0b\00\00\00 \00\00\00\00\00\00\00 \00\00\00\00\00\00\00\00\00\00\00\00\00\00\00 \00\00\00\00\00\00\00 \00\00\00\03\00\00\00\00\00\00\00\00\00\00\00 \00\00\00\00\00\00\00\00\00\00\00\05\00\00\00 \00\00\00\06\00\00\00 \00\00\00\n\00\00\00 \00\00\00\00\00\00\00")
(data (i32.const 480) "\n\00\00\00 \00\00\00\00\00\00\00 \00\00\00\00\00\00\00\00\00\00\00\00\00\00\00 \00\00\00\00\00\00\00 \00\00\00\03\00\00\00\00\00\00\00\00\00\00\00 \00\00\00\00\00\00\00\00\00\00\00\05\00\00\00 \00\00\00\t\00\00\00 \00\00\00\00\00\00\00")
(table $0 1 1 funcref)
(elem $0 (i32.const 1))
(export "memory" (memory $0))
Expand Down Expand Up @@ -2217,11 +2217,6 @@
local.get $1
i32.store $0
)
(func $duplicate-fields/Bar#set:bar (param $0 i32) (param $1 i32)
local.get $0
local.get $1
i32.store $0 offset=4
)
(func $duplicate-fields/A3#set:protProt (param $0 i32) (param $1 i32)
local.get $0
local.get $1
Expand Down Expand Up @@ -2315,46 +2310,43 @@
block $invalid
block $duplicate-fields/A3
block $duplicate-fields/B3
block $duplicate-fields/Bar
block $duplicate-fields/B2
block $duplicate-fields/Foo
block $duplicate-fields/A2
block $duplicate-fields/B
block $duplicate-fields/A
block $~lib/arraybuffer/ArrayBufferView
block $~lib/string/String
block $~lib/arraybuffer/ArrayBuffer
local.get $0
i32.const 8
i32.sub
i32.load $0
br_table $~lib/arraybuffer/ArrayBuffer $~lib/string/String $~lib/arraybuffer/ArrayBufferView $duplicate-fields/A $duplicate-fields/B $duplicate-fields/A2 $duplicate-fields/Foo $duplicate-fields/B2 $duplicate-fields/Bar $duplicate-fields/B3 $duplicate-fields/A3 $invalid
end
return
block $duplicate-fields/B2
block $duplicate-fields/Foo
block $duplicate-fields/A2
block $duplicate-fields/B
block $duplicate-fields/A
block $~lib/arraybuffer/ArrayBufferView
block $~lib/string/String
block $~lib/arraybuffer/ArrayBuffer
local.get $0
i32.const 8
i32.sub
i32.load $0
br_table $~lib/arraybuffer/ArrayBuffer $~lib/string/String $~lib/arraybuffer/ArrayBufferView $duplicate-fields/A $duplicate-fields/B $duplicate-fields/A2 $duplicate-fields/Foo $duplicate-fields/B2 $duplicate-fields/B3 $duplicate-fields/A3 $invalid
end
return
end
local.get $0
local.get $1
call $~lib/arraybuffer/ArrayBufferView~visit
return
end
local.get $0
local.get $1
call $~lib/arraybuffer/ArrayBufferView~visit
return
end
return
end
local.get $0
local.get $1
call $duplicate-fields/A2~visit
return
end
local.get $0
local.get $1
call $duplicate-fields/A2~visit
return
end
local.get $0
local.get $1
call $duplicate-fields/B2~visit
return
end
local.get $0
local.get $1
call $duplicate-fields/B2~visit
return
end
return
Expand Down Expand Up @@ -2442,8 +2434,7 @@
i32.const 0
i32.const 0
i32.const 1
i32.const 2
call $duplicate-fields/Bar#constructor
call $duplicate-fields/Foo#constructor
local.set $0
global.get $~lib/memory/__stack_pointer
local.get $0
Expand All @@ -2453,8 +2444,8 @@
global.set $duplicate-fields/raz
global.get $duplicate-fields/raz
i32.load $0
i32.load $0 offset=4
i32.const 2
i32.load $0
i32.const 1
i32.eq
i32.eqz
if
Expand Down Expand Up @@ -2655,46 +2646,6 @@
global.set $~lib/memory/__stack_pointer
local.get $2
)
(func $duplicate-fields/Bar#constructor (param $this i32) (param $foo i32) (param $bar i32) (result i32)
(local $3 i32)
global.get $~lib/memory/__stack_pointer
i32.const 4
i32.sub
global.set $~lib/memory/__stack_pointer
call $~stack_check
global.get $~lib/memory/__stack_pointer
i32.const 0
i32.store $0
local.get $this
i32.eqz
if
global.get $~lib/memory/__stack_pointer
i32.const 8
i32.const 8
call $~lib/rt/itcms/__new
local.tee $this
i32.store $0
end
local.get $this
i32.const 0
call $duplicate-fields/Bar#set:bar
global.get $~lib/memory/__stack_pointer
local.get $this
local.get $foo
call $duplicate-fields/Foo#constructor
local.tee $this
i32.store $0
local.get $this
local.get $bar
call $duplicate-fields/Bar#set:bar
local.get $this
local.set $3
global.get $~lib/memory/__stack_pointer
i32.const 4
i32.add
global.set $~lib/memory/__stack_pointer
local.get $3
)
(func $duplicate-fields/A3#constructor (param $this i32) (result i32)
(local $1 i32)
global.get $~lib/memory/__stack_pointer
Expand All @@ -2710,7 +2661,7 @@
if
global.get $~lib/memory/__stack_pointer
i32.const 12
i32.const 10
i32.const 9
call $~lib/rt/itcms/__new
local.tee $this
i32.store $0
Expand Down Expand Up @@ -2747,7 +2698,7 @@
if
global.get $~lib/memory/__stack_pointer
i32.const 12
i32.const 9
i32.const 8
call $~lib/rt/itcms/__new
local.tee $this
i32.store $0
Expand Down
Loading