-
Notifications
You must be signed in to change notification settings - Fork 3k
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
setelement/3 causes unexpected mutation in nested tuples #9014
Comments
This is definitely a bug. Running erlc with |
AFAIU elixir folks are also affected because they also wrap https://github.com/elixir-lang/elixir/blob/v1.17.3/lib/elixir/lib/kernel.ex#L1886-L1903 |
The error only occurs when CSE does not unify all extracts from one tuple and the lifetime of the variables containing the extracted elements are non-overlapping. In that case the alias analysis fails to detect aliasing and then the destructive update pass thinks it is safe to modify the tuple in-place. |
Correct an omission in the alias analysis which could make it fail to detect aliasing of a tuple element if the same element was extracted more than once in a function. If we had code looking like this: function `bad`:`foo`(_0) { 0: _1 = get_tuple_element _0, `1` _2 = call (`bar`/1), _1 ... 1: _3 = get_tuple_element _0, `1` The alias analysis would think that _1 died with the call to bar/1 and thus prune _1 from the sharing state database when leaving block 0 and thus fail to detect the aliasing of element 1 in _0. This in turn could allow bar/1 to destructively update elements of its argument, which is not safe. This omission is corrected by detecting when the same element is extracted from a tuple multiple times within a function and create a database which, given a variable, allows for looking up variables storing later extractions of the same element. The database is used during liveness calculations to artificially extend the liveness of the first extracted value to overlap with at least one of the later extractions to ensure that aliasing of tuple elements are not missed. Thanks to @intarga at Github for finding this bug. Closes erlang#9014
Correct an omission in the alias analysis pass which could make it fail to detect aliasing of a tuple element if the same element was extracted more than once in a function. If we had code looking like this: function `bad`:`foo`(_0) { 0: _1 = get_tuple_element _0, `1` _2 = call (`bar`/1), _1 ... 1: _3 = get_tuple_element _0, `1` The alias analysis would decide that _1 died with the call to bar/1 and thus prune _1 from the sharing state database when leaving block 0 and thus fail to detect the aliasing of element 1 in _0. This in turn could allow bar/1 to destructively update elements of its argument, which is not safe. This omission is corrected by detecting when the same element is extracted from a tuple multiple times in a function. Normally the CSE pass ensures that this is only done once, but sometimes it decides that it is more efficient to keep the tuple around and extract the element again. This interacts badly with the alias analysis which takes care to minimize the database it keeps about aliasing status to variables that are live, and can therefore in rare cases fail to detect aliasing. Instead of complicating and slowing down the main alias analysis, we do a once over on all functions and detect when the same field is extracted twice and store the afflicted variables in a set. During the main alias analysis pass we consult the set and forcibly alias the variable when it is defined. Thanks to @intarga for finding this bug. Closes erlang#9014
Correct an omission in the alias analysis pass which could make it fail to detect aliasing of a tuple element if the same element was extracted more than once in a function. If we had code looking like this: function `bad`:`foo`(_0) { 0: _1 = get_tuple_element _0, `1` _2 = call (`bar`/1), _1 ... 1: _3 = get_tuple_element _0, `1` The alias analysis would decide that _1 died with the call to bar/1 and thus prune _1 from the sharing state database when leaving block 0 and thus fail to detect the aliasing of element 1 in _0. This in turn could allow bar/1 to destructively update elements of its argument, which is not safe. This omission is corrected by detecting when the same element is extracted from a tuple multiple times in a function. Normally the CSE pass ensures that this is only done once, but sometimes it decides that it is more efficient to keep the tuple around and extract the element again. This interacts badly with the alias analysis which takes care to minimize the database it keeps about aliasing status to variables that are live, and can therefore in rare cases fail to detect aliasing. Instead of complicating and slowing down the main alias analysis, we do a once over on all functions and detect when the same field is extracted twice and store the afflicted variables in a set. During the main alias analysis pass we consult the set and forcibly alias the variable when it is defined. Thanks to @intarga for finding this bug. Closes erlang#9014
Correct an omission in the alias analysis pass which could make it fail to detect aliasing of a tuple element if the same element was extracted more than once in a function. If we had code looking like this: function `bad`:`foo`(_0) { 0: _1 = get_tuple_element _0, `1` _2 = call (`bar`/1), _1 ... 1: _3 = get_tuple_element _0, `1` The alias analysis would decide that _1 died with the call to bar/1 and thus prune _1 from the sharing state database when leaving block 0 and thus fail to detect the aliasing of element 1 in _0. This in turn could allow bar/1 to destructively update elements of its argument, which is not safe. This omission is corrected by detecting when the same element is extracted from a tuple multiple times in a function. Normally the CSE pass ensures that this is only done once, but sometimes it decides that it is more efficient to keep the tuple around and extract the element again. This interacts badly with the alias analysis which takes care to minimize the database it keeps about aliasing status to variables that are live, and can therefore in rare cases fail to detect aliasing. Instead of complicating and slowing down the main alias analysis, we do a once over on all functions and detect when the same field is extracted twice and store the afflicted variables in a set. During the main alias analysis pass we consult the set and forcibly alias the variable when it is defined. Thanks to @intarga for finding this bug. Closes erlang#9014
Correct an omission in the alias analysis pass which could make it fail to detect aliasing of a tuple element if the same element was extracted more than once in a function. If we had code looking like this: function `bad`:`foo`(_0) { 0: _1 = get_tuple_element _0, `1` _2 = call (`bar`/1), _1 ... 1: _3 = get_tuple_element _0, `1` The alias analysis would decide that _1 died with the call to bar/1 and thus prune _1 from the sharing state database when leaving block 0 and thus fail to detect the aliasing of element 1 in _0. This in turn could allow bar/1 to destructively update elements of its argument, which is not safe. This omission is corrected by detecting when the same element is extracted from a tuple multiple times in a function. Normally the CSE pass ensures that this is only done once, but sometimes it decides that it is more efficient to keep the tuple around and extract the element again. This interacts badly with the alias analysis which takes care to minimize the database it keeps about aliasing status to variables that are live, and can therefore in rare cases fail to detect aliasing. Instead of complicating and slowing down the main alias analysis, we do a once over on all functions and detect when the same field is extracted twice and store the afflicted variables in a set. During the main alias analysis pass we consult the set and forcibly alias the variable when it is defined. Thanks to @intarga for finding this bug. Closes erlang#9014
#9024 contains a fix for this problem. |
Correct an omission in the alias analysis pass which could make it fail to detect aliasing of a tuple element if the same element was extracted more than once in a function. If we had code looking like this: function `bad`:`foo`(_0) { 0: _1 = get_tuple_element _0, `1` _2 = call (`bar`/1), _1 ... 1: _3 = get_tuple_element _0, `1` The alias analysis would decide that _1 died with the call to bar/1 and thus prune _1 from the sharing state database when leaving block 0 and thus fail to detect the aliasing of element 1 in _0. This in turn could allow bar/1 to destructively update elements of its argument, which is not safe. This omission is corrected by detecting when the same element is extracted from a tuple multiple times in a function. Normally the CSE pass ensures that this is only done once, but sometimes it decides that it is more efficient to keep the tuple around and extract the element again. This interacts badly with the alias analysis which takes care to minimize the database it keeps about aliasing status to variables that are live, and can therefore in rare cases fail to detect aliasing. Instead of complicating and slowing down the main alias analysis, we do a once over on all functions and detect when the same field is extracted twice and store the afflicted variables in a set. During the main alias analysis pass we consult the set and forcibly alias the variable when it is defined. Thanks to @intarga for finding this bug. Closes erlang#9014
Describe the bug
As I understand from the documentation, setelement/3 can mutate tuples in place as an optimisation, but this optimisation should only be applied where it doesn't affect the behaviour of the function. I.e any mutation should never be visible to the user.
However, when using setelement/3 on a tuple inside another tuple, it always mutates the tuple, changing the behaviour of the program.
To Reproduce
Expected behavior
This program should crash. The first 5 calls to
inc_counter
inwibble
should do nothing,wibble
should return{counter, 1}
and main should crash.Instead, the first 5 calls mutate the
counter
tuple in place, andwibble
returns{counter, 5}
Affected versions
I've verified this with:
Someone else has corroborated it with:
The text was updated successfully, but these errors were encountered: