Skip to content

Commit

Permalink
bugfix: fixes #448 (#505)
Browse files Browse the repository at this point in the history
Fixes issue #448 (Trie.mergeDisjoint can produce broken tries)
* actually forces a trap when Trie.mergeDisjoint tries to merge maps
with non disjoint domains (as claimed in the doc).

Unfortunately, the test harness isn't set up to test for traps, but you
manually verify it by running (in dir test)

```

[nix-shell:~/motoko-base/test]$ moc -c --package base ../src -wasi-system-api traps/issue-448.mo 

[nix-shell:~/motoko-base/test]$ wasmtime run --disable-cache issue-448.wasm
Trie.mergeDisjoint
Error: failed to run main module `issue-448.wasm`

Caused by:
    0: failed to invoke command default
    1: wasm trap: wasm `unreachable` instruction executed
       wasm backtrace:
           0: 0x1634 - <unknown>!trap
           1: 0x1618 - <unknown>!anon-func-576.15
           2: 0x1c73 - <unknown>!rec2
           3: 0x1d62 - <unknown>!rec1
           4: 0x1586 - <unknown>!disj
           5: 0x1732 - <unknown>!rec
           6:  0x5eb - <unknown>!mergeDisjoint
           7:  0x57e - <unknown>!init
           8: 0x1f84 - <unknown>!_start
       

```
  • Loading branch information
crusso authored Jan 13, 2023
1 parent 22152e9 commit b2e6e58
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 80 deletions.
161 changes: 81 additions & 80 deletions src/Trie.mo
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
/// type Trie<K, V> = Trie.Trie<K, V>;
/// type Key<K> = Trie.Key<K>;
///
/// // we have to provide `put`, `get` and `remove` with
/// // we have to provide `put`, `get` and `remove` with
/// // a record of type `Key<K> = { hash : Hash.Hash; key : K }`;
/// // thus we define the following function that takes a value of type `K`
/// // (in this case `Text`) and returns a `Key<K>` record.
Expand All @@ -46,9 +46,9 @@
/// // - the key of the value we want to insert (note that we use the `key` function defined above),
/// // - a function that checks for equality of keys, and
/// // - the value we want to insert.
/// //
/// //
/// // When inserting a value, `put` returns a tuple of type `(Trie<K, V>, ?V)`.
/// // to get the new trie that contains the value, we use the `0` projection
/// // to get the new trie that contains the value, we use the `0` projection
/// // and assign it to `t1` and `t2` respectively.
/// let t1 : Trie<Text, Nat> = Trie.put(t0, key "hello", Text.equal, 42).0;
/// let t2 : Trie<Text, Nat> = Trie.put(t1, key "world", Text.equal, 24).0;
Expand Down Expand Up @@ -244,7 +244,7 @@ module {
/// type Trie<K, V> = Trie.Trie<K, V>;
/// type Key<K> = Trie.Key<K>;
///
/// // We have to provide `put`, `get` and `remove` with
/// // We have to provide `put`, `get` and `remove` with
/// // a function of return type `Key<K> = { hash : Hash.Hash; key : K }`
/// func key(t: Text) : Key<Text> { { hash = Text.hash t; key = t } };
/// // We start off by creating an empty `Trie`
Expand All @@ -253,11 +253,11 @@ module {
public func empty<K, V>() : Trie<K, V> { #empty };


/// Get the size in O(1) time.
/// Get the size in O(1) time.
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// var size = Trie.size(trie); // Returns 0, as `trie` is empty
Expand Down Expand Up @@ -390,7 +390,7 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
Expand All @@ -405,10 +405,10 @@ module {
///
/// For a more detailed overview of how to use a Trie,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// var value = Trie.get(trie, key "hello", Text.equal); // Returns `?42`
/// assert(value == ?42);
/// value := Trie.get(trie, key "world", Text.equal); // Returns `null`
Expand All @@ -420,10 +420,10 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// var value = Trie.find(trie, key "hello", Text.equal); // Returns `?42`
/// assert(value == ?42);
/// value := Trie.find(trie, key "world", Text.equal); // Returns `null`
Expand Down Expand Up @@ -483,18 +483,18 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 42).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 42).0;
/// // trie2 is a copy of trie
/// var trie2 = Trie.clone(trie);
/// var trie2 = Trie.clone(trie);
/// // trie2 has a different value for "hello"
/// trie2 := Trie.put(trie2, key "hello", Text.equal, 33).0;
/// // mergedTrie has the value 42 for "hello", as the left trie is preferred
/// trie2 := Trie.put(trie2, key "hello", Text.equal, 33).0;
/// // mergedTrie has the value 42 for "hello", as the left trie is preferred
/// // in the case of a collision
/// var mergedTrie = Trie.merge(trie, trie2, Text.equal);
/// var mergedTrie = Trie.merge(trie, trie2, Text.equal);
/// var value = Trie.get(mergedTrie, key "hello", Text.equal);
/// assert(value == ?42);
/// ```
Expand Down Expand Up @@ -547,18 +547,18 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 42).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 42).0;
/// // trie2 is a copy of trie
/// var trie2 = Trie.clone(trie);
/// var trie2 = Trie.clone(trie);
/// // trie2 has a different value for "hello"
/// trie2 := Trie.put(trie2, key "hello", Text.equal, 33).0;
/// trie2 := Trie.put(trie2, key "hello", Text.equal, 33).0;
/// // `mergeDisjoint` signals a dynamic errror
/// // in the case of a collision
/// var mergedTrie = Trie.mergeDisjoint(trie, trie2, Text.equal);
/// var mergedTrie = Trie.mergeDisjoint(trie, trie2, Text.equal);
/// ```
public func mergeDisjoint<K, V>(tl : Trie<K, V>, tr : Trie<K, V>, k_eq : (K, K) -> Bool) : Trie<K, V> {
let key_eq = equalKey(k_eq);
Expand All @@ -569,14 +569,15 @@ module {
case (_, #empty) { return tl };
case (#leaf(l1), #leaf(l2)) {
leaf(
AssocList.disjDisjoint(
AssocList.disj(
l1.keyvals,
l2.keyvals,
equalKey(k_eq),
func(x : ?V, y : ?V) : V {
switch (x, y) {
case (null, ?v) { v };
case (?v, null) { v };
case (_, _) { P.unreachable() }
case (_, _) { Debug.trap "Trie.mergeDisjoint"}
}
}
),
Expand Down Expand Up @@ -608,19 +609,19 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 42).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 42).0;
/// // trie2 is a copy of trie
/// var trie2 = Trie.clone(trie);
/// // trie2 now has an additional key
/// trie2 := Trie.put(trie2, key "ciao", Text.equal, 33).0;
/// var trie2 = Trie.clone(trie);
/// // trie2 now has an additional key
/// trie2 := Trie.put(trie2, key "ciao", Text.equal, 33).0;
/// // `diff` returns a trie with the key "ciao",
/// // as this key is not present in `trie`
/// // (note that we pass `trie2` as the left trie)
/// Trie.diff(trie2, trie, Text.equal);
/// Trie.diff(trie2, trie, Text.equal);
/// ```
public func diff<K, V, W>(tl : Trie<K, V>, tr : Trie<K, W>, k_eq : (K, K) -> Bool) : Trie<K, V> {
let key_eq = equalKey(k_eq);
Expand Down Expand Up @@ -853,17 +854,17 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// // create an Iterator over key-value pairs of trie
/// let iter = Trie.iter(trie);
/// let iter = Trie.iter(trie);
/// // add another key-value pair to `trie`.
/// // because we created our iterator before
/// // this update, it will not contain this new key-value pair
/// trie := Trie.put(trie, key "ciao", Text.equal, 3).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 3).0;
/// var sum : Nat = 0;
/// for ((k,v) in iter) {
/// sum += v;
Expand Down Expand Up @@ -1055,12 +1056,12 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 3).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 3).0;
/// // create an accumulator, in our case the sum of all values
/// func calculateSum(k : Text, v : Nat, acc : Nat) : Nat = acc + v;
/// // Fold over the trie using the accumulator.
Expand Down Expand Up @@ -1089,12 +1090,12 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 3).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 3).0;
/// // `some` takes a function that returns a Boolean indicating whether
/// // the key-value pair is present or not
/// var isPresent = Trie.some(
Expand Down Expand Up @@ -1128,12 +1129,12 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// // `all` takes a function that returns a boolean indicating whether
/// // the key-value pairs all have a given property, in our case that
/// // all values are greater than 9
Expand Down Expand Up @@ -1172,20 +1173,20 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// import Array "mo:base/Array";
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// // `tabulate` takes a size parameter, so we check the size of
/// // the trie first
/// let size = Trie.size(trie);
/// // Now we can create an array of the same size passing `nth` as
/// // the generator used to fill the array.
/// // Note that `toArray` is a convenience function that does the
/// // same thing without you having to check whether the tuple is
/// // same thing without you having to check whether the tuple is
/// // `null` or not, which we're not doing in this example
/// let array = Array.tabulate<?(Key<Text>, Nat)>(
/// size,
Expand Down Expand Up @@ -1215,15 +1216,15 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// // `toArray` takes a function that takes a key-value tuple
/// // and returns a value of the type you want to use to fill
/// // the array.
/// // the array.
/// // In our case we just return the value
/// let array = Trie.toArray<Text, Nat, Nat>(
/// trie,
Expand Down Expand Up @@ -1255,12 +1256,12 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// // `filter` takes a function that takes a key-value tuple
/// // and returns true if the key-value pair should be included.
/// // In our case those are pairs with a value greater than 20
Expand Down Expand Up @@ -1301,12 +1302,12 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// // `mapFilter` takes a function that takes a key-value tuple
/// // and returns a possibly-distinct value if the key-value pair should be included.
/// // In our case, we filter for values greater than 20 and map them to their square.
Expand Down Expand Up @@ -1388,13 +1389,13 @@ module {
///
/// For a more detailed overview of how to use a Trie,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// // `replaceThen` takes the same arguments as `replace` but also a success continuation
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "ciao", Text.equal, 10).0;
/// // `replaceThen` takes the same arguments as `replace` but also a success continuation
/// // and a failure connection that are called in the respective scenarios.
/// // if the replace fails, that is the key is not present in the trie, the failure continuation is called.
/// // if the replace succeeds, that is the key is present in the trie, the success continuation is called.
Expand Down Expand Up @@ -1437,14 +1438,14 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// // note that compared to `put`, `putFresh` does not return a tuple
/// trie := Trie.putFresh(trie, key "hello", Text.equal, 42);
/// trie := Trie.putFresh(trie, key "bye", Text.equal, 32);
/// trie := Trie.putFresh(trie, key "hello", Text.equal, 42);
/// trie := Trie.putFresh(trie, key "bye", Text.equal, 32);
/// // this will fail as "hello" is already present in the trie
/// trie := Trie.putFresh(trie, key "hello", Text.equal, 10);
/// trie := Trie.putFresh(trie, key "hello", Text.equal, 10);
/// ```
public func putFresh<K, V>(t : Trie<K, V>, k : Key<K>, k_eq : (K, K) -> Bool, v : V) : Trie<K, V> {
let (t2, none) = replace(t, k, k_eq, ?v);
Expand Down Expand Up @@ -1511,11 +1512,11 @@ module {
///
/// For a more detailed overview of how to use a `Trie`,
/// see the [User's Overview](#overview).
///
///
/// Example:
/// ```motoko include=initialize
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// trie := Trie.put(trie, key "hello", Text.equal, 42).0;
/// trie := Trie.put(trie, key "bye", Text.equal, 32).0;
/// // remove the value associated with "hello"
/// trie := Trie.remove(trie, key "hello", Text.equal).0;
/// assert (Trie.get(trie, key "hello", Text.equal) == null);
Expand Down
16 changes: 16 additions & 0 deletions test/traps/issue-448.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Trie "mo:base/Trie";
import Text "mo:base/Text";

func key(t : Text) : Trie.Key<Text> = {key = t; hash = Text.hash(t)};

var trie = Trie.empty<Text,Nat>();

trie := Trie.put(trie, key "hello", Text.equal, 42).0;
trie := Trie.put(trie, key "bye", Text.equal, 42).0;
// trie2 is a copy of trie
var trie2 = Trie.clone(trie);
// trie2 has a different value for "hello"
trie2 := Trie.put(trie2, key "hello", Text.equal, 33).0;
// mergeDisjoint should signal a dynamic error
// in the case of a collision
Trie.mergeDisjoint(trie, trie2, Text.equal); // should trap

0 comments on commit b2e6e58

Please sign in to comment.