[m-rev.] for review: implement --warn-suspicious-recursion
Peter Wang
novalazy at gmail.com
Wed May 1 15:22:58 AEST 2019
On Wed, 01 May 2019 12:13:49 +1000 (AEST), "Zoltan Somogyi" <zoltan.somogyi at runbox.com> wrote:
> Implement warnings for suspicious recursion.
>
> diff --git a/compiler/simplify_goal_call.m b/compiler/simplify_goal_call.m
> index 85aade0..cd44674 100644
> --- a/compiler/simplify_goal_call.m
> +++ b/compiler/simplify_goal_call.m
...
> +input_args_are_suspicious(_, _, _, _, [], [], _,
> + !AllInputsEqv, !AllInputsEqvOrSvar, !HeadBaseNames, !ArgBaseNames).
> +input_args_are_suspicious(ModuleInfo, CommonInfo, VarSet, VarNameRemap,
> + [HeadVar | HeadVars], [ArgVar | ArgVars], [Mode | Modes],
> + !AllInputsEqv, !AllInputsEqvOrSvar, !HeadBaseNames, !ArgBaseNames) :-
> + InitialInst = mode_get_initial_inst(ModuleInfo, Mode),
> + ( if inst_is_bound(ModuleInfo, InitialInst) then
> + % This is an input argument.
> +
> + % Fail (and thus don't generate a warning) if an input argument's
> + % initial inst is not ground, which means it may contain `any' insts.
> + % This is because the argument might have become more constrained
> + % before the recursive call, in which case the recursion might
> + % eventually terminate.
> %
> -:- pred input_args_are_equiv(module_info::in, common_info::in,
> - list(prog_var)::in, list(prog_var)::in, list(mer_mode)::in) is semidet.
> + % XXX This check will only allow warnings if the inputs are all fully
> + % ground; i.e. we won't warn in the case of partially instantiated
> + % insts such as list_skel(free). Still, it is better to miss warnings
> + % in that rare and unsupported case rather than to issue spurious
> + % warnings in cases involving `any' insts.
> + inst_is_ground(ModuleInfo, InitialInst),
>
> -input_args_are_equiv(_, _, [], [], _).
> -input_args_are_equiv(ModuleInfo, CommonInfo,
> - [ArgVar | ArgVars], [HeadVar | HeadVars], [Mode | Modes]) :-
> - ( if mode_is_input(ModuleInfo, Mode) then
> + ( if
> common_vars_are_equivalent(CommonInfo, ArgVar, HeadVar)
> + then
> + true
> + else
> + % If the input argument is not the same in the call as in
> + % the clause head (which it won't if we get here), then fail
it won't be
> + % (and thus don't generate a warning) if the input argument
> + % is unique. This is because in that case, the recursion may be
> + % terminated by changes in the state *outside* the view
> + % of the compiler.
> + inst_is_not_partly_unique(ModuleInfo, InitialInst),
> +
> + !:AllInputsEqv = not_all_inputs_eqv,
> + % If either the argument or the head variable is unnamed, then
> + % we have reason to believe the recursive call is suspicious,
> + % so we fail.
no reason?
> + head_var_name(VarSet, VarNameRemap, HeadVar, HeadName),
> + varset.search_name(VarSet, ArgVar, ArgName),
> + delete_any_numeric_suffix(HeadName, HeadBaseName),
> + delete_any_numeric_suffix(ArgName, ArgBaseName),
> + ( if HeadBaseName = ArgBaseName then
> + ( if string.prefix(HeadBaseName, "STATE_VARIABLE") then
> + true
> + else
> + !:AllInputsEqvOrSvar = not_all_inputs_eqv_or_svar
> + )
> else
> + !:AllInputsEqvOrSvar = not_all_inputs_eqv_or_svar,
> + set.insert(HeadBaseName, !HeadBaseNames),
> + set.insert(ArgBaseName, !ArgBaseNames)
> + )
> + )
> + else
> + % This is not an input argument.
> true
> ),
> - input_args_are_equiv(ModuleInfo, CommonInfo, ArgVars, HeadVars, Modes).
> + input_args_are_suspicious(ModuleInfo, CommonInfo, VarSet, VarNameRemap,
> + HeadVars, ArgVars, Modes,
> + !AllInputsEqv, !AllInputsEqvOrSvar, !HeadBaseNames, !ArgBaseNames).
> +
> +:- pred delete_any_numeric_suffix(string::in, string::out) is det.
> +
> +delete_any_numeric_suffix(Str, StrNoSuffix) :-
> + ( if has_numeric_suffix(Str, StrNoSuffixPrime) then
> + StrNoSuffix = StrNoSuffixPrime
> + else
> + StrNoSuffix = Str
> + ).
It would be straightforward to avoid converting the string to a list and
back. I can do that later if you wish.
> diff --git a/library/string.m b/library/string.m
> index 251dfed..ad6bbf7 100644
> --- a/library/string.m
> +++ b/library/string.m
> @@ -115,6 +115,7 @@
>
> % Convert the string to a list of characters (code points).
> % Throws an exception if the list of characters contains a null character.
> + % XXX I (zs) see no code that would throw this exception.
> %
> % NOTE: In the future we may also throw an exception if the list contains
> % a surrogate code point.
That happens in the reverse mode of to_char_list, i.e. from_char_list.
> @@ -143,6 +144,18 @@
> %
> :- pred semidet_from_char_list(list(char)::in, string::uo) is semidet.
>
> + % Convert the string to a list of characters (code points) in reverse order.
> + % Throws an exception if the list of characters contains a null character.
> + % XXX I (zs) see no code that would throw this exception.
It would be thrown by from_rev_char_list.
> + %
> + % NOTE: In the future we may also throw an exception if the list contains
> + % a surrogate code point.
> + %
> +:- func to_rev_char_list(string) = list(char).
> +:- pred to_rev_char_list(string, list(char)).
> +:- mode to_rev_char_list(in, out) is det.
> +:- mode to_rev_char_list(uo, in) is det.
> +
> % Same as from_char_list, except that it reverses the order
> % of the characters.
> % Throws an exception if the list of characters contains a null character.
> %---------------------%
>
> +to_rev_char_list(S) = Cs :-
> + to_rev_char_list(S, Cs).
> +
> +:- pragma promise_equivalent_clauses(to_rev_char_list/2).
> +
> +to_rev_char_list(Str::in, CharList::out) :-
> + do_to_rev_char_list(Str, CharList).
> +to_rev_char_list(Str::uo, CharList::in) :-
> + from_rev_char_list(CharList, Str).
> +
> +:- pred do_to_rev_char_list(string::in, list(char)::out) is det.
> +
> +:- pragma foreign_proc("C",
> + do_to_rev_char_list(Str::in, CharList::out),
> + [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
> + does_not_affect_liveness, no_sharing],
> +"{
> + MR_Integer len = strlen(Str);
Delete len.
> + MR_Integer pos;
> + int c;
> +
> + CharList = MR_list_empty_msg(MR_ALLOC_ID);
> + pos = 0;
> + for (;;) {
> + c = MR_utf8_get_next(Str, &pos);
> + if (c <= 0) {
> + break;
> + }
> + CharList = MR_char_list_cons_msg((MR_UnsignedChar) c, CharList,
> + MR_ALLOC_ID);
> + }
> +}").
> +
> +do_to_rev_char_list(Str, RevCharList) :-
> + do_to_char_list(Str, CharList),
> + list.reverse(CharList, RevCharList).
do_to_char_list can be written efficiently in Mercury using unsafe_index_next.
I can do that later if you wish.
> diff --git a/tests/warnings/suspicious_recursion.m b/tests/warnings/suspicious_recursion.m
> index e69de29..e0bd09d 100644
> --- a/tests/warnings/suspicious_recursion.m
> +++ b/tests/warnings/suspicious_recursion.m
> @@ -0,0 +1,42 @@
> +%---------------------------------------------------------------------------%
> +% vim: ts=4 sw=4 et ft=mercury
> +%---------------------------------------------------------------------------%
> +%
> +% Test the warningsfor suspicious recursion.
Missing space.
The rest looks fine.
Peter
More information about the reviews
mailing list