%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2020-2023. 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(erl_stdlib_errors). -export([format_error/2]). -spec format_error(Reason, StackTrace) -> ErrorMap when Reason :: term(), StackTrace :: erlang:stacktrace(), ErrorMap :: #{pos_integer() => unicode:chardata()}. format_error(_Reason, [{M,F,As,Info}|_]) -> ErrorInfoMap = proplists:get_value(error_info, Info, #{}), Cause = maps:get(cause, ErrorInfoMap, none), Res = case M of binary -> format_binary_error(F, As, Cause); ets -> format_ets_error(F, As, Cause); lists -> format_lists_error(F, As); maps -> format_maps_error(F, As); math -> format_math_error(F, As); re -> format_re_error(F, As, Cause); unicode -> format_unicode_error(F, As); io -> format_io_error(F, As, Cause); _ -> [] end, format_error_map(Res, 1, #{}). format_binary_error(at, [Subject,Pos], _) -> [must_be_binary(Subject), must_be_position(Pos)]; format_binary_error(bin_to_list, [Subject], _) -> [must_be_binary(Subject)]; format_binary_error(bin_to_list, Args, Cause) -> format_binary_error(part, Args, Cause); format_binary_error(compile_pattern, [_], _) -> [<<"not a valid pattern">>]; format_binary_error(copy, [Subject], _) -> [must_be_binary(Subject)]; format_binary_error(copy, [Subject, N], _) -> [must_be_binary(Subject), must_be_non_neg_integer(N)]; format_binary_error(decode_unsigned, [Subject], _) -> [must_be_binary(Subject)]; format_binary_error(decode_unsigned, [Subject, Endianness], _) -> [must_be_binary(Subject), must_be_endianness(Endianness)]; format_binary_error(encode_unsigned, [Subject], _) -> [must_be_non_neg_integer(Subject)]; format_binary_error(encode_unsigned, [Subject, Endianness], _) -> [must_be_non_neg_integer(Subject), must_be_endianness(Endianness)]; format_binary_error(encode_hex, [Subject], _) -> [must_be_binary(Subject)]; format_binary_error(encode_hex, [Subject, Case], _) -> [must_be_binary(Subject), must_be_hex_case(Case)]; format_binary_error(decode_hex, [Subject], _) -> if is_binary(Subject) -> if byte_size(Subject) rem 2 =:= 1 -> [<<"must contain an even number of bytes">>]; true -> [<<"must only contain hex digits 0-9, A-F, and a-f">>] end; true -> [must_be_binary(Subject)] end; format_binary_error(unhex, [Subject], _) -> [<<(Subject)/binary, " is not a valid hex">>]; format_binary_error(first, [Subject], _) -> [case Subject of <<>> -> empty_binary; _ -> must_be_binary(Subject) end]; format_binary_error(last, [Subject], _) -> [case Subject of <<>> -> empty_binary; _ -> must_be_binary(Subject) end]; format_binary_error(list_to_bin, [_], _) -> [not_iodata]; format_binary_error(longest_common_prefix, [_], _) -> [bad_binary_list]; format_binary_error(longest_common_suffix, [_], _) -> [bad_binary_list]; format_binary_error(match, [Subject, Pattern], _) -> [must_be_binary(Subject), must_be_pattern(Pattern)]; format_binary_error(match, [Subject, Pattern, Options], _) -> case [must_be_binary(Subject), must_be_pattern(Pattern)] of [[], []] -> case Options of [{scope,{Start,Len}}] when is_integer(Start), is_integer(Len) -> [[], [], <<"specified part is not wholly inside binary">>]; _ -> [[], [], bad_options] end; Errors -> Errors end; format_binary_error(matches, Args, Cause) -> format_binary_error(match, Args, Cause); format_binary_error(part=Name, [Subject, PosLen], Cause) -> case PosLen of {Pos,Len} when is_integer(Pos), is_integer(Len) -> case format_binary_error(Name, [Subject,Pos,Len], Cause) of [Arg1,[],[]] -> [Arg1]; [Arg1,_,_] -> [Arg1,range] end; _ -> [must_be_binary(Subject),<<"not a valid {Pos,Length} tuple">>] end; format_binary_error(part, [Subject, Pos, Len], _) -> case [must_be_binary(Subject),must_be_position(Pos),must_be_integer(Len)] of [[],[],[]] -> Arg2 = if Pos > byte_size(Subject) -> range; true -> [] end, case Arg2 of [] -> [[],[],range]; range -> [[],Arg2] end; Errors -> Errors end; format_binary_error(referenced_byte_size, [Subject], _) -> [must_be_binary(Subject)]; format_binary_error(split, [Subject, Pattern], _) -> [must_be_binary(Subject), must_be_pattern(Pattern)]; format_binary_error(split, [Subject, Pattern, _Options], _) -> case [must_be_binary(Subject), must_be_pattern(Pattern)] of [[], []] -> [[], [], bad_options]; Errors -> Errors end; format_binary_error(replace, [Subject, Pattern, Replacement], _) -> [must_be_binary(Subject), must_be_pattern(Pattern), must_be_binary(Replacement)]; format_binary_error(replace, [Subject, Pattern, Replacement, _Options], Cause) -> Errors = format_binary_error(replace, [Subject, Pattern, Replacement], Cause), case Cause of badopt -> Errors ++ [bad_options]; _ -> case Errors of [[], [], []] -> %% Options are syntactically correct, but not semantically %% (e.g. referencing outside the subject). [[], [], [], bad_options]; _ -> Errors end end. format_lists_error(keyfind, [_Key, Pos, List]) -> PosError = if is_integer(Pos) -> if Pos < 1 -> range; true -> [] end; true -> not_integer end, [[], PosError, must_be_list(List)]; format_lists_error(keymember, Args) -> format_lists_error(keyfind, Args); format_lists_error(keysearch, Args) -> format_lists_error(keyfind, Args); format_lists_error(member, [_Key, List]) -> [[], must_be_list(List)]; format_lists_error(reverse, [List, _Acc]) -> [must_be_list(List)]; format_lists_error(seq, [First, Last, Inc]) -> case [must_be_integer(First), must_be_integer(Last), must_be_integer(Inc)] of [[], [], []] -> IncError = if (Inc =< 0 andalso First - Inc =< Last) -> <<"not a positive increment">>; (Inc >= 0 andalso First - Inc >= Last) -> <<"not a negative increment">> end, [[], [], IncError]; Errors -> Errors end. format_maps_error(filter, Args) -> format_maps_error(map, Args); format_maps_error(filtermap, Args) -> format_maps_error(map, Args); format_maps_error(foreach, Args) -> format_maps_error(map, Args); format_maps_error(find, _Args) -> [[], not_map]; format_maps_error(fold, [Pred, _Init, Map]) -> [must_be_fun(Pred, 3), [], must_be_map_or_iter(Map)]; format_maps_error(from_keys, [List, _]) -> [must_be_list(List)]; format_maps_error(from_list, [List]) -> [must_be_list(List)]; format_maps_error(get, [_Key,Map]) -> if is_map(Map) -> [<<"not present in map">>]; true -> [[],not_map] end; format_maps_error(groups_from_list, [Fun, List]) -> [must_be_fun(Fun, 1), must_be_list(List)]; format_maps_error(groups_from_list, [Fun1, Fun2, List]) -> [must_be_fun(Fun1, 1), must_be_fun(Fun2, 1), must_be_list(List)]; format_maps_error(get, [_,_,_]) -> [[],not_map]; format_maps_error(intersect, [Map1, Map2]) -> [must_be_map(Map1), must_be_map(Map2)]; format_maps_error(intersect_with, [Combiner, Map1, Map2]) -> [must_be_fun(Combiner, 3), must_be_map(Map1), must_be_map(Map2)]; format_maps_error(is_key, _Args) -> [[], not_map]; format_maps_error(iterator, [Map]) -> [must_be_map(Map)]; format_maps_error(iterator, [Map, Order]) -> [must_be_map(Map), must_be_map_iterator_order(Order)]; format_maps_error(keys, _Args) -> [not_map]; format_maps_error(map, [Pred, Map]) -> [must_be_fun(Pred, 2), must_be_map_or_iter(Map)]; format_maps_error(merge, [Map1, Map2]) -> [must_be_map(Map1), must_be_map(Map2)]; format_maps_error(merge_with, [Combiner, Map1, Map2]) -> [must_be_fun(Combiner, 3), must_be_map(Map1), must_be_map(Map2)]; format_maps_error(next, _Args) -> [bad_iterator]; format_maps_error(put, _Args) -> [[], [], not_map]; format_maps_error(remove, _Args) -> [[], not_map]; format_maps_error(size, _Args) -> [not_map]; format_maps_error(take, _Args) -> [[], not_map]; format_maps_error(to_list, _Args) -> [not_map_or_iterator]; format_maps_error(update, _Args) -> [[], [], not_map]; format_maps_error(update_with, [_Key, Fun, Map]) -> [[], must_be_fun(Fun, 1), must_be_map(Map)]; format_maps_error(update_with, [_Key, Fun, _Init, Map]) -> [[], must_be_fun(Fun, 1), [], must_be_map(Map)]; format_maps_error(values, _Args) -> [not_map]; format_maps_error(with, [List, Map]) -> [must_be_list(List), must_be_map(Map)]; format_maps_error(without, [List, Map]) -> [must_be_list(List), must_be_map(Map)]. format_math_error(acos, Args) -> maybe_domain_error(Args); format_math_error(acosh, Args) -> maybe_domain_error(Args); format_math_error(asin, Args) -> maybe_domain_error(Args); format_math_error(atanh, Args) -> maybe_domain_error(Args); format_math_error(log, Args) -> maybe_domain_error(Args); format_math_error(log2, Args) -> maybe_domain_error(Args); format_math_error(log10, Args) -> maybe_domain_error(Args); format_math_error(sqrt, Args) -> maybe_domain_error(Args); format_math_error(fmod, [Arg1, Arg2]) -> case [must_be_number(Arg1), must_be_number(Arg2)] of [[], []] -> if Arg2 == 0 -> [[], domain_error]; true -> [] end; Error -> Error end; format_math_error(_, [Arg]) -> [must_be_number(Arg)]; format_math_error(_, [Arg1, Arg2]) -> [must_be_number(Arg1), must_be_number(Arg2)]. maybe_domain_error([Arg]) -> case must_be_number(Arg) of [] -> [domain_error]; Error -> [Error] end. format_re_error(compile, [_], _) -> [not_iodata]; format_re_error(compile, [Re, _Options], Cause) -> ReError = try re:compile(Re) of {ok, _} -> []; {error, Reason} -> {bad_regexp, Reason} catch _:_ -> not_iodata end, case Cause of badopt -> [ReError, bad_options]; _ -> [ReError] end; format_re_error(inspect, [CompiledRE, Item], _) -> ReError = try re:inspect(CompiledRE, namelist) of _ -> [] catch error:_ -> not_compiled_regexp end, if ReError =:= []; not is_atom(Item) -> [ReError, <<"not a valid item">>]; true -> [ReError] end; format_re_error(replace, [Subject, RE, Replacement], _) -> [must_be_iodata(Subject), must_be_regexp(RE), must_be_iodata(Replacement)]; format_re_error(replace, [Subject, RE, Replacement, _Options], Cause) -> Errors = [must_be_iodata(Subject), must_be_regexp(RE), must_be_iodata(Replacement)], case Cause of badopt -> Errors ++ [bad_options]; _ -> Errors end; format_re_error(run, [Subject, RE], _) -> [must_be_iodata(Subject), must_be_regexp(RE)]; format_re_error(run, [Subject, RE, _Options], Cause) -> Errors = [must_be_iodata(Subject), must_be_regexp(RE)], case Cause of badopt -> Errors ++ [bad_options]; _ -> Errors end; format_re_error(split, [Subject, RE], _) -> [must_be_iodata(Subject), must_be_regexp(RE)]; format_re_error(split, [Subject, RE, _Options], Cause) -> Errors = [must_be_iodata(Subject), must_be_regexp(RE)], case Cause of badopt -> Errors ++ [bad_options]; _ -> Errors end. format_unicode_error(characters_to_binary, [_]) -> [bad_char_data]; format_unicode_error(characters_to_binary, [Chars, InEnc]) -> [unicode_char_data(Chars), unicode_encoding(InEnc)]; format_unicode_error(characters_to_binary, [Chars, InEnc, OutEnc]) -> [unicode_char_data(Chars), unicode_encoding(InEnc), unicode_encoding(OutEnc)]; format_unicode_error(characters_to_list, Args) -> format_unicode_error(characters_to_binary, Args); format_unicode_error(characters_to_nfc_binary, [_]) -> [bad_char_data]; format_unicode_error(characters_to_nfc_list, [_]) -> [bad_char_data]; format_unicode_error(characters_to_nfd_binary, [_]) -> [bad_char_data]; format_unicode_error(characters_to_nfd_list, [_]) -> [bad_char_data]; format_unicode_error(characters_to_nfkc_binary, [_]) -> [bad_char_data]; format_unicode_error(characters_to_nfkc_list, [_]) -> [bad_char_data]; format_unicode_error(characters_to_nfkd_binary, [_]) -> [bad_char_data]; format_unicode_error(characters_to_nfkd_list, [_]) -> [bad_char_data]. unicode_char_data(Chars) -> try unicode:characters_to_binary(Chars) of {error,_,_} -> bad_char_data; {incomplete,_,_} -> bad_char_data; _ -> [] catch error:_ -> bad_char_data end. unicode_encoding(Enc) -> try unicode:characters_to_binary(<<"a">>, Enc) of _ -> [] catch error:_ -> bad_encoding end. %% Rewrite fwrite to format and add info if call has an %% explicit Io Device as first argument. format_io_error(fwrite, Args, Cause) -> format_io_error(format, Args, Cause); format_io_error(format = Fn, [_Io, _Fmt, _Args] = Args, Cause) -> format_io_error(Fn, Args, Cause, true); format_io_error(put_chars = Fn, [_Io, _Chars] = Args, Cause) -> format_io_error(Fn, Args, Cause, true); format_io_error(put_chars = Fn, [_Io, _Encoding, _Chars] = Args, Cause) -> format_io_error(Fn, Args, Cause, true); format_io_error(nl = Fn, [_Io] = Args, Cause) -> format_io_error(Fn, Args, Cause, true); format_io_error(write = Fn, [_Io, _Term] = Args, Cause) -> format_io_error(Fn, Args, Cause, true); format_io_error(Fn, Args, Cause) -> format_io_error(Fn, Args, Cause, false). %% The cause should always be wrapped in either `io` or %% `device`. If it is wrapped in `io` we know that the %% error originated in the io module. If it is wrapped %% in `device`, the error came from the called io device. %% arguments, whereis(Io) failed format_io_error(_, _, {io, arguments}, true) -> [device_arguments]; format_io_error(_, _, {io, arguments}, false) -> [{general,device_arguments}]; %% calling_self, Io =:= self() format_io_error(_, _, {io, calling_self}, true) -> [calling_self]; format_io_error(_, _, {io, calling_self}, false) -> [{general,calling_self}]; %% terminated, monitor(Io) failed format_io_error(_, _, {io, terminated}, true) -> [device_terminated]; format_io_error(_, _, {io, terminated}, false) -> [{general,device_terminated}]; %% Strip the wrapping as we no longer need it format_io_error(Fn, Args, {device, Cause}, HasDevice) -> format_io_error_cause(Fn, Args, Cause, HasDevice). %% Could not translate from unicode to latin1 format_io_error_cause(_, _, {no_translation, In, Out}, true) -> [{no_translation, In, Out}]; format_io_error_cause(_, _, {no_translation, In, Out}, false) -> [{general,{no_translation, In, Out}}]; format_io_error_cause(format, Args, Cause, HasDevice) -> case maybe_posix_message(Cause, HasDevice) of unknown -> if HasDevice -> [[]] ++ check_io_format(tl(Args), Cause); not HasDevice -> check_io_format(Args, Cause) end; PosixError -> PosixError end; format_io_error_cause(put_chars, Args, Cause, HasDevice) -> Data = if HasDevice -> hd(tl(Args)); not HasDevice -> hd(Args) end, case maybe_posix_message(Cause, HasDevice) of unknown -> [[] || HasDevice] ++ case unicode_char_data(Data) of [] -> [{general,{unknown_error,Cause}}]; InvalidData -> [InvalidData] end; PosixError -> PosixError ++ [unicode_char_data(Data)] end; format_io_error_cause(Fn, _Args, Cause, HasDevice) when Fn =:= write; Fn =:= nl -> case maybe_posix_message(Cause, HasDevice) of unknown -> [{general,{unknown_error,Cause}}]; PosixError -> PosixError end; format_io_error_cause(_, _, _, _HasDevice) -> []. maybe_posix_message(Cause, HasDevice) -> case erl_posix_msg:message(Cause) of "unknown POSIX error" ++ _ -> unknown; PosixStr when HasDevice -> [io_lib:format("~ts (~tp)",[PosixStr, Cause])]; PosixStr when not HasDevice -> [{general,io_lib:format("~ts (~tp)",[PosixStr, Cause])}] end. check_io_format([Fmt], Cause) -> check_io_format([Fmt, []], Cause); check_io_format([Fmt, Args], Cause) -> case is_io_format(Fmt) of false -> [invalid_format, must_be_list(Args)] ++ case (is_pid(Fmt) or is_atom(Fmt)) and is_io_format(Args) of true -> %% The user seems to have called io:format(Dev,"string"). [{general,missing_argument_list}]; false -> [] end; _ when not is_list(Args) -> [[],must_be_list(Args)]; true -> case erl_lint:check_format_string(Fmt) of {error,S} -> [io_lib:format("format string invalid (~ts)",[S])]; {ok,ArgTypes} when length(ArgTypes) =/= length(Args) -> [<<"wrong number of arguments">>] ++ %% The user seems to have called io:format(user,"string") [{general,missing_argument_list} || is_atom(Fmt)]; {ok,ArgTypes} -> case check_io_arguments(ArgTypes, Args) of [] when Cause =:= format -> [format_failed]; [] -> try io_lib:format(Fmt, Args) of _ -> [{general,{unknown_error,Cause}}] catch _:_ -> [format_failed] end; ArgErrors -> ArgErrors end end end. is_io_format(Fmt) when is_list(Fmt) -> try lists:all(fun erlang:is_integer/1, Fmt) of Res -> Res catch _:_ -> false end; is_io_format(Fmt) when is_atom(Fmt); is_binary(Fmt) -> true; is_io_format(_Fmt) -> false. check_io_arguments(Types, Args) -> case check_io_arguments(Types, Args, 1) of [] -> []; Checks -> [[],lists:join("\n",Checks)] end. check_io_arguments([], [], _No) -> []; check_io_arguments([Type|TypeT], [Arg|ArgT], No) -> case Type of float when is_float(Arg) -> check_io_arguments(TypeT, ArgT, No+1); int when is_integer(Arg) -> check_io_arguments(TypeT, ArgT, No+1); term -> check_io_arguments(TypeT, ArgT, No+1); string when is_atom(Arg); is_binary(Arg) -> check_io_arguments(TypeT, ArgT, No+1); string when is_list(Arg) -> try unicode:characters_to_binary(Arg) of _ -> check_io_arguments(TypeT, ArgT, No+1) catch _:_ -> [io_lib:format("element ~B must be of type ~p", [No, string]) | check_io_arguments(TypeT, ArgT, No+1)] end; int -> [io_lib:format("element ~B must be of type ~p", [No, integer]) | check_io_arguments(TypeT, ArgT, No+1)]; _ when Type =:= float; Type =:= string -> [io_lib:format("element ~B must be of type ~p", [No, Type]) | check_io_arguments(TypeT, ArgT, No+1)] end. format_ets_error(delete_object, Args, Cause) -> format_object(Args, Cause); format_ets_error(give_away, [_Tab,Pid,_Gift]=Args, Cause) -> TabCause = format_cause(Args, Cause), case Cause of owner -> [TabCause, already_owner]; not_owner -> [TabCause, not_owner]; _ -> [TabCause, case {is_pid(Pid),TabCause} of {true,""} -> dead_process; {false,_} -> not_pid; _ -> "" end] end; format_ets_error(info, Args, Cause) -> format_default(bad_info_item, Args, Cause); format_ets_error(insert, Args, Cause) -> format_objects(Args, Cause); format_ets_error(insert_new, Args, Cause) -> format_objects(Args, Cause); format_ets_error(lookup_element, [_,_,Pos]=Args, Cause) -> TabCause = format_cause(Args, Cause), PosCause = format_non_negative_integer(Pos), case Cause of badkey -> [TabCause, bad_key, PosCause]; _ -> case {TabCause,PosCause} of {"",""} -> ["", "", <<"position is greater than the size of the object">>]; {_,_} -> [TabCause, "", PosCause] end end; format_ets_error(lookup_element, [Tab, Key, Pos, _Default], Cause) -> % The default argument cannot cause an error. format_ets_error(lookup_element, [Tab, Key, Pos], Cause); format_ets_error(match, [_], _Cause) -> [bad_continuation]; format_ets_error(match, [_,_,_]=Args, Cause) -> format_limit(Args, Cause); format_ets_error(match_object, [_], _Cause) -> [bad_continuation]; format_ets_error(match_object, [_,_,_]=Args, Cause) -> format_limit(Args, Cause); format_ets_error(match_spec_compile, [_], _Cause) -> [bad_matchspec]; format_ets_error(next, Args, Cause) -> format_default(bad_key, Args, Cause); format_ets_error(new, [Name,Options], Cause) -> NameError = if is_atom(Name) -> []; true -> not_atom end, OptsError = must_be_list(Options), case {NameError, OptsError, Cause} of {[], [], already_exists} -> [name_already_exists, []]; {[], [], _} -> [[], bad_options]; {_, _, _} -> [NameError, OptsError] end; format_ets_error(prev, Args, Cause) -> format_default(bad_key, Args, Cause); format_ets_error(rename, [_,NewName]=Args, Cause) -> case [format_cause(Args, Cause), if is_atom(NewName) -> ""; true -> bad_table_name end] of ["", ""] -> ["", name_already_exists]; Result -> Result end; format_ets_error(safe_fixtable, Args, Cause) -> format_default(bad_boolean, Args, Cause); format_ets_error(select, [_], _Cause) -> [bad_continuation]; format_ets_error(select, [_,_]=Args, Cause) -> format_default(bad_matchspec, Args, Cause); format_ets_error(select, [_,_,_]=Args, Cause) -> format_ms_limit(Args, Cause); format_ets_error(select_count, [_,_]=Args, Cause) -> format_default(bad_matchspec, Args, Cause); format_ets_error(select_count, [_,_,_]=Args, Cause) -> format_ms_limit(Args, Cause); format_ets_error(internal_select_delete, Args, Cause) -> format_default(bad_matchspec, Args, Cause); format_ets_error(select_replace, Args, Cause) -> format_default(bad_matchspec, Args, Cause); format_ets_error(select_reverse, [_], _Cause) -> [bad_continuation]; format_ets_error(select_reverse, [_,_]=Args, Cause) -> format_default(bad_matchspec, Args, Cause); format_ets_error(select_reverse, [_,_,_]=Args, Cause) -> format_ms_limit(Args, Cause); format_ets_error(setopts, Args, Cause) -> format_default(bad_options, Args, Cause); format_ets_error(slot, Args, Cause) -> format_default(range, Args, Cause); format_ets_error(update_counter, [_,_,UpdateOp]=Args, Cause) -> TabCause = format_cause(Args, Cause), case Cause of badkey -> [TabCause, bad_key, format_update_op(UpdateOp)]; keypos -> [TabCause, "", same_as_keypos]; position -> [TabCause, "", update_op_range]; none -> case is_update_op_top(UpdateOp) of false -> [TabCause, "", bad_update_op]; true -> %% This is the only possible remaining error. [TabCause, "", counter_not_integer] end; _ -> [TabCause, "", format_update_op(UpdateOp)] end; format_ets_error(update_counter, [_,_,UpdateOp,Default]=Args, Cause) -> case format_cause(Args, Cause) of TabCause when TabCause =/= [] -> [TabCause]; "" -> %% The table is OK. The error is in one or more of the %% other arguments. TupleCause = format_tuple(Default), case Cause of badkey -> ["", bad_key, format_update_op(UpdateOp) | TupleCause]; keypos -> ["", "", same_as_keypos | TupleCause]; position -> ["", "", update_op_range]; _ -> case {format_update_op(UpdateOp),TupleCause} of {"",[""]} -> %% UpdateOp and Default are individually %% OK. The only possible remaining %% problem is that the value in the record %% is not an integer. ["", "", counter_not_integer]; {UpdateOpCause,_} -> ["", "", UpdateOpCause | TupleCause] end end end; format_ets_error(update_element, [_,_,ElementSpec]=Args, Cause) -> TabCause = format_cause(Args, Cause), [TabCause, "" | case Cause of keypos -> [same_as_keypos]; _ -> case is_element_spec_top(ElementSpec) of true -> case TabCause of [] -> [range]; _ -> [] end; false -> [<<"is not a valid element specification">>] end end]; format_ets_error(whereis, _Args, _Cause) -> [bad_table_name]; format_ets_error(_, Args, Cause) -> [format_cause(Args, Cause)]. format_default(Default, Args, Cause) -> case format_cause(Args, Cause) of "" -> ["",Default]; Error -> [Error] end. is_element_spec_top(List) when is_list(List) -> lists:all(fun is_element_spec/1, List); is_element_spec_top(Other) -> is_element_spec(Other). is_element_spec({Pos, _Value}) when is_integer(Pos), Pos > 0 -> true; is_element_spec(_) -> false. format_ms_limit([_,Ms,_]=Args, Cause) -> [Tab, [], Limit] = format_limit(Args, Cause), case is_match_spec(Ms) of true -> [Tab, "", Limit]; false -> [Tab, bad_matchspec, Limit] end. format_limit([_,_,Limit]=Args, Cause) -> [format_cause(Args, Cause), "", format_non_negative_integer(Limit)]. format_non_negative_integer(N) -> if not is_integer(N) -> not_integer; N < 1 -> range; true -> "" end. format_object([_,Object|_]=Args, Cause) -> [format_cause(Args, Cause) | format_tuple(Object)]. format_tuple(Term) -> if tuple_size(Term) > 0 -> [""]; is_tuple(Term) -> [empty_tuple]; true -> [not_tuple] end. format_objects([_,Term|_]=Args, Cause) -> [format_cause(Args, Cause) | if tuple_size(Term) > 0 -> []; is_tuple(Term) -> [empty_tuple]; is_list(Term) -> try lists:all(fun(T) -> tuple_size(T) > 0 end, Term) of true -> []; false -> [not_tuple_or_list] catch _:_ -> [not_tuple_or_list] end; true -> [not_tuple] end]. format_cause(Args, Cause) -> case Cause of none -> ""; type -> case Args of [Ref|_] when is_reference(Ref) -> <<"not a valid table identifier">>; _ -> <<"not an atom or a table identifier">> end; id -> <<"the table identifier does not refer to an existing ETS table">>; access -> <<"the table identifier refers to an ETS table with insufficient access rights">>; table_type -> <<"the table identifier refers to an ETS table of a type not supported by this operation">>; %% The following error reasons don't have anything to do with %% the table argument, but with some of the other arguments. badkey -> ""; keypos -> ""; position -> ""; owner -> ""; not_owner -> "" end. is_match_spec(Term) -> ets:is_compiled_ms(Term) orelse try ets:match_spec_compile(Term) of _ -> true catch error:badarg -> false end. format_update_op(UpdateOp) -> case is_update_op_top(UpdateOp) of true -> ""; false -> bad_update_op end. is_update_op_top(List) when is_list(List) -> lists:all(fun is_update_op/1, List); is_update_op_top(Op) -> is_update_op(Op). is_update_op({Pos, Incr}) when is_integer(Pos), is_integer(Incr) -> true; is_update_op({Pos, Incr, Threshold, SetValue}) when is_integer(Pos), is_integer(Incr), is_integer(Threshold), is_integer(SetValue) -> true; is_update_op(Incr) -> is_integer(Incr). format_error_map([""|Es], ArgNum, Map) -> format_error_map(Es, ArgNum + 1, Map); format_error_map([{general, E}|Es], ArgNum, Map) -> format_error_map(Es, ArgNum, Map#{ general => expand_error(E)}); format_error_map([E|Es], ArgNum, Map) -> format_error_map(Es, ArgNum + 1, Map#{ArgNum => expand_error(E)}); format_error_map([], _, Map) -> Map. must_be_binary(Bin) -> must_be_binary(Bin, []). must_be_binary(Bin, Error) when is_binary(Bin) -> Error; must_be_binary(Bin, _Error) when is_bitstring(Bin) -> bitstring; must_be_binary(_, _) -> not_binary. must_be_hex_case(uppercase) -> []; must_be_hex_case(lowercase) -> []; must_be_hex_case(_) -> bad_hex_case. must_be_endianness(little) -> []; must_be_endianness(big) -> []; must_be_endianness(_) -> bad_endianness. must_be_fun(F, Arity) when is_function(F, Arity) -> []; must_be_fun(_, Arity) -> {not_fun,Arity}. must_be_integer(N) when is_integer(N) -> []; must_be_integer(_) -> not_integer. must_be_integer(N, Min, Max, Default) when is_integer(N) -> if Min =< N, N =< Max -> Default; true -> range end; must_be_integer(_, _, _, _) -> not_integer. must_be_integer(N, Min, Max) -> must_be_integer(N, Min, Max, []). must_be_non_neg_integer(N) -> must_be_integer(N, 0, infinity). must_be_iodata(Term) -> try iolist_size(Term) of _ -> [] catch error:_ -> not_iodata end. must_be_list(List) when is_list(List) -> try length(List) of _ -> [] catch error:badarg -> not_proper_list end; must_be_list(_) -> not_list. must_be_map(#{}) -> []; must_be_map(_) -> not_map. must_be_map_iterator_order(undefined) -> []; must_be_map_iterator_order(ordered) -> []; must_be_map_iterator_order(CmpFun) when is_function(CmpFun, 2) -> []; must_be_map_iterator_order(_) -> not_map_iterator_order. must_be_map_or_iter(Map) when is_map(Map) -> []; must_be_map_or_iter(Iter) -> case maps:is_iterator_valid(Iter) of true -> []; false -> not_map_or_iterator end. must_be_number(N) -> if is_number(N) -> []; true -> not_number end. must_be_pattern(P) -> try binary:match(<<"a">>, P) of _ -> [] catch error:badarg -> bad_binary_pattern end. must_be_position(Pos) when is_integer(Pos), Pos >= 0 -> []; must_be_position(Pos) when is_integer(Pos) -> range; must_be_position(_) -> not_integer. must_be_regexp(Term) -> %% First check if we can compile the regexp as this %% returns better error messages try re:compile(Term) of {ok, _} -> []; {error, Reason} -> {bad_regexp, Reason} catch error:_ -> %% Then check if we can run it as this also allows %% compiled reg exps try re:run("", Term) of _ -> [] catch error:_ -> not_regexp end end. expand_error(already_owner) -> <<"the process is already the owner of the table">>; expand_error(bad_boolean) -> <<"not a boolean value">>; expand_error(bad_binary_list) -> <<"not a flat list of binaries">>; expand_error(bad_char_data) -> <<"not valid character data (an iodata term)">>; expand_error(bad_binary_pattern) -> <<"not a valid pattern">>; expand_error(bad_continuation) -> <<"invalid continuation">>; expand_error(bad_encoding) -> <<"not a valid encoding">>; expand_error(bad_endianness) -> <<"must be 'big' or 'little'">>; expand_error(bad_info_item) -> <<"not a valid info item">>; expand_error(bad_iterator) -> <<"not a valid iterator">>; expand_error(bad_key) -> <<"not a key that exists in the table">>; expand_error(bad_matchspec) -> <<"not a valid match specification">>; expand_error(bad_options) -> <<"invalid options">>; expand_error(bad_table_name) -> <<"invalid table name (must be an atom)">>; expand_error(bad_update_op) -> <<"not a valid update operation">>; expand_error(bitstring) -> <<"is a bitstring (expected a binary)">>; expand_error(calling_self) -> <<"the device is not allowed to be the current process">>; expand_error(counter_not_integer) -> <<"the value in the given position, in the object, is not an integer">>; expand_error(dead_process) -> <<"the pid refers to a terminated process">>; expand_error(device_arguments) -> <<"the device does not exist">>; expand_error(device_terminated) -> <<"the device has terminated">>; expand_error(domain_error) -> <<"is outside the domain for this function">>; expand_error(empty_binary) -> <<"a zero-sized binary is not allowed">>; expand_error(empty_tuple) -> <<"is an empty tuple">>; expand_error(format_failed) -> <<"failed to format string">>; expand_error(invalid_format) -> <<"not a valid format string">>; expand_error(missing_argument_list) -> <<"possibly missing argument list">>; expand_error(name_already_exists) -> <<"table name already exists">>; expand_error({no_translation, In, Out}) -> unicode:characters_to_binary( io_lib:format("device failed to transcode string from ~p to ~p", [In, Out])); expand_error(not_atom) -> <<"not an atom">>; expand_error(not_binary) -> <<"not a binary">>; expand_error(bad_hex_case) -> <<"not 'uppercase' or 'lowercase'">>; expand_error(not_compiled_regexp) -> <<"not a compiled regular expression">>; expand_error(not_iodata) -> <<"not an iodata term">>; expand_error({not_fun,1}) -> <<"not a fun that takes one argument">>; expand_error({not_fun,2}) -> <<"not a fun that takes two arguments">>; expand_error({not_fun,3}) -> <<"not a fun that takes three arguments">>; expand_error(not_integer) -> <<"not an integer">>; expand_error(not_list) -> <<"not a list">>; expand_error(not_map_iterator_order) -> <<"not 'undefined', 'ordered', or a fun that takes two arguments">>; expand_error(not_map_or_iterator) -> <<"not a map or an iterator">>; expand_error(not_number) -> <<"not a number">>; expand_error(not_proper_list) -> <<"not a proper list">>; expand_error(not_map) -> <<"not a map">>; expand_error(not_owner) -> <<"the current process is not the owner">>; expand_error(not_pid) -> <<"not a pid">>; expand_error(not_regexp) -> <<"neither an iodata term nor a compiled regular expression">>; expand_error({bad_regexp, {Reason,Column}}) -> unicode:characters_to_binary( io_lib:format("could not parse regular expression~n" "~ts on character ~p", [Reason, Column])); expand_error(not_tuple) -> <<"not a tuple">>; expand_error(not_tuple_or_list) -> <<"not a non-empty tuple or a list of non-empty tuples">>; expand_error(range) -> <<"out of range">>; expand_error(same_as_keypos) -> <<"the position is the same as the key position">>; expand_error({unknown_error,Cause}) -> unicode:characters_to_binary( io_lib:format("unknown error: ~tp", [Cause])); expand_error(update_op_range) -> <<"the position in the update operation is out of range">>; expand_error(Other) -> Other.