From 88beede7dc2615618ca2397cd76b2b952f8c760d Mon Sep 17 00:00:00 2001 From: Akos Hajdu Date: Tue, 13 Jul 2021 03:30:45 -0700 Subject: [PATCH] [erl-frontend] Add support for guards Summary: Add support for guards, both in function clauses and case clauses. Reviewed By: rgrig Differential Revision: D29634937 fbshipit-source-id: 5a9f8ec2d --- infer/src/erlang/ErlangTranslator.ml | 50 ++++++++--- .../codetoanalyze/erlang/nonmatch/issues.exp | 11 +++ .../erlang/nonmatch/src/case_guards.erl | 46 +++++++++++ .../erlang/nonmatch/src/function_guards.erl | 82 +++++++++++++++++++ 4 files changed, 179 insertions(+), 10 deletions(-) create mode 100644 infer/tests/codetoanalyze/erlang/nonmatch/src/case_guards.erl create mode 100644 infer/tests/codetoanalyze/erlang/nonmatch/src/function_guards.erl diff --git a/infer/src/erlang/ErlangTranslator.ml b/infer/src/erlang/ErlangTranslator.ml index 5947436b4..cbf48013f 100644 --- a/infer/src/erlang/ErlangTranslator.ml +++ b/infer/src/erlang/ErlangTranslator.ml @@ -348,6 +348,38 @@ let rec translate_pattern env (value : Ident.t) {Ast.line; simple_expression} : Block.make_failure env +and translate_guard_expression env (expression : Ast.expression) : Ident.t * Block.t = + let id = Ident.create_fresh Ident.knormal in + let block = translate_expression {env with result= Present (Exp.Var id)} expression in + (* If we'd like to catch "silent" errors later, we might do it here *) + (id, block) + + +and translate_guard env (expressions : Ast.expression list) : Block.t = + match expressions with + | [] -> + Block.make_success env + | _ -> + let ids_blocks = List.map ~f:(translate_guard_expression env) expressions in + let ids, expr_blocks = List.unzip ids_blocks in + let make_and (e1 : Exp.t) (e2 : Exp.t) = Exp.BinOp (LAnd, e1, e2) in + let make_var (id : Ident.t) : Exp.t = Var id in + let cond = List.reduce_exn (List.map ids ~f:make_var) ~f:make_and in + let start = Node.make_nop env in + let exit_success = Node.make_if env true cond in + let exit_failure = Node.make_if env false cond in + start |~~> [exit_success; exit_failure] ; + Block.all env (expr_blocks @ [{Block.start; exit_success; exit_failure}]) + + +and translate_guard_sequence env (guards : Ast.expression list list) : Block.t = + match guards with + | [] -> + Block.make_success env + | _ -> + Block.any env (List.map ~f:(translate_guard env) guards) + + and translate_expression env {Ast.line; simple_expression} = let env = update_location line env in let any = ptr_typ_of_name Any in @@ -504,22 +536,20 @@ and translate_body env body : Block.t = (** Assumes that the values on which patterns should be matched have been loaded into the identifiers listed in [values]. *) -and translate_case_clause env (values : Ident.t list) {Ast.line= _; patterns; guards= _; body} : +and translate_case_clause env (values : Ident.t list) {Ast.line= _; patterns; guards; body} : Block.t = - let matchers_block = - let f (one_value, one_pattern) = translate_pattern env one_value one_pattern in - let matchers = List.map ~f (List.zip_exn values patterns) in - Block.all env matchers - in + let f (one_value, one_pattern) = translate_pattern env one_value one_pattern in + let matchers = List.map ~f (List.zip_exn values patterns) in + let guard_block = translate_guard_sequence env guards in + let matchers_and_guards = Block.all env [Block.all env matchers; guard_block] in let body_block = translate_body env body in - (* TODO: Evaluate the guards. *) - matchers_block.exit_success |~~> [body_block.start] ; + matchers_and_guards.exit_success |~~> [body_block.start] ; let () = let (Present procdesc) = env.procdesc in body_block.exit_failure |~~> [Procdesc.get_exit_node procdesc] in - { start= matchers_block.start - ; exit_failure= matchers_block.exit_failure + { start= matchers_and_guards.start + ; exit_failure= matchers_and_guards.exit_failure ; exit_success= body_block.exit_success } diff --git a/infer/tests/codetoanalyze/erlang/nonmatch/issues.exp b/infer/tests/codetoanalyze/erlang/nonmatch/issues.exp index 19e5ce315..81bc64d24 100644 --- a/infer/tests/codetoanalyze/erlang/nonmatch/issues.exp +++ b/infer/tests/codetoanalyze/erlang/nonmatch/issues.exp @@ -2,6 +2,10 @@ codetoanalyze/erlang/nonmatch/src/case_expression.erl, case_simple/1, 1, NONEXHA codetoanalyze/erlang/nonmatch/src/case_expression.erl, case_test_simple3_Bad/0, -14, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `case_simple/1`,no pattern match here] codetoanalyze/erlang/nonmatch/src/case_expression.erl, case_test_tail3_Bad/0, -15, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `tail_with_case/1`,no pattern match here] codetoanalyze/erlang/nonmatch/src/case_expression.erl, tail_with_case/1, 1, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [*** LATENT ***,no pattern match here] +codetoanalyze/erlang/nonmatch/src/case_guards.erl, accepts_positive/1, 1, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [*** LATENT ***,no pattern match here] +codetoanalyze/erlang/nonmatch/src/case_guards.erl, accepts_positive2/1, 1, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [*** LATENT ***,no pattern match here] +codetoanalyze/erlang/nonmatch/src/case_guards.erl, test_accepts_positive2_Bad/0, -16, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `accepts_positive2/1`,no pattern match here] +codetoanalyze/erlang/nonmatch/src/case_guards.erl, test_accepts_positive_Bad/0, -15, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `accepts_positive/1`,no pattern match here] codetoanalyze/erlang/nonmatch/src/function.erl, assert_empty/1, 0, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [*** LATENT ***,no pattern match here] codetoanalyze/erlang/nonmatch/src/function.erl, assert_second_is_nil/1, 0, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [*** LATENT ***,no pattern match here] codetoanalyze/erlang/nonmatch/src/function.erl, fp_list_match_test_secondnil1_Ok/0, -17, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `assert_second_is_nil/1`,no pattern match here] @@ -11,6 +15,13 @@ codetoanalyze/erlang/nonmatch/src/function.erl, list_match_test_empty3_Bad/0, -1 codetoanalyze/erlang/nonmatch/src/function.erl, list_match_test_secondnil3_Bad/0, -22, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `assert_second_is_nil/1`,no pattern match here] codetoanalyze/erlang/nonmatch/src/function.erl, list_match_test_tail3_Bad/0, -8, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `tail/1`,no pattern match here] codetoanalyze/erlang/nonmatch/src/function.erl, tail/1, 0, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [*** LATENT ***,no pattern match here] +codetoanalyze/erlang/nonmatch/src/function_guards.erl, accepts_positive/1, 0, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [*** LATENT ***,no pattern match here] +codetoanalyze/erlang/nonmatch/src/function_guards.erl, accepts_positive2/1, 0, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [*** LATENT ***,no pattern match here] +codetoanalyze/erlang/nonmatch/src/function_guards.erl, fp_accepts_all_tricky3/1, 0, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [*** LATENT ***,no pattern match here] +codetoanalyze/erlang/nonmatch/src/function_guards.erl, possible_exception/1, 0, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [*** LATENT ***,no pattern match here] +codetoanalyze/erlang/nonmatch/src/function_guards.erl, test_accepts_positive2_Bad/0, -23, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `accepts_positive2/1`,no pattern match here] +codetoanalyze/erlang/nonmatch/src/function_guards.erl, test_accepts_positive_Bad/0, -19, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `accepts_positive/1`,no pattern match here] +codetoanalyze/erlang/nonmatch/src/function_guards.erl, test_possible_exception_Bad/0, -37, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `possible_exception/1`,no pattern match here] codetoanalyze/erlang/nonmatch/src/match.erl, fp_match_test_c_Ok/0, 1, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [no pattern match here] codetoanalyze/erlang/nonmatch/src/match.erl, fp_match_test_d_Ok/0, -13, NONEXHAUSTIVE_PATTERN_MATCH, no_bucket, ERROR, [calling context starts here,in call to `tail/1`,no pattern match here] codetoanalyze/erlang/nonmatch/src/match.erl, match_test_b_Bad/0, 1, CONSTANT_ADDRESS_DEREFERENCE, no_bucket, WARNING, [in call to `two/0`,is the constant 2,assigned,returned,return from call to `two/0`,invalid access occurs here] diff --git a/infer/tests/codetoanalyze/erlang/nonmatch/src/case_guards.erl b/infer/tests/codetoanalyze/erlang/nonmatch/src/case_guards.erl new file mode 100644 index 000000000..c3bd2a7ed --- /dev/null +++ b/infer/tests/codetoanalyze/erlang/nonmatch/src/case_guards.erl @@ -0,0 +1,46 @@ +% Copyright (c) Facebook, Inc. and its affiliates. +% +% This source code is licensed under the MIT license found in the +% LICENSE file in the root directory of this source tree. + +-module(case_guards). + +-export([ + test_accepts_positive_Bad/0, + test_accepts_positive_Ok/0, + test_accepts_positive2_Bad/0, + test_accepts_positive2_Ok/0, + test_accepts_all_Ok/0 +]). + +accepts_positive(X) -> + case X of + X when X > 0 -> ok + end. + +accepts_positive2(X) -> + case X of + X when 1 =:= 1, 1 =:= 0; X > 0 -> ok + end. + +accepts_all(X) -> + case X of + X when X > 0 -> ok; + X when not (X > 0) -> ok + end. + +test_accepts_positive_Bad() -> + accepts_positive(0). + +test_accepts_positive_Ok() -> + accepts_positive(1). + +test_accepts_positive2_Bad() -> + accepts_positive2(0). + +test_accepts_positive2_Ok() -> + accepts_positive2(1). + +test_accepts_all_Ok() -> + accepts_all(0), + accepts_all(1). diff --git a/infer/tests/codetoanalyze/erlang/nonmatch/src/function_guards.erl b/infer/tests/codetoanalyze/erlang/nonmatch/src/function_guards.erl new file mode 100644 index 000000000..74d16323b --- /dev/null +++ b/infer/tests/codetoanalyze/erlang/nonmatch/src/function_guards.erl @@ -0,0 +1,82 @@ +% Copyright (c) Facebook, Inc. and its affiliates. +% +% This source code is licensed under the MIT license found in the +% LICENSE file in the root directory of this source tree. + +-module(function_guards). + +-export([ + test_accepts_positive_Bad/0, + test_accepts_positive_Ok/0, + test_accepts_positive2_Bad/0, + test_accepts_positive2_Ok/0, + test_accepts_all_basic_Ok/0, + test_accepts_all_basic2_Ok/0, + test_accepts_all_tricky_Ok/0, + test_accepts_all_tricky2_Ok/0, + test_accepts_all_tricky3_Ok/0, + test_possible_exception_Ok/0, + test_possible_exception_Bad/0, + fn_test_possible_exception2_Bad/0 +]). + +accepts_positive(X) when X > 0 -> ok. + +accepts_positive2(X) when 1 =:= 1, 1 =:= 0; X > 0 -> ok. + +accepts_all_basic(X) when X > 0 -> ok; +accepts_all_basic(_) -> ok. + +accepts_all_basic2(X) when X > 0; 1 =:= 1 -> ok. + +accepts_all_tricky(X) when X > 0; not (X > 0) -> ok. + +accepts_all_tricky2(X) when X > 0 -> ok; +accepts_all_tricky2(X) when not (X > 0) -> ok. + +% FP (T95259136) +fp_accepts_all_tricky3(X) when X > 0; X =< 0 -> ok. + +possible_exception(X) when 1 div X =:= 1 -> ok. + +test_accepts_positive_Bad() -> + accepts_positive(0). + +test_accepts_positive_Ok() -> + accepts_positive(1). + +test_accepts_positive2_Bad() -> + accepts_positive2(0). + +test_accepts_positive2_Ok() -> + accepts_positive2(1). + +test_accepts_all_basic_Ok() -> + accepts_all_basic(0), + accepts_all_basic(1). + +test_accepts_all_basic2_Ok() -> + accepts_all_basic2(0), + accepts_all_basic2(1). + +test_accepts_all_tricky_Ok() -> + accepts_all_tricky(0), + accepts_all_tricky(1). + +test_accepts_all_tricky2_Ok() -> + accepts_all_tricky2(0), + accepts_all_tricky2(1). + +test_accepts_all_tricky3_Ok() -> + fp_accepts_all_tricky3(0), + fp_accepts_all_tricky3(1). + +test_possible_exception_Ok() -> + possible_exception(1). + +test_possible_exception_Bad() -> + possible_exception(2). + +% FN (T95472386) +fn_test_possible_exception2_Bad() -> + possible_exception(0).