Skip to content

Record update uses inplace beam directive unsafely in specific scenario, which causes a variable to be mutated #10367

@Peter-Searby

Description

@Peter-Searby

Describe the bug
When the below code is compiled in OTP 28.1.1, it uses the inplace beam directive, but this can cause P2 to be updated before it is used for the final time.

I tried to simplify the case, or otherwise determine what specifically about this code causes the mis-compilation, but this is the best I could get. Any simplifying changes to the code cause the compiled code to behave correctly again.

To Reproduce

-module(test_0).
-export([update_test/0]).

-record(r, {x, y}).

gen() ->
    [#r{x = a}, #r{x = b, y = dict:new()}].

update([_, #r{x = b} = P2]) ->
    P2#r{x = a};
update([_, #r{x = a} = P2]) ->
    P2#r{x = b}.

update_test() ->
    [P1, P2] = gen(),
    Expected = P2#r{x = a},
    timer:sleep(0),
    Expected = update([P1, P2]).

Compiled and run with

erlc test_0.erl && erl -pa ./ -noshell -eval 'test_0:update_test(),init:stop().'

The final line fails with a badmatch, because Expected has #r{x = a}, but the result of update/2 gives #r{x = b}.

Since P2 was defined with #r{x = b}, and update/2 swaps b for a, we would expect that update([P1, P2]) should return #r{x = a}.

In fact, P2 gets updated by Expected = P2#r{x = a}, because the beam code uses inplace:

{beam_file,test_0,
           [{module_info,0,11},{module_info,1,13},{update_test,0,7}],
           [{vsn,[39080679506587980296061558520440968194]}],
           [{version,"9.0.2"},
            {options,[]},
...
            {function,update_test,0,7,
...
                       {update_record,{atom,inplace},
                                      3,
                                      {tr,{x,0},{t_tuple,0,false,#{}}},
                                      {y,0},
                                      {list,[2,{atom,a}]}},

Expected behavior

The record update of P2 in update_test should not be compiled with the inplace directive, or if it did, it should certainly not affect usages of P2 after.

This can be seen to be correct in the beam code when compiled with OTP 27.3.4.3:

{beam_file,test_0,
           [{module_info,0,11},{module_info,1,13},{update_test,0,7}],
           [{vsn,[181881847084282917466935693926325942879]}],
           [{version,"8.6.1.2"},
            {options,[]},
...
            {function,update_test,0,7,
...
                       {update_record,{atom,reuse},
                                      3,
                                      {tr,{x,0},{t_tuple,0,false,#{}}},
                                      {y,0},
                                      {list,[2,{atom,a}]}},

Affected versions
Affects: OTP 28.1.1

Doesn't affect: OTP 27.3.4.3

Metadata

Metadata

Assignees

Labels

bugIssue is reported as a bugteam:VMAssigned to OTP team VM

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions