Skip to content

Commit

Permalink
[compiler-v2] Adapt reference safety to quirks of v1 bytecode verifie…
Browse files Browse the repository at this point in the history
…r semantics (aptos-labs#12757)

* [compiler-v2] Adapt reference safety to quirks of v1 bytecode verifier semantics

This does a number of adaptions to reference safety to deal with some of the unexpected quirks of the v1 borrow semantics. Additional cases are now detected which are logical safe but anyway rejected by v1. Also, the treatment of freeze has been updated to relax some corner cases which led to failures in framework compilation. With this PR, all known bytecode verification failures are now detected except of a few which are sensitive to optimizations on and which pass without optimizations but not without (see aptos-labs#12756).

This closes aptos-labs#12301 and closes aptos-labs#12701

- Fixes `borrow_global` borrow edge to carry a code offset, to be able to distinguish multiple global borrows with different addresses. Since the address in `borrow_global<R>(addr)` is dynamic, the reference checker cannot know whether to borrows results in the same reference. We already tackle this situation for calls which derive reference by adding the code offset, and now do the same for borrow global.
- Makes assignment and let a checkpoint for borrow safety. Until now, we have considered those operations as no-ops for safety (which is safe from memory perspective), but v1 insists on borrow safety for those ops.
- Adds a new check to borrow safety for mut refs which are duplicated and live beyond a safety check point. A particular quirk in v1 is that a mut ref which was used to derive another mut ref is allowed, but one which is duplicated is not. Since the information from which temps refs have been derived is not present in the borrow graph, which is designed to abstract from mut ref copies, we needed to introduce a new field in the borrow state to track this information. Specifically, the following is _not_ allowed in v1: `let r = &mut s; let r1 = r; let x = &mut r.f; *x; *r1`. However, this is allowed: `let r = &mut s; let x = &mut r.f; *x; *r`. The v1 logic behind this seems to be that `x` is derived from `r` but not (for the first example) from `r1`.

* Addessing reviewer comments
  • Loading branch information
wrwg authored Apr 3, 2024
1 parent 8c31f4d commit b448c40
Show file tree
Hide file tree
Showing 41 changed files with 879 additions and 238 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,16 @@ fun m::f($t0: u8, $t1: &vector<u64>): u64 {
#
5: $t6 := borrow_global<m::R>($t7)
# live vars: $t6
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[]}
# locals: {$t6=@501}
# globals: {m::R=@500}
#
6: $t3 := borrow_field<m::R>.data($t6)
# live vars: $t3
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[]}
# locals: {$t3=@601}
# globals: {m::R=@500}
# derived-from: @601=$t6
#
7: goto 10
# live vars: $t1
Expand All @@ -144,27 +145,31 @@ fun m::f($t0: u8, $t1: &vector<u64>): u64 {
#
9: $t3 := infer($t1)
# live vars: $t3
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# locals: {$t3=@601}
# globals: {m::R=@500}
# derived-from: @601=$t6
#
10: label L2
# live vars: $t3
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# locals: {$t3=@601}
# globals: {m::R=@500}
# derived-from: @601=$t6
#
11: $t9 := 0
# live vars: $t3, $t9
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# locals: {$t3=@601}
# globals: {m::R=@500}
# derived-from: @601=$t6
#
12: $t8 := vector::borrow<u64>($t3, $t9)
# live vars: $t8
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[call(false) -> @C00],@C00=derived[],@1000000=external[borrow(false) -> @601]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[call(false, 12) -> @C00],@C00=derived[],@1000000=external[borrow(false) -> @601]}
# locals: {$t8=@C00}
# globals: {m::R=@500}
# derived-from: @601=$t6,@C00=$t3
#
13: $t2 := read_ref($t8)
# live vars: $t2
Expand Down Expand Up @@ -231,16 +236,17 @@ fun m::f($t0: u8, $t1: &vector<u64>): u64 {
5: $t6 := borrow_global<m::R>($t7)
# abort state: {returns,aborts}
# live vars: $t6
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[]}
# locals: {$t6=@501}
# globals: {m::R=@500}
#
6: $t3 := borrow_field<m::R>.data($t6)
# abort state: {returns,aborts}
# live vars: $t3
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[]}
# locals: {$t3=@601}
# globals: {m::R=@500}
# derived-from: @601=$t6
#
7: goto 10
# abort state: {returns,aborts}
Expand All @@ -259,30 +265,34 @@ fun m::f($t0: u8, $t1: &vector<u64>): u64 {
9: $t3 := infer($t1)
# abort state: {returns,aborts}
# live vars: $t3
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# locals: {$t3=@601}
# globals: {m::R=@500}
# derived-from: @601=$t6
#
10: label L2
# abort state: {returns,aborts}
# live vars: $t3
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# locals: {$t3=@601}
# globals: {m::R=@500}
# derived-from: @601=$t6
#
11: $t9 := 0
# abort state: {returns,aborts}
# live vars: $t3, $t9
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[],@1000000=external[borrow(false) -> @601]}
# locals: {$t3=@601}
# globals: {m::R=@500}
# derived-from: @601=$t6
#
12: $t8 := vector::borrow<u64>($t3, $t9)
# abort state: {returns}
# live vars: $t8
# graph: {@500=global<m::R>[borrow_global(false) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[call(false) -> @C00],@C00=derived[],@1000000=external[borrow(false) -> @601]}
# graph: {@500=global<m::R>[borrow_global(false, 5) -> @501],@501=derived[borrow_field(false) -> @601],@601=derived[call(false, 12) -> @C00],@C00=derived[],@1000000=external[borrow(false) -> @601]}
# locals: {$t8=@C00}
# globals: {m::R=@500}
# derived-from: @601=$t6,@C00=$t3
#
13: $t2 := read_ref($t8)
# abort state: {returns}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,10 @@ fun m::g() {
#
2: $t2 := m::f($t1)
# live vars: $t0, $t2
# graph: {@100=local($t0)[borrow(true) -> @101],@101=derived[call(true) -> @200],@200=derived[]}
# graph: {@100=local($t0)[borrow(true) -> @101],@101=derived[call(true, 2) -> @200],@200=derived[]}
# locals: {$t0=@100,$t2=@200}
# globals: {}
# derived-from: @200=$t1
#
3: $t1 := infer($t2)
# live vars: $t0
Expand Down Expand Up @@ -169,9 +170,10 @@ fun m::g() {
2: $t2 := m::f($t1)
# abort state: {returns}
# live vars: $t0, $t2
# graph: {@100=local($t0)[borrow(true) -> @101],@101=derived[call(true) -> @200],@200=derived[]}
# graph: {@100=local($t0)[borrow(true) -> @101],@101=derived[call(true, 2) -> @200],@200=derived[]}
# locals: {$t0=@100,$t2=@200}
# globals: {}
# derived-from: @200=$t1
#
3: $t1 := infer($t2)
# abort state: {returns}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,17 @@ fun m::test_for_each_mut() {
#
9: $t6 := vector::borrow_mut<u64>($t4, $t1)
# live vars: $t0, $t1, $t2, $t4, $t6
# graph: {@200=local($t0)[borrow(true) -> @401],@401=derived[call(true) -> @900],@900=derived[]}
# graph: {@200=local($t0)[borrow(true) -> @401],@401=derived[call(true, 9) -> @900],@900=derived[]}
# locals: {$t0=@200,$t4=@401,$t6=@900}
# globals: {}
# derived-from: @900=$t4
#
10: $t7 := 2
# live vars: $t0, $t1, $t2, $t4, $t6, $t7
# graph: {@200=local($t0)[borrow(true) -> @401],@401=derived[call(true) -> @900],@900=derived[]}
# graph: {@200=local($t0)[borrow(true) -> @401],@401=derived[call(true, 9) -> @900],@900=derived[]}
# locals: {$t0=@200,$t4=@401,$t6=@900}
# globals: {}
# derived-from: @900=$t4
#
11: write_ref($t6, $t7)
# live vars: $t0, $t1, $t2, $t4
Expand Down Expand Up @@ -423,16 +425,18 @@ fun m::test_for_each_mut() {
9: $t6 := vector::borrow_mut<u64>($t4, $t1)
# abort state: {returns,aborts}
# live vars: $t0, $t1, $t2, $t4, $t6
# graph: {@200=local($t0)[borrow(true) -> @401],@401=derived[call(true) -> @900],@900=derived[]}
# graph: {@200=local($t0)[borrow(true) -> @401],@401=derived[call(true, 9) -> @900],@900=derived[]}
# locals: {$t0=@200,$t4=@401,$t6=@900}
# globals: {}
# derived-from: @900=$t4
#
10: $t7 := 2
# abort state: {returns,aborts}
# live vars: $t0, $t1, $t2, $t4, $t6, $t7
# graph: {@200=local($t0)[borrow(true) -> @401],@401=derived[call(true) -> @900],@900=derived[]}
# graph: {@200=local($t0)[borrow(true) -> @401],@401=derived[call(true, 9) -> @900],@900=derived[]}
# locals: {$t0=@200,$t4=@401,$t6=@900}
# globals: {}
# derived-from: @900=$t4
#
11: write_ref($t6, $t7)
# abort state: {returns,aborts}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,21 @@ public fun m::new_scalar_from_u8($t0: u8): m::Scalar {
# graph: {@100=local($t2)[borrow(true) -> @101],@101=derived[borrow_field(true) -> @201],@201=derived[]}
# locals: {$t2=@100,$t4=@201}
# globals: {}
# derived-from: @201=$t5
#
3: $t6 := 0
# live vars: $t0, $t2, $t4, $t6
# graph: {@100=local($t2)[borrow(true) -> @101],@101=derived[borrow_field(true) -> @201],@201=derived[]}
# locals: {$t2=@100,$t4=@201}
# globals: {}
# derived-from: @201=$t5
#
4: $t3 := vector::borrow_mut<u8>($t4, $t6)
# live vars: $t0, $t2, $t3
# graph: {@100=local($t2)[borrow(true) -> @101],@101=derived[borrow_field(true) -> @201],@201=derived[call(true) -> @400],@400=derived[]}
# graph: {@100=local($t2)[borrow(true) -> @101],@101=derived[borrow_field(true) -> @201],@201=derived[call(true, 4) -> @400],@400=derived[]}
# locals: {$t2=@100,$t3=@400}
# globals: {}
# derived-from: @201=$t5,@400=$t4
#
5: write_ref($t3, $t0)
# live vars: $t2
Expand Down Expand Up @@ -190,20 +193,23 @@ public fun m::new_scalar_from_u8($t0: u8): m::Scalar {
# graph: {@100=local($t2)[borrow(true) -> @101],@101=derived[borrow_field(true) -> @201],@201=derived[]}
# locals: {$t2=@100,$t4=@201}
# globals: {}
# derived-from: @201=$t5
#
3: $t6 := 0
# abort state: {returns,aborts}
# live vars: $t0, $t2, $t4, $t6
# graph: {@100=local($t2)[borrow(true) -> @101],@101=derived[borrow_field(true) -> @201],@201=derived[]}
# locals: {$t2=@100,$t4=@201}
# globals: {}
# derived-from: @201=$t5
#
4: $t3 := vector::borrow_mut<u8>($t4, $t6)
# abort state: {returns}
# live vars: $t0, $t2, $t3
# graph: {@100=local($t2)[borrow(true) -> @101],@101=derived[borrow_field(true) -> @201],@201=derived[call(true) -> @400],@400=derived[]}
# graph: {@100=local($t2)[borrow(true) -> @101],@101=derived[borrow_field(true) -> @201],@201=derived[call(true, 4) -> @400],@400=derived[]}
# locals: {$t2=@100,$t3=@400}
# globals: {}
# derived-from: @201=$t5,@400=$t4
#
5: write_ref($t3, $t0)
# abort state: {returns}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ fun m::test($t0: u64): u64 {
6: return $t1
}


Diagnostics:
error: mutable reference in local `a` requires exclusive access but is borrowed
┌─ tests/copy-propagation/mut_refs_3.move:7:9
7 │ *a = 0;
│ ^^^^^^ requirement enforced here
8 │ *c
│ -- conflicting reference `c` used here

============ after DeadStoreElimination: ================

[variant baseline]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

Diagnostics:
error: mutable reference in local `a` requires exclusive access but is borrowed
┌─ tests/reference-safety/multiple_use_bug_12301.move:8:9
8 │ *a = 0;
│ ^^^^^^ requirement enforced here
9 │ *c
│ -- conflicting reference `c` used here

error: mutable reference in local `a` requires exclusive access but is borrowed
┌─ tests/reference-safety/multiple_use_bug_12301.move:17:9
17 │ *a = 0;
│ ^^^^^^ requirement enforced here
18 │ *k = 1;
19 │ *c
│ -- conflicting reference `c` used here
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// See also #12301
module 0x42::m {
// Expected to be invalid
public fun test1(p: u64): u64 {
let a = &mut p;
let b = a;
let c = b;
*a = 0;
*c
}
// Expected to be invalid
public fun test2(p: u64): u64 {
let a = &mut p;
let k = &mut p;
let b = a;
let c = b;
*a = 0;
*k = 1;
*c
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

Diagnostics:
error: mutable reference in local `a` requires exclusive access but is borrowed
┌─ tests/reference-safety/multiple_use_bug_12301.move:8:9
8 │ *a = 0;
│ ^^^^^^ requirement enforced here
9 │ *c
│ -- conflicting reference `c` used here

error: mutable reference in local `a` requires exclusive access but is borrowed
┌─ tests/reference-safety/multiple_use_bug_12301.move:17:9
17 │ *a = 0;
│ ^^^^^^ requirement enforced here
18 │ *k = 1;
19 │ *c
│ -- conflicting reference `c` used here
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Diagnostics:
error: mutable reference in local `f` requires exclusive access but is borrowed
┌─ tests/reference-safety/mut_borrow_after_invalid.move:8:9
8 │ *f;
│ ^^ requirement enforced here
9 │ *s1;
│ --- conflicting reference `s1` used here

error: mutable reference in local `a` requires exclusive access but is borrowed
┌─ tests/reference-safety/mut_borrow_after_invalid.move:16:9
16 │ *a = 0;
│ ^^^^^^ requirement enforced here
17 │ *b
│ -- conflicting reference `b` used here

error: mutable reference in local `a` requires exclusive access but is borrowed
┌─ tests/reference-safety/mut_borrow_after_invalid.move:25:9
25 │ *a = 0;
│ ^^^^^^ requirement enforced here
26 │ *c
│ -- conflicting reference `c` used here
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module 0x42::m {
struct S has copy, drop { f: u64, g: u64 }

// bytecode verification fails
fun t1(s: &mut S) {
let s1 = s;
let f = &mut s.f;
*f;
*s1;
}

// bytecode verification fails
fun t3(p: u64): u64 {
let a = &mut p;
let b = a;
*a = 0;
*b
}

// bytecode verification fails
fun t4(p: u64): u64 {
let a = &mut p;
let b = a;
let c = b;
*a = 0;
*c
}

fun id_mut<T>(r: &mut T): &mut T {
r
}

// bytecode verification fails
fun t5() {
let x = &mut 0;
let y = id_mut(x);
*y;
*x;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Diagnostics:
error: mutable reference in local `f` requires exclusive access but is borrowed
┌─ tests/reference-safety/mut_borrow_after_invalid.move:8:9
8 │ *f;
│ ^^ requirement enforced here
9 │ *s1;
│ --- conflicting reference `s1` used here

error: mutable reference in local `a` requires exclusive access but is borrowed
┌─ tests/reference-safety/mut_borrow_after_invalid.move:16:9
16 │ *a = 0;
│ ^^^^^^ requirement enforced here
17 │ *b
│ -- conflicting reference `b` used here

error: mutable reference in local `a` requires exclusive access but is borrowed
┌─ tests/reference-safety/mut_borrow_after_invalid.move:25:9
25 │ *a = 0;
│ ^^^^^^ requirement enforced here
26 │ *c
│ -- conflicting reference `c` used here
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

============ bytecode verification succeeded ========
Loading

0 comments on commit b448c40

Please sign in to comment.