diff --git a/lib/compiler/src/beam_ssa_alias.erl b/lib/compiler/src/beam_ssa_alias.erl index 69cc611dc19c..45877c965704 100644 --- a/lib/compiler/src/beam_ssa_alias.erl +++ b/lib/compiler/src/beam_ssa_alias.erl @@ -1215,10 +1215,13 @@ aa_bif(Dst, tl, [Pair], _Types, SS, _AAS) -> aa_pair_extraction(Dst, Pair, tl, SS); aa_bif(Dst, map_get, [_Key,Map], _Types, SS, AAS) -> aa_map_extraction(Dst, Map, SS, AAS); -aa_bif(Dst, binary_part, Args, _Types, SS0, _AAS) -> - %% bif:binary_part/{2,3} is the only guard bif which could lead to - %% aliasing, it extracts a sub-binary with a reference to its - %% argument. +aa_bif(Dst, Bif, Args, _Types, SS0, _AAS) when Bif =:= binary_part; + Bif =:= min; + Bif =:= max -> + %% bif:binary_part/{2,3}, min/2, max/2 are guard bifs that could lead to + %% aliasing. binary_part extracts a sub-binary with a reference to its + %% argument. min and max return one of their arguments unchanged, so the + %% result aliases one of them. SS = beam_ssa_ss:add_var(Dst, unique, SS0), aa_set_aliased([Dst|Args], SS); aa_bif(Dst, Bif, Args, Types, SS, _AAS) -> diff --git a/lib/compiler/src/beam_ssa_lint.erl b/lib/compiler/src/beam_ssa_lint.erl index 5cd522e5a9c1..c5df3bea43c1 100644 --- a/lib/compiler/src/beam_ssa_lint.erl +++ b/lib/compiler/src/beam_ssa_lint.erl @@ -458,7 +458,7 @@ format_error_1({unknown_phi_variable, Name, {From, _To}, I}) -> [format_var(Name), format_instr(I), From]); format_error_1({unknown_block, Label, I}) -> io_lib:format("Unknown block ~p referenced in ~ts", - [Label, I]); + [Label, format_instr(I)]); format_error_1({unknown_variable, Name, I}) -> io_lib:format("Unbound variable ~ts used in ~ts", [format_var(Name), format_instr(I)]); diff --git a/lib/compiler/src/beam_ssa_pp.erl b/lib/compiler/src/beam_ssa_pp.erl index 67626384b0c5..d805a1ae84ff 100644 --- a/lib/compiler/src/beam_ssa_pp.erl +++ b/lib/compiler/src/beam_ssa_pp.erl @@ -393,7 +393,8 @@ format_type(identifier) -> "identifier()"; format_type(none) -> "none()"; -format_type(#t_union{atom=A,list=L,number=N,tuple_set=Ts,other=O}) -> +format_type(#t_union{atom=A,list=L,number=N,tuple_set=Ts, + native_record_set=Rs,other=O}) -> Es = case A of none -> []; _ -> [format_type(A)] @@ -410,6 +411,10 @@ format_type(#t_union{atom=A,list=L,number=N,tuple_set=Ts,other=O}) -> none -> []; _ -> [format_tuple_set(Ts)] end + ++ case Rs of + none -> []; + _ -> [format_native_record_set(Rs)] + end ++ case O of none -> []; _ -> [format_type(O)] @@ -443,3 +448,9 @@ format_tuple_set(RecordSet) -> format_tuple_set_1({{Arity,Key},#t_tuple{size=Arity,elements=Elems}=Tuple}) -> false = none =:= beam_types:meet(Key, map_get(1, Elems)), % Assertion format_type(Tuple). + +format_native_record_set(#t_record{}=R) -> + format_type(R); +format_native_record_set(RecordSet) -> + string:join([format_type(R) || R <- ordsets:to_list(RecordSet)], + " | "). diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 15fc13168da8..2f38ae67aa73 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -1875,7 +1875,7 @@ eval_bif_1(#b_set{args=Args}=I, Op, Ts, Ds) -> false -> I end; - [T,#t_integer{elements={1,1}}] when Op =:= '*'; Op =:= 'div' -> + [T,#t_integer{elements={1,1}}] when Op =:= '*' -> case beam_types:is_numerical_type(T) of true -> #b_set{args=[Result,_]} = I, diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl index 8bc31c478bb1..cba53342bdad 100644 --- a/lib/compiler/src/cerl.erl +++ b/lib/compiler/src/cerl.erl @@ -3894,11 +3894,11 @@ subtrees(T) -> tuple -> [tuple_es(T)]; record -> - [record_es(T)]; + [[record_arg(T)], [record_id(T)], record_es(T)]; record_pair -> [[record_pair_key(T)], [record_pair_val(T)]]; map -> - [map_es(T)]; + [[map_arg(T)], map_es(T)]; map_pair -> [[map_pair_op(T)],[map_pair_key(T)],[map_pair_val(T)]]; 'let' -> @@ -4030,6 +4030,10 @@ ann_make_tree(As, alias, [[V], [P]]) -> ann_c_alias(As, V, P); ann_make_tree(As, 'fun', [Vs, [B]]) -> ann_c_fun(As, Vs, B); ann_make_tree(As, 'receive', [Cs, [T], [A]]) -> ann_c_receive(As, Cs, T, A); +ann_make_tree(As, record, [[A], [Id], Es]) -> + ann_c_record(As, A, Id, Es); +ann_make_tree(As, record_pair, [[K],[V]]) -> + ann_c_record_pair(As, K, V); ann_make_tree(As, 'try', [[E], Vs, [B], Evs, [H]]) -> ann_c_try(As, E, Vs, B, Evs, H); ann_make_tree(As, 'catch', [[B]]) -> ann_c_catch(As, B); diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl index 69d85f319534..0e16fa1da3f5 100644 --- a/lib/compiler/test/beam_ssa_SUITE.erl +++ b/lib/compiler/test/beam_ssa_SUITE.erl @@ -31,7 +31,7 @@ mapfoldl/0,mapfoldl/1, grab_bag/1,redundant_br/1, coverage/1,normalize/1, - trycatch/1,gh_6599/1]). + trycatch/1,gh_6599/1,cs_div/1]). -import_record(beam_ssa, [b_set, b_var, b_literal]). @@ -57,7 +57,8 @@ groups() -> coverage, normalize, trycatch, - gh_6599 + gh_6599, + cs_div ]}]. init_per_suite(Config) -> @@ -1563,6 +1564,17 @@ gh_6599_7(X, Y) -> ok end. +cs_div(_Config) -> + ?assertError(badarith, cs_div_1(id(2.0))), + 2 = cs_div_2(id(2)), + ok. + +cs_div_1(X) when is_number(X) -> + X div 1. + +cs_div_2(X) when is_integer(X) -> + X div 1. + %% The identity function. id(I) -> I. diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/tuple_inplace_checks.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/tuple_inplace_checks.erl index f5cb7b126870..ce470b63cba9 100644 --- a/lib/compiler/test/beam_ssa_check_SUITE_data/tuple_inplace_checks.erl +++ b/lib/compiler/test/beam_ssa_check_SUITE_data/tuple_inplace_checks.erl @@ -26,7 +26,7 @@ update_record0/0, fc/0, track_update_record/1, gh8124_a/0, gh8124_b/0, tuple_set_a/1, tuple_set_b/0, failure_to_patch_list/0, erierl1208/0, gh_9903/0, - gh10367/0]). + gh10367/0, cs_min_max/2]). -record(r, {a=0,b=0,c=0,tot=0}). -record(r1, {a}). -record(r2, {b}). @@ -334,3 +334,10 @@ gh10367() -> Expected = P2#r4{a = a}, timer:sleep(0), Expected = gh10367_update([P1, P2]). + +cs_min_max(X,Y) -> +%ssa% (X, Y) when post_ssa_opt -> +%ssa% _ = update_record(reuse, ...). + A = #r{a=X,b=1}, B = #r{a=Y,b=2}, + Min = min(A,B), Updated = Min#r{a=updated}, + {A,B,Updated}. diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index e9dd9789b24f..0a0cb66730c8 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -2119,6 +2119,9 @@ types_pp(Config) when is_list(Config) -> "'foo' | nonempty_list(1..3) | number(3, 7) |" " {'tag0', 1, 2} | {'tag1', 3, 4} | bitstring(8)"}, {make_bitstring, "bitstring(8)"}, + {make_native_record_set, + "#types_pp:rec_a{present:x=1} |" + " #types_pp:rec_b{present:y=2.0}"}, {make_none, "none()"}], lists:foreach(fun({FunName, Expected}) -> Actual = map_get(atom_to_list(FunName), ResultTypes), diff --git a/lib/compiler/test/compile_SUITE_data/types_pp.erl b/lib/compiler/test/compile_SUITE_data/types_pp.erl index bcbd93a835ee..1e56677ddce0 100644 --- a/lib/compiler/test/compile_SUITE_data/types_pp.erl +++ b/lib/compiler/test/compile_SUITE_data/types_pp.erl @@ -1,3 +1,25 @@ +%% +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2021-2026. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + -module(types_pp). -export([doit/0]). @@ -103,6 +125,15 @@ make_union() -> make_bitstring() -> <<1, 2, 3>>. +-record #rec_a{x=0}. +-record #rec_b{y=0.0}. + +make_native_record_set() -> + case ext:f() of + 0 -> #rec_a{x=1}; + _ -> #rec_b{y=2.0} + end. + doit() -> {make_number(ext:f(), ext:f()), make_atom(), make_float(), @@ -121,6 +152,7 @@ doit() -> make_inexact_tuple(ext:f()), make_union(), make_bitstring(), + make_native_record_set(), make_none() }. diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl index a671495d8956..e550a59b215c 100644 --- a/lib/compiler/test/map_SUITE.erl +++ b/lib/compiler/test/map_SUITE.erl @@ -95,7 +95,8 @@ t_cse_assoc/1, shared_key_tuples/1, map_aliases/1, - coverage/1 + coverage/1, + cerl_update_tree/1 ]). -define(BADMAP(V, F, Args, Expr), @@ -177,7 +178,8 @@ all() -> t_cse_assoc, shared_key_tuples, map_aliases, - coverage + coverage, + cerl_update_tree ]. groups() -> []. @@ -2690,5 +2692,14 @@ do_badmap(Test) -> [],{a,b,c},[a,b],atom,10.0,42,(1 bsl 65) + 3], [Test(T) || T <- Terms]. +%% cerl:update_tree/2 did not preserve the map argument for update maps. + +cerl_update_tree(_Config) -> + V = cerl:c_var('M'), + Pair = cerl:c_map_pair(cerl:c_atom(k), cerl:c_int(1)), + Upd = cerl:ann_c_map([], V, [Pair]), + Upd = cerl:update_tree(Upd, cerl:subtrees(Upd)), + ok. + %% Use this function to avoid compile-time evaluation of an expression. id(I) -> I. diff --git a/lib/compiler/test/native_record_SUITE.erl b/lib/compiler/test/native_record_SUITE.erl index d5998ad23bd9..006d149a9b0c 100644 --- a/lib/compiler/test/native_record_SUITE.erl +++ b/lib/compiler/test/native_record_SUITE.erl @@ -26,7 +26,8 @@ init_per_group/2,end_per_group/2, local_basic/1,local_updates/1,non_atomic_names/1, external_records/1,any_record/1, - matching/1,is_record_bif/1,type_opts/1]). + matching/1,is_record_bif/1,type_opts/1, + cerl_update_tree/1]). %% Unexported records. -record #empty{}. @@ -68,7 +69,8 @@ groups() -> external_records, matching, is_record_bif, - type_opts + type_opts, + cerl_update_tree ]}]. init_per_suite(Config) -> @@ -798,6 +800,15 @@ type_opt_ccc(A, B) -> ok end. +%% cerl:update_tree/2 did not handle record and record_pair nodes. + +cerl_update_tree(_Config) -> + Pair = cerl:c_record_pair(cerl:c_atom(f), cerl:c_int(1)), + Pair = cerl:update_tree(Pair, cerl:subtrees(Pair)), + R = cerl:c_record(cerl:c_atom(rec), [Pair]), + R = cerl:update_tree(R, cerl:subtrees(R)), + ok. + %%% Common utilities. id(I) -> diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 6f33ab4a4502..e946856a58cb 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -1419,27 +1419,28 @@ disallowed_compile_flags(Forms, St0) -> post_traversal_check(Forms, St0) -> St1 = check_behaviour(St0), St2 = check_deprecated(Forms, St1), - St3 = check_imports(Forms, St2), - St4 = check_inlines(Forms, St3), - St5 = check_undefined_functions(St4), - St6 = check_unused_functions(Forms, St5), - St7 = check_bif_clashes(Forms, St6), - St8 = check_specs_without_function(St7), - St9 = check_functions_without_spec(Forms, St8), - StA = check_undefined_types(St9), - StB = check_unused_types(Forms, StA), - StC = check_untyped_records(Forms, StB), - StD = check_on_load(StC), - StE = check_export_record(Forms, StD), - StF = check_unused_records(Forms, StE), - StG = check_native_records_header(Forms, StF), - StH = check_local_opaque_types(StG), - StI = check_dialyzer_attribute(Forms, StH), - StJ = check_callback_information(StI), - StK = check_nifs(Forms, StJ), - StL = check_unexported_functions(StK), - StM = check_removed(Forms, StL), - check_unsafe(Forms, StM). + St3 = check_deprecated_type_callback(Forms, St2), + St4 = check_imports(Forms, St3), + St5 = check_inlines(Forms, St4), + St6 = check_undefined_functions(St5), + St7 = check_unused_functions(Forms, St6), + St8 = check_bif_clashes(Forms, St7), + St9 = check_specs_without_function(St8), + StA = check_functions_without_spec(Forms, St9), + StB = check_undefined_types(StA), + StC = check_unused_types(Forms, StB), + StD = check_untyped_records(Forms, StC), + StE = check_on_load(StD), + StF = check_export_record(Forms, StE), + StG = check_unused_records(Forms, StF), + StH = check_native_records_header(Forms, StG), + StI = check_local_opaque_types(StH), + StJ = check_dialyzer_attribute(Forms, StI), + StK = check_callback_information(StJ), + StL = check_nifs(Forms, StK), + StM = check_unexported_functions(StL), + StN = check_removed(Forms, StM), + check_unsafe(Forms, StN). %% check_behaviour(State0) -> State %% Check that the behaviour attribute is valid. @@ -1636,6 +1637,28 @@ deprecated_desc([Char | Str]) when is_integer(Char) -> deprecated_desc(Str); deprecated_desc([]) -> true; deprecated_desc(_) -> false. +%% check_deprecated_type_callback(Forms, State0) -> State + +check_deprecated_type_callback(Forms, St0) -> + Bad = [{E,Anno} || {attribute, Anno, Attr, Depr} <- Forms, + (Attr =:= deprecated_type orelse + Attr =:= deprecated_callback), + D <- lists:flatten([Depr]), + E <- depr_cat_flag(D)], + foldl(fun ({E,Anno}, St1) -> + add_error(Anno, E, St1) + end, St0, Bad). + +depr_cat_flag({_F, _A, Flg}=D) -> + case deprecated_flag(Flg) of + false -> [{invalid_deprecated,D}]; + true -> [] + end; +depr_cat_flag({_F, _A}) -> + []; +depr_cat_flag(D) -> + [{invalid_deprecated,D}]. + %% check_removed(Forms, State0) -> State check_removed(Forms, St0) -> diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl index 5460d66e6661..0d35c59e7a9f 100644 --- a/lib/stdlib/test/erl_lint_SUITE.erl +++ b/lib/stdlib/test/erl_lint_SUITE.erl @@ -95,7 +95,8 @@ illegal_zip_generator/1, record_info_0/1, coverage/1, - native_records/1]). + native_records/1, + cs_deprecated_attr/1]). suite() -> [{ct_hooks,[ts_install_cth]}, @@ -137,7 +138,8 @@ all() -> illegal_zip_generator, record_info_0, coverage, - native_records]. + native_records, + cs_deprecated_attr]. groups() -> [{unused_vars_warn, [], @@ -2231,6 +2233,26 @@ otp_5917(Config) when is_list(Config) -> [] = run(Config, Ts), ok. +%% Check the 'deprecated_type' and 'deprecated_callback' attributes. +cs_deprecated_attr(Config) when is_list(Config) -> + Ts = [{deprecated_type_1, + <<"-export_type([foo/0]). + -deprecated_type([{foo, 0, 1}]). + -type foo() :: integer(). + ">>, + {[]}, + {errors,[{{2,15},erl_lint,{invalid_deprecated,{foo,0,1}}}], + []}}, + {deprecated_callback_1, + <<"-callback bar(integer()) -> ok. + -deprecated_callback([{bar, 1, 42}]). + ">>, + {[]}, + {errors,[{{2,15},erl_lint,{invalid_deprecated,{bar,1,42}}}], + []}}], + [] = run(Config, Ts), + ok. + %% OTP-6585. Check that deprecated guards list/1, pid/1, etc. are no longer recognized otp_6585(Config) when is_list(Config) -> Ts = [{otp_6585_1,