diff options
23 files changed, 126 insertions, 37 deletions
diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index 313c60d342..206bb5e278 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -118,7 +118,8 @@ atom await_sched_wall_time_modifications atom awaiting_load atom awaiting_unload atom backtrace backtrace_depth -atom badarg badarith badarity badfile badfun badkey badmap badmatch badsig +atom badarg badarith badarity badfile badfun badkey badmap badmatch +atom badrecord badsig atom badopt badtype atom bad_map_iterator atom bag diff --git a/erts/emulator/beam/beam_common.c b/erts/emulator/beam/beam_common.c index 75a74e6fcf..304b4968a1 100644 --- a/erts/emulator/beam/beam_common.c +++ b/erts/emulator/beam/beam_common.c @@ -395,6 +395,7 @@ Eterm error_atom[NUMBER_EXIT_CODES] = { am_notsup, /* 17 */ am_badmap, /* 18 */ am_badkey, /* 19 */ + am_badrecord, /* 20 */ }; /* Returns the return address at E[0] in printable form, skipping tracing in @@ -753,6 +754,7 @@ expand_error_value(Process* c_p, Uint freason, Eterm Value) { case (GET_EXC_INDEX(EXC_BADARITY)): case (GET_EXC_INDEX(EXC_BADMAP)): case (GET_EXC_INDEX(EXC_BADKEY)): + case (GET_EXC_INDEX(EXC_BADRECORD)): /* Some common exceptions: value -> {atom, value} */ ASSERT(is_value(Value)); hp = HAlloc(c_p, 3); diff --git a/erts/emulator/beam/emu/instrs.tab b/erts/emulator/beam/emu/instrs.tab index 79dd6d566e..34b6c1be32 100644 --- a/erts/emulator/beam/emu/instrs.tab +++ b/erts/emulator/beam/emu/instrs.tab @@ -1074,6 +1074,13 @@ if_end() { //| -no_next; } +badrecord(Src) { + c_p->fvalue = $Src; + c_p->freason = EXC_BADRECORD; + goto find_func_info; + //| -no_next; +} + system_limit_body() { c_p->freason = SYSTEM_LIMIT; $FAIL_BODY(); diff --git a/erts/emulator/beam/emu/ops.tab b/erts/emulator/beam/emu/ops.tab index a11c0de7bc..b827052239 100644 --- a/erts/emulator/beam/emu/ops.tab +++ b/erts/emulator/beam/emu/ops.tab @@ -287,6 +287,8 @@ badmatch x if_end +badrecord s + # Operands for raise/2 are almost always in x(2) and x(1). # Optimize for that case. raise x==2 x==1 => i_raise diff --git a/erts/emulator/beam/error.h b/erts/emulator/beam/error.h index 2067505eda..e4abe85761 100644 --- a/erts/emulator/beam/error.h +++ b/erts/emulator/beam/error.h @@ -165,8 +165,10 @@ /* Bad map */ #define EXC_BADKEY ((19 << EXC_OFFSET) | EXC_ERROR) /* Bad key in map */ +#define EXC_BADRECORD ((20 << EXC_OFFSET) | EXC_ERROR) + /* Bad key in map */ -#define NUMBER_EXIT_CODES 20 /* The number of exit code indices */ +#define NUMBER_EXIT_CODES 21 /* The number of exit code indices */ /* * Internal pseudo-error codes. diff --git a/erts/emulator/beam/jit/arm/instr_common.cpp b/erts/emulator/beam/jit/arm/instr_common.cpp index a3be03a34e..bdfbca4afa 100644 --- a/erts/emulator/beam/jit/arm/instr_common.cpp +++ b/erts/emulator/beam/jit/arm/instr_common.cpp @@ -1473,6 +1473,11 @@ void BeamModuleAssembler::emit_if_end() { emit_error(EXC_IF_CLAUSE); } +void BeamModuleAssembler::emit_badrecord(const ArgVal &Src) { + mov_arg(arm::Mem(c_p, offsetof(Process, fvalue)), Src); + emit_error(EXC_BADRECORD); +} + void BeamModuleAssembler::emit_catch(const ArgVal &Y, const ArgVal &Handler) { a.ldr(TMP1, arm::Mem(c_p, offsetof(Process, catches))); a.add(TMP1, TMP1, imm(1)); diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab index 02a997e27b..ad79a9b2b2 100644 --- a/erts/emulator/beam/jit/arm/ops.tab +++ b/erts/emulator/beam/jit/arm/ops.tab @@ -298,6 +298,8 @@ badmatch s if_end +badrecord s + raise s s # Workaround the limitation that generators must always return at least one instruction. diff --git a/erts/emulator/beam/jit/x86/instr_common.cpp b/erts/emulator/beam/jit/x86/instr_common.cpp index 2758b1a3b6..514aabb6f4 100644 --- a/erts/emulator/beam/jit/x86/instr_common.cpp +++ b/erts/emulator/beam/jit/x86/instr_common.cpp @@ -1736,6 +1736,11 @@ void BeamModuleAssembler::emit_if_end() { emit_error(EXC_IF_CLAUSE); } +void BeamModuleAssembler::emit_badrecord(const ArgVal &Src) { + mov_arg(x86::qword_ptr(c_p, offsetof(Process, fvalue)), Src); + emit_error(EXC_BADRECORD); +} + void BeamModuleAssembler::emit_catch(const ArgVal &Y, const ArgVal &Fail) { a.inc(x86::qword_ptr(c_p, offsetof(Process, catches))); diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab index 2880244778..b5c6da35cd 100644 --- a/erts/emulator/beam/jit/x86/ops.tab +++ b/erts/emulator/beam/jit/x86/ops.tab @@ -321,6 +321,8 @@ badmatch s if_end +badrecord s + raise s s # Workaround the limitation that generators must always return at least one instruction. diff --git a/erts/emulator/test/exception_SUITE.erl b/erts/emulator/test/exception_SUITE.erl index eb115b0fb8..30f0c4e575 100644 --- a/erts/emulator/test/exception_SUITE.erl +++ b/erts/emulator/test/exception_SUITE.erl @@ -1589,6 +1589,10 @@ line_numbers(Config) when is_list(Config) -> {?MODULE,line_numbers,1,_}|_]}} = (catch crash_huge_line(gurka)), + {'EXIT',{{badrecord,[1,2,3]}, + [{?MODULE,bad_record,1,[{file,"bad_records.erl"},{line,4}]}|_]}} = + catch bad_record([1,2,3]), + ok. id(I) -> I. @@ -1716,3 +1720,8 @@ foo() -> id(100). crash_huge_line(_) -> %Line 100000002 erlang:error(crash). %Line 100000003 + +-file("bad_records.erl", 1). +-record(foobar, {a,b,c,d}). %Line 2. +bad_record(R) -> %Line 3. + R#foobar.a. %Line 4. diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl index aed0f5bb78..e63755d22c 100644 --- a/lib/compiler/src/beam_disasm.erl +++ b/lib/compiler/src/beam_disasm.erl @@ -1235,6 +1235,8 @@ resolve_inst({bs_create_bin,Args},_,_,_) -> {bs_create_bin,Args}; resolve_inst({call_fun2,[Tag,{u,Arity},Func]},_,_,_) -> {call_fun2,Tag,Arity,Func}; +resolve_inst({badrecord,[Arg]},_,_,_) -> + {badrecord,resolve_arg(Arg)}; %% %% Catches instructions that are not yet handled. diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl index f4ca52ebe7..fffc940d9f 100644 --- a/lib/compiler/src/beam_jump.erl +++ b/lib/compiler/src/beam_jump.erl @@ -777,6 +777,7 @@ is_exit_instruction(if_end) -> true; is_exit_instruction({case_end,_}) -> true; is_exit_instruction({try_case_end,_}) -> true; is_exit_instruction({badmatch,_}) -> true; +is_exit_instruction({badrecord,_}) -> true; is_exit_instruction(_) -> false. %% remove_unused_labels(Instructions0) -> Instructions diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index b39292e15d..33dacad296 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -916,6 +916,9 @@ expand_mf_instr(#b_set{args=[#b_literal{val=try_clause} | Args]}=I0, expand_mf_instr(#b_set{args=[#b_literal{val=badmatch} | _Args]}=I, Is, Count, Acc) -> {reverse(Acc, [I | Is]), Count}; +expand_mf_instr(#b_set{args=[#b_literal{val=badrecord} | _Args]}=I, + Is, Count, Acc) -> + {reverse(Acc, [I | Is]), Count}; expand_mf_instr(#b_set{args=[#b_literal{val=function_clause} | Args]}=I0, Is, Count0, Acc0) -> %% We can't make a direct jump to `func_info` or an inlined stub: simulate diff --git a/lib/compiler/src/beam_trim.erl b/lib/compiler/src/beam_trim.erl index a332bd522d..c3b1677bb7 100644 --- a/lib/compiler/src/beam_trim.erl +++ b/lib/compiler/src/beam_trim.erl @@ -362,6 +362,8 @@ is_safe_label([{try_case_end,{Tag,_}}|_]) -> Tag =/= y; is_safe_label([if_end|_]) -> true; +is_safe_label([{badrecord,{Tag,_}}|_]) -> + Tag =/= y; is_safe_label([{block,Bl}|Is]) -> is_safe_label_block(Bl) andalso is_safe_label(Is); is_safe_label([{call_ext,_,{extfunc,M,F,A}}|_]) -> diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 7a6ca09d5c..11f328b470 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -1058,6 +1058,9 @@ vi(if_end, Vst) -> vi({try_case_end,Src}, Vst) -> assert_durable_term(Src, Vst), branch(?EXCEPTION_LABEL, Vst, fun kill_state/1); +vi({badrecord,Src}, Vst) -> + assert_durable_term(Src, Vst), + branch(?EXCEPTION_LABEL, Vst, fun kill_state/1); vi(raw_raise=I, Vst0) -> validate_body_call(I, 3, Vst0); diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab index f5930f7b01..bcddaa9481 100755 --- a/lib/compiler/src/genop.tab +++ b/lib/compiler/src/genop.tab @@ -666,3 +666,7 @@ BEAM_FORMAT_NUMBER=0 ## @spec nif_start ## @doc No-op at start of each function declared in -nifs(). 179: nif_start/0 + +## @spec badrecord Value +## @doc Raises a {badrecord,Value} error exception. +180: badrecord/1 diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index f82bf5aa04..139828084d 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -742,10 +742,20 @@ expr({named_fun,L,'_',Cs}, St) -> fun_tq(Cs, L, St, unnamed); expr({named_fun,L,Name,Cs}, St) -> fun_tq(Cs, L, St, {named,Name}); -expr({call,L,{remote,_,M,F},As0}, St0) -> - {[M1,F1|As1],Aps,St1} = safe_list([M,F|As0], St0), +expr({call,L,{remote,_,M0,F0},As0}, St0) -> + {[M1,F1|As1],Aps,St1} = safe_list([M0,F0|As0], St0), Anno = full_anno(L, St1), - {#icall{anno=#a{anno=Anno},module=M1,name=F1,args=As1},Aps,St1}; + case {M1,F1,As1} of + {#c_literal{val=erlang}, + #c_literal{val=error}, + [#c_tuple{es=[#c_literal{val=badrecord},_]}=Tuple]} -> + Fail = #iprimop{anno=#a{anno=Anno}, + name=#c_literal{val=match_fail}, + args=[Tuple]}, + {Fail,Aps,St1}; + {_,_,_} -> + {#icall{anno=#a{anno=Anno},module=M1,name=F1,args=As1},Aps,St1} + end; expr({call,Lc,{atom,Lf,F},As0}, St0) -> {As1,Aps,St1} = safe_list(As0, St0), Op = #c_var{anno=lineno_anno(Lf, St1),name={F,length(As1)}}, diff --git a/lib/compiler/test/beam_jump_SUITE.erl b/lib/compiler/test/beam_jump_SUITE.erl index a97d863160..22b7e4f679 100644 --- a/lib/compiler/test/beam_jump_SUITE.erl +++ b/lib/compiler/test/beam_jump_SUITE.erl @@ -232,7 +232,8 @@ ambiguous_catch_try_state_3() -> -record(message3, {id, p1, p2}). build_tuple(_Config) -> - {'EXIT',{{badrecord,message3},_}} = (catch do_build_tuple(#message2{})), + Message2 = #message2{}, + {'EXIT',{{badrecord,Message2},_}} = (catch do_build_tuple(#message2{})), ok. do_build_tuple(Message) -> @@ -319,7 +320,7 @@ cs_2(I) -> I. undecided_allocation(_Config) -> ok = catch undecided_allocation_1(<<10:(3*7)>>), - {'EXIT',{{badrecord,rec},_}} = catch undecided_allocation_1(8), + {'EXIT',{{badrecord,<<0>>},_}} = catch undecided_allocation_1(8), ok. -record(rec, {}). diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index 5469379792..9bb1a57c6f 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -931,21 +931,21 @@ strict_record(Config) when is_list(Config) -> %% Default (possibly influenced by ERL_COMPILER_OPTIONS). {ok,M} = c:c(M, [{outdir,Priv},report_errors]), try - {1,2} = record_access:test(Turtle), - {comment,"Default: no_strict_record_tests"} - catch - error:{badrecord,tortoise} -> - {comment,"Default: strict_record_tests"} - end. + {1,2} = record_access:test(Turtle), + {comment,"Default: no_strict_record_tests"} + catch + error:{badrecord,Turtle} -> + {comment,"Default: strict_record_tests"} + end. test_strict() -> Turtle = record_access:turtle(), try - record_access:test(Turtle) - catch - error:{badrecord,tortoise} -> - ok - end, + record_access:test(Turtle) + catch + error:{badrecord,Turtle} -> + ok + end, Turtle. test_sloppy() -> diff --git a/lib/compiler/test/record_SUITE.erl b/lib/compiler/test/record_SUITE.erl index 2dd40e6a77..ae959d0743 100644 --- a/lib/compiler/test/record_SUITE.erl +++ b/lib/compiler/test/record_SUITE.erl @@ -73,17 +73,17 @@ errors(Config) when is_list(Config) -> Foo = #foo{a=1,b=2,c=3,d=4}, #foo{a=19,b=42,c=3,d=4} = update_foo(Foo, 19, 42), - {'EXIT',{{badrecord,bar},_}} = (catch update_foo_bar(Foo, 19)), - {'EXIT',{{badrecord,bar},_}} = (catch update_foo_bar(Foo, 19, 35)), - {'EXIT',{{badrecord,bar},_}} = (catch update_foo_bar(Foo, 19, 35, 17)), - {'EXIT',{{badrecord,bar},_}} = (catch update_foo_bar(Foo, 19, 35, 17, 42)), - - {'EXIT',{{badrecord,barf},_}} = (catch update_foo_barf(Foo, 19)), - {'EXIT',{{badrecord,barf},_}} = (catch update_foo_barf(Foo, 19, 35)), - {'EXIT',{{badrecord,barf},_}} = (catch update_foo_barf(Foo, 19, 35, 17)), - {'EXIT',{{badrecord,barf},_}} = (catch update_foo_barf(Foo, 19, 35, 17, 42)), - {'EXIT',{{badrecord,barf},_}} = (catch update_foo_barf(Foo, 19, - 35, 17, 42, -2)), + {'EXIT',{{badrecord,Foo},_}} = (catch update_foo_bar(Foo, 19)), + {'EXIT',{{badrecord,Foo},_}} = (catch update_foo_bar(Foo, 19, 35)), + {'EXIT',{{badrecord,Foo},_}} = (catch update_foo_bar(Foo, 19, 35, 17)), + {'EXIT',{{badrecord,Foo},_}} = (catch update_foo_bar(Foo, 19, 35, 17, 42)), + + {'EXIT',{{badrecord,Foo},_}} = (catch update_foo_barf(Foo, 19)), + {'EXIT',{{badrecord,Foo},_}} = (catch update_foo_barf(Foo, 19, 35)), + {'EXIT',{{badrecord,Foo},_}} = (catch update_foo_barf(Foo, 19, 35, 17)), + {'EXIT',{{badrecord,Foo},_}} = (catch update_foo_barf(Foo, 19, 35, 17, 42)), + {'EXIT',{{badrecord,Foo},_}} = (catch update_foo_barf(Foo, 19, + 35, 17, 42, -2)), ok. diff --git a/lib/compiler/test/record_SUITE_data/record_access_in_guards.erl b/lib/compiler/test/record_SUITE_data/record_access_in_guards.erl index dbd2419ad2..60a98cfe80 100644 --- a/lib/compiler/test/record_SUITE_data/record_access_in_guards.erl +++ b/lib/compiler/test/record_SUITE_data/record_access_in_guards.erl @@ -172,7 +172,7 @@ t() -> ok = F(R, 42, tab), error = F(R, 42, a), error = F(R, 0, tab), - {'EXIT',{{badrecord,r},_}} = (catch F({x,y,z}, 4, 5)), + {'EXIT',{{badrecord,{x,y,z}},_}} = (catch F({x,y,z}, 4, 5)), ok end(#r{a=42,b=tab}), diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl index 4c8ba0578b..47da4bda9d 100644 --- a/lib/stdlib/src/erl_expand_records.erl +++ b/lib/stdlib/src/erl_expand_records.erl @@ -595,11 +595,11 @@ strict_get_record_field(Anno, R, {atom,_,F}=Index, Name, St0) -> RAnno = mark_record(NAnno, St), E = {'case',Anno,R, [{clause,NAnno,[{tuple,RAnno,P}],[],[Var]}, - {clause,NAnno,[{var,NAnno,'_'}],[], + {clause,NAnno,[Var],[], [{call,NAnno,{remote,NAnno, {atom,NAnno,erlang}, {atom,NAnno,error}}, - [{tuple,NAnno,[{atom,NAnno,badrecord},{atom,NAnno,Name}]}]}]}]}, + [{tuple,NAnno,[{atom,NAnno,badrecord},Var]}]}]}]}, expr(E, St); true -> %In a guard. Fs = record_fields(Name, Anno, St0), @@ -714,7 +714,7 @@ record_match(R, Name, AnnoR, Fs, Us, St0) -> [{clause,AnnoR,[{tuple,RAnno,[{atom,AnnoR,Name} | Ps]}],[], [{tuple,RAnno,[{atom,AnnoR,Name} | News]}]}, {clause,NAnnoR,[{var,NAnnoR,'_'}],[], - [call_error(NAnnoR, {tuple,NAnnoR,[{atom,NAnnoR,badrecord},{atom,NAnnoR,Name}]})]} + [call_error(NAnnoR, {tuple,NAnnoR,[{atom,NAnnoR,badrecord},R]})]} ]}, St1}. @@ -752,7 +752,7 @@ record_setel(R, Name, Fs, Us0) -> {atom,Anno,setelement}},[I,Acc,Val]} end, R, Us)]}, {clause,NAnnoR,[{var,NAnnoR,'_'}],[], - [call_error(NAnnoR, {tuple,NAnnoR,[{atom,NAnnoR,badrecord},{atom,NAnnoR,Name}]})]}]}. + [call_error(NAnnoR, {tuple,NAnnoR,[{atom,NAnnoR,badrecord},R]})]}]}. %% Expand a call to record_info/2. We have checked that it is not %% shadowed by an import. diff --git a/lib/stdlib/test/erl_expand_records_SUITE.erl b/lib/stdlib/test/erl_expand_records_SUITE.erl index ed5b6325fc..4151f211e6 100644 --- a/lib/stdlib/test/erl_expand_records_SUITE.erl +++ b/lib/stdlib/test/erl_expand_records_SUITE.erl @@ -39,7 +39,8 @@ -export([attributes/1, expr/1, guard/1, init/1, pattern/1, strict/1, update/1, otp_5915/1, otp_7931/1, otp_5990/1, - otp_7078/1, otp_7101/1, maps/1]). + otp_7078/1, otp_7101/1, maps/1, + side_effects/1]). init_per_testcase(_Case, Config) -> Config. @@ -53,7 +54,8 @@ suite() -> all() -> [attributes, expr, guard, init, - pattern, strict, update, maps, {group, tickets}]. + pattern, strict, update, maps, + side_effects, {group, tickets}]. groups() -> [{tickets, [], @@ -317,7 +319,7 @@ strict(Config) when is_list(Config) -> ok = try {1, 2} = {A#r2.a, A#r2.b}, not_ok - catch error:{badrecord,r2} -> ok + catch error:{badrecord,{r1,1,2}} -> ok end, try case foo of @@ -761,6 +763,30 @@ otp_7101_update3(R) -> otp_7101_update4(R) -> R#otp_7101{a=1,b=2}. + +-record(side_effects, {a,b,c}). + +%% Make sure that the record expression is only evaluated once. +side_effects(_Config) -> + init_counter(), + + {'EXIT',{{badrecord,0},_}} = catch (id(bump_counter()))#side_effects{a=1}, + 1 = read_counter(), + + {'EXIT',{{badrecord,1},_}} = catch (id(bump_counter()))#side_effects.b, + 2 = read_counter(), + + ok. + +init_counter() -> + put(counter, 0). + +bump_counter() -> + put(counter, get(counter) + 1). + +read_counter() -> + get(counter). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% run(Config, Tests) -> |