Fix return parameter handling

Summary:
public
1. Change exps result of translating call expressions
2. Modify field/method_deref_trans to make them work with rvalues returned by function
3. Add E2E test

Reviewed By: jberdine

Differential Revision: D2874822

fb-gh-sync-id: 42c617d
master
Andrzej Kotulski 9 years ago committed by facebook-github-bot-1
parent 74dbfd72b5
commit 44a6ec4f10

@ -252,7 +252,8 @@ struct
call_flags ~is_objc_method =
let ret_id = if (Sil.typ_equal return_type Sil.Tvoid) then []
else [Ident.create_fresh Ident.knormal] in
let ret_id_call, params, instrs_after_call, initd_exps =
let ret_id', params, initd_exps, ret_exps =
(* Assumption: should_add_return_param will return true only for struct types *)
if CMethod_trans.should_add_return_param return_type is_objc_method then
let param_type = Sil.Tptr (return_type, Sil.Pk_pointer) in
let var_exp = match trans_state.var_exp with
@ -261,18 +262,32 @@ struct
let tenv = trans_state.context.CContext.tenv in
let procdesc = trans_state.context.CContext.procdesc in
let pvar = mk_temp_sil_var tenv procdesc "__temp_return_" in
Cfg.Procdesc.append_locals procdesc [(Sil.pvar_get_name pvar, param_type)];
Cfg.Procdesc.append_locals procdesc [(Sil.pvar_get_name pvar, return_type)];
Sil.Lvar pvar in
(* It is very confusing - same expression has two different types in two contexts:*)
(* 1. if passed as parameter it's RETURN_TYPE* since we are passing it as rvalue *)
(* 2. for return expression it's RETURN_TYPE since backend allows to treat it as lvalue*)
(* of RETURN_TYPE *)
(* Implications: *)
(* Fields: field_deref_trans relies on it - if exp has RETURN_TYPE then *)
(* it means that it's not lvalue in clang's AST (it'd be reference otherwise) *)
(* Methods: method_deref_trans actually wants a pointer to the object, which is*)
(* equivalent of value of ret_param. Since ret_exp has type RETURN_TYPE,*)
(* we optionally add pointer there to avoid backend confusion. *)
(* It works either way *)
(* Passing by value: may cause problems - there needs to be extra Sil.Letderef, but*)
(* doing so would create problems with methods. Passing structs by*)
(* value doesn't work good anyway. This may need to be revisited later*)
let ret_param = (var_exp, param_type) in
let ret_id' = match ret_id with [x] -> x | _ -> assert false in
let deref_instr = Sil.Letderef (ret_id', var_exp, return_type, sil_loc) in
[], params_sil @ [ret_param], [deref_instr], [var_exp]
else ret_id, params_sil, [], [] in
let call_instr = Sil.Call (ret_id_call, function_sil, params, sil_loc, call_flags) in
let ret_exp = (var_exp, return_type) in
[], params_sil @ [ret_param], [var_exp], [ret_exp]
else ret_id, params_sil, [], match ret_id with [x] -> [(Sil.Var x, return_type)] | _ -> [] in
let call_instr = Sil.Call (ret_id', function_sil, params, sil_loc, call_flags) in
{ empty_res_trans with
ids = ret_id;
instrs = call_instr :: instrs_after_call;
initd_exps = initd_exps }
ids = ret_id';
instrs = [call_instr];
exps = ret_exps;
initd_exps = initd_exps;}
let breakStmt_trans trans_state =
match trans_state.continuation with
@ -425,22 +440,35 @@ struct
dereference_value_from_result sil_loc res_trans ~strip_pointer:true
else res_trans
let field_deref_trans trans_state pre_trans_result decl_ref =
let field_deref_trans trans_state stmt_info pre_trans_result decl_ref =
let open CContext in
let context = trans_state.context in
let sil_loc = CLocation.get_sil_location stmt_info context in
let name_info, _, type_ptr = get_info_from_decl_ref decl_ref in
Printing.log_out "!!!!! Dealing with field '%s' @." name_info.Clang_ast_t.ni_name;
let field_typ = CTypes_decl.type_ptr_to_sil_type context.tenv type_ptr in
let (obj_sil, class_typ) = extract_exp_from_list pre_trans_result.exps
"WARNING: in Field dereference we expect to know the object\n" in
let is_pointer_typ = match class_typ with
| Sil.Tptr _ -> true
| _ -> false in
let class_typ =
match class_typ with
| Sil.Tptr (t, _) -> CTypes.expand_structured_type context.CContext.tenv t
| t -> t in
Printing.log_out "Type is '%s' @." (Sil.typ_to_string class_typ);
let field_name = General_utils.mk_class_field_name name_info in
let exp = Sil.Lfield (obj_sil, field_name, class_typ) in
{ pre_trans_result with exps = [(exp, field_typ)] }
let field_exp = Sil.Lfield (obj_sil, field_name, class_typ) in
let exp, deref_ids, deref_instrs = if not is_pointer_typ then
(* There will be no LValueToRValue cast, but backend needs dereference there either way *)
let id = Ident.create_fresh Ident.knormal in
let deref_instr = Sil.Letderef (id, field_exp, field_typ, sil_loc) in
Sil.Var id, [id], [deref_instr]
else
field_exp, [], [] in
let instrs = pre_trans_result.instrs @ deref_instrs in
let ids = pre_trans_result.ids @ deref_ids in
{ pre_trans_result with ids; instrs; exps = [(exp, field_typ)] }
let method_deref_trans trans_state pre_trans_result decl_ref =
let open CContext in
@ -458,8 +486,14 @@ struct
(* pre_trans_result.exps may contain expr for 'this' parameter:*)
(* if it comes from CXXMemberCallExpr it will be there *)
(* if it comes from CXXOperatorCallExpr it won't be there and will be added later *)
assert (IList.length pre_trans_result.exps <= 1);
pre_trans_result.exps)
(* In case of CXXMemberCallExpr it's possible that type of 'this' parameter *)
(* won't have a pointer - if that happens add a pointer to type of the object *)
match pre_trans_result.exps with
| [] -> []
| [(_, Sil.Tptr _ )] -> pre_trans_result.exps
| [(sil, typ)] -> [(sil, Sil.Tptr (typ, Sil.Pk_reference))]
| _ -> assert false
)
else
(* don't add 'this' expression for static methods *)
[] in
@ -517,7 +551,7 @@ struct
| `EnumConstant -> enum_constant_trans trans_state decl_ref
| `Function -> function_deref_trans trans_state decl_ref
| `Var | `ImplicitParam | `ParmVar -> var_deref_trans trans_state stmt_info decl_ref
| `Field | `ObjCIvar -> field_deref_trans trans_state pre_trans_result decl_ref
| `Field | `ObjCIvar -> field_deref_trans trans_state stmt_info pre_trans_result decl_ref
| `CXXMethod | `CXXConstructor -> method_deref_trans trans_state pre_trans_result decl_ref
| _ ->
let print_error decl_kind =
@ -741,7 +775,11 @@ struct
| None ->
let res_trans_call =
match cast_trans context act_params sil_loc callee_pname_opt function_type with
| Some (id, instr, _) -> { empty_res_trans with ids = [id]; instrs = [instr] }
| Some (id, instr, _) ->
{ empty_res_trans with
ids = [id];
instrs = [instr];
exps = [(Sil.Var id, function_type)]; }
| None ->
let call_flags = { Sil.cf_default with Sil.cf_is_objc_block = is_call_to_block; } in
create_call_instr trans_state function_type sil_fe act_params sil_loc call_flags
@ -758,10 +796,7 @@ struct
Cg.add_edge context.cg procname callee_pname;
end
| None -> ());
(match res_trans_call.ids with
| [] -> { res_trans_to_parent with exps =[] }
| [ret_id'] -> { res_trans_to_parent with exps =[(Sil.Var ret_id', function_type)] }
| _ -> assert false) (* by construction of red_id, we cannot be in this case *)
{ res_trans_to_parent with exps = res_trans_call.exps }
and cxx_method_construct_call_trans trans_state_pri result_trans_callee params_stmt
si function_type =
@ -799,10 +834,8 @@ struct
let result_trans_to_parent =
PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname si all_res_trans in
Cg.add_edge context.CContext.cg procname callee_pname;
match res_trans_call.ids with
| [] -> { result_trans_to_parent with exps = [] }
| [ret_id'] -> { result_trans_to_parent with exps = [(Sil.Var ret_id', function_type)] }
| _ -> assert false (* by construction of red_id, we cannot be in this case *)
{ result_trans_to_parent with exps = res_trans_call.exps }
and cxxMemberCallExpr_trans trans_state si stmt_list expr_info =
let context = trans_state.context in
@ -936,10 +969,8 @@ struct
let all_res_trans = res_trans_subexpr_list @ [res_trans_block; res_trans_call] in
let res_trans_to_parent =
PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname si all_res_trans in
match res_trans_call.ids with
| [] -> { res_trans_to_parent with exps = [] }
| [ret_id'] -> { res_trans_to_parent with exps = [(Sil.Var ret_id', method_type)] }
| _ -> assert false (* by construction of red_id, we cannot be in this case *)
{ res_trans_to_parent with exps = res_trans_call.exps }
and dispatch_function_trans trans_state stmt_info stmt_list ei n =
Printing.log_out "\n Call to a dispatch function treated as special case...\n";
@ -1656,7 +1687,11 @@ struct
and do_memb_ivar_ref_exp trans_state expr_info stmt_info stmt_list decl_ref =
let exp_stmt = extract_stmt_from_singleton stmt_list
"WARNING: in MemberExpr there must be only one stmt defining its expression.\n" in
let result_trans_exp_stmt = exec_with_lvalue_as_reference instruction trans_state exp_stmt in
(* Don't pass var_exp to child of MemberExpr - this may lead to initializing variable *)
(* with wrong value. For example, we don't want p to be initialized with X(1) for:*)
(* int p = X(1).field; *)
let trans_state' = { trans_state with var_exp = None } in
let result_trans_exp_stmt = exec_with_lvalue_as_reference instruction trans_state' exp_stmt in
decl_ref_trans trans_state result_trans_exp_stmt stmt_info expr_info decl_ref
and objCIvarRefExpr_trans trans_state stmt_info expr_info stmt_list obj_c_ivar_ref_expr_info =

@ -1,5 +1,5 @@
digraph iCFG {
12 [label="12: DeclStmt \n n$2=*&a:class A * [line 22]\n _fun_A_get(n$2:class A *,1:int ,&SIL_materialize_temp__n$1:struct X *) [line 22]\n n$3=*&SIL_materialize_temp__n$1:struct X [line 22]\n _fun_X_X(&x:struct X *,&SIL_materialize_temp__n$1:struct X ) [line 22]\n REMOVE_TEMPS(n$2,n$3); [line 22]\n NULLIFY(&a,false); [line 22]\n " shape="box"]
12 [label="12: DeclStmt \n n$2=*&a:class A * [line 22]\n _fun_A_get(n$2:class A *,1:int ,&SIL_materialize_temp__n$1:struct X *) [line 22]\n _fun_X_X(&x:struct X *,&SIL_materialize_temp__n$1:struct X ) [line 22]\n REMOVE_TEMPS(n$2); [line 22]\n NULLIFY(&a,false); [line 22]\n " shape="box"]
12 -> 11 ;

@ -9,6 +9,11 @@
struct X {
int f;
// copy constructor that doesn't use init list
X(const X& x) { f = x.f; }
X() { f = 1; }
int div() { return 1 / f; }
int skip(); // this should be skip in the backend
};
@ -18,7 +23,29 @@ X get(int a) {
return x;
}
int test() {
int get_div0() {
X x = get(0);
return 1 / x.f;
}
int get_field_div0() {
get(0).skip(); // this should do nothing - backend shouldn't crash
return 1 / get(0).f;
}
int get_method_div0() {
return get(0).div();
}
int get_div1() {
X x = get(1);
return 1 / x.f;
}
int get_field_div1() {
return 1 / get(1).f;
}
int get_method_div1() {
return get(1).div();
}

@ -1,50 +1,132 @@
digraph iCFG {
13 [label="13: DeclStmt \n _fun_get(0:int ,&SIL_materialize_temp__n$1:struct X *) [line 22]\n n$2=*&SIL_materialize_temp__n$1:struct X [line 22]\n _fun_X_X(&x:struct X *,&SIL_materialize_temp__n$1:struct X ) [line 22]\n REMOVE_TEMPS(n$2); [line 22]\n " shape="box"]
35 [label="35: Return Stmt \n _fun_get(1:int ,&__temp_return_n$1:class X *) [line 50]\n n$2=_fun_X_div(&__temp_return_n$1:class X &) [line 50]\n *&return:int =n$2 [line 50]\n REMOVE_TEMPS(n$2); [line 50]\n NULLIFY(&__temp_return_n$1,false); [line 50]\n APPLY_ABSTRACTION; [line 50]\n " shape="box"]
35 -> 34 ;
34 [label="34: Exit get_method_div1 \n " color=yellow style=filled]
33 [label="33: Start get_method_div1\nFormals: \nLocals: __temp_return_n$1:class X \n DECLARE_LOCALS(&return,&__temp_return_n$1); [line 49]\n " color=yellow style=filled]
33 -> 35 ;
32 [label="32: Return Stmt \n _fun_get(1:int ,&__temp_return_n$1:class X *) [line 46]\n n$2=*&__temp_return_n$1.f:int [line 46]\n *&return:int =(1 / n$2) [line 46]\n REMOVE_TEMPS(n$2); [line 46]\n NULLIFY(&__temp_return_n$1,false); [line 46]\n APPLY_ABSTRACTION; [line 46]\n " shape="box"]
32 -> 31 ;
31 [label="31: Exit get_field_div1 \n " color=yellow style=filled]
30 [label="30: Start get_field_div1\nFormals: \nLocals: __temp_return_n$1:class X \n DECLARE_LOCALS(&return,&__temp_return_n$1); [line 45]\n " color=yellow style=filled]
30 -> 32 ;
29 [label="29: DeclStmt \n _fun_get(1:int ,&SIL_materialize_temp__n$1:class X *) [line 41]\n _fun_X_X(&x:class X *,&SIL_materialize_temp__n$1:class X &) [line 41]\n " shape="box"]
29 -> 28 ;
28 [label="28: Return Stmt \n n$0=*&x.f:int [line 42]\n *&return:int =(1 / n$0) [line 42]\n REMOVE_TEMPS(n$0); [line 42]\n NULLIFY(&SIL_materialize_temp__n$1,false); [line 42]\n NULLIFY(&x,false); [line 42]\n APPLY_ABSTRACTION; [line 42]\n " shape="box"]
28 -> 27 ;
27 [label="27: Exit get_div1 \n " color=yellow style=filled]
26 [label="26: Start get_div1\nFormals: \nLocals: x:class X SIL_materialize_temp__n$1:class X \n DECLARE_LOCALS(&return,&x,&SIL_materialize_temp__n$1); [line 40]\n " color=yellow style=filled]
26 -> 29 ;
25 [label="25: Return Stmt \n _fun_get(0:int ,&__temp_return_n$1:class X *) [line 37]\n n$2=_fun_X_div(&__temp_return_n$1:class X &) [line 37]\n *&return:int =n$2 [line 37]\n REMOVE_TEMPS(n$2); [line 37]\n NULLIFY(&__temp_return_n$1,false); [line 37]\n APPLY_ABSTRACTION; [line 37]\n " shape="box"]
25 -> 24 ;
24 [label="24: Exit get_method_div0 \n " color=yellow style=filled]
23 [label="23: Start get_method_div0\nFormals: \nLocals: __temp_return_n$1:class X \n DECLARE_LOCALS(&return,&__temp_return_n$1); [line 36]\n " color=yellow style=filled]
23 -> 25 ;
22 [label="22: Call _fun_X_skip \n _fun_get(0:int ,&__temp_return_n$4:class X *) [line 32]\n n$5=_fun_X_skip(&__temp_return_n$4:class X &) [line 32]\n REMOVE_TEMPS(n$5); [line 32]\n " shape="box"]
22 -> 21 ;
21 [label="21: Return Stmt \n _fun_get(0:int ,&__temp_return_n$1:class X *) [line 33]\n n$2=*&__temp_return_n$1.f:int [line 33]\n *&return:int =(1 / n$2) [line 33]\n REMOVE_TEMPS(n$2); [line 33]\n NULLIFY(&__temp_return_n$1,false); [line 33]\n NULLIFY(&__temp_return_n$4,false); [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="box"]
21 -> 20 ;
20 [label="20: Exit get_field_div0 \n " color=yellow style=filled]
19 [label="19: Start get_field_div0\nFormals: \nLocals: __temp_return_n$1:class X __temp_return_n$4:class X \n DECLARE_LOCALS(&return,&__temp_return_n$1,&__temp_return_n$4); [line 31]\n " color=yellow style=filled]
19 -> 22 ;
18 [label="18: DeclStmt \n _fun_get(0:int ,&SIL_materialize_temp__n$1:class X *) [line 27]\n _fun_X_X(&x:class X *,&SIL_materialize_temp__n$1:class X &) [line 27]\n " shape="box"]
18 -> 17 ;
17 [label="17: Return Stmt \n n$0=*&x.f:int [line 28]\n *&return:int =(1 / n$0) [line 28]\n REMOVE_TEMPS(n$0); [line 28]\n NULLIFY(&SIL_materialize_temp__n$1,false); [line 28]\n NULLIFY(&x,false); [line 28]\n APPLY_ABSTRACTION; [line 28]\n " shape="box"]
17 -> 16 ;
16 [label="16: Exit get_div0 \n " color=yellow style=filled]
15 [label="15: Start get_div0\nFormals: \nLocals: x:class X SIL_materialize_temp__n$1:class X \n DECLARE_LOCALS(&return,&x,&SIL_materialize_temp__n$1); [line 26]\n " color=yellow style=filled]
15 -> 18 ;
14 [label="14: DeclStmt \n _fun_X_X(&x:class X *) [line 21]\n " shape="box"]
14 -> 13 ;
13 [label="13: BinaryOperatorStmt: Assign \n n$1=*&a:int [line 22]\n *&x.f:int =n$1 [line 22]\n REMOVE_TEMPS(n$1); [line 22]\n NULLIFY(&a,false); [line 22]\n " shape="box"]
13 -> 12 ;
12 [label="12: Return Stmt \n n$0=*&x.f:int [line 23]\n *&return:int =(1 / n$0) [line 23]\n REMOVE_TEMPS(n$0); [line 23]\n NULLIFY(&SIL_materialize_temp__n$1,false); [line 23]\n NULLIFY(&x,false); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"]
12 [label="12: Return Stmt \n n$0=*&__return_param:void * [line 23]\n _fun_X_X(n$0:class X *,&x:class X &) [line 23]\n REMOVE_TEMPS(n$0); [line 23]\n NULLIFY(&__return_param,false); [line 23]\n NULLIFY(&x,false); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"]
12 -> 11 ;
11 [label="11: Exit test \n " color=yellow style=filled]
11 [label="11: Exit get \n " color=yellow style=filled]
10 [label="10: Start test\nFormals: \nLocals: x:struct X SIL_materialize_temp__n$1:struct X \n DECLARE_LOCALS(&return,&x,&SIL_materialize_temp__n$1); [line 21]\n " color=yellow style=filled]
10 [label="10: Start get\nFormals: a:int __return_param:class X *\nLocals: x:class X \n DECLARE_LOCALS(&return,&x); [line 20]\n " color=yellow style=filled]
10 -> 13 ;
9 [label="9: DeclStmt \n _fun_X_X(&x:struct X *) [line 16]\n " shape="box"]
10 -> 14 ;
9 [label="9: Return Stmt \n n$0=*&this:class X * [line 15]\n n$1=*n$0.f:int [line 15]\n *&return:int =(1 / n$1) [line 15]\n REMOVE_TEMPS(n$0,n$1); [line 15]\n NULLIFY(&this,false); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"]
9 -> 8 ;
8 [label="8: BinaryOperatorStmt: Assign \n n$1=*&a:int [line 17]\n *&x.f:int =n$1 [line 17]\n REMOVE_TEMPS(n$1); [line 17]\n NULLIFY(&a,false); [line 17]\n " shape="box"]
8 [label="8: Exit X_div \n " color=yellow style=filled]
8 -> 7 ;
7 [label="7: Return Stmt \n n$0=*&__return_param:void * [line 18]\n _fun_X_X(n$0:struct X *,&x:struct X ) [line 18]\n REMOVE_TEMPS(n$0); [line 18]\n NULLIFY(&__return_param,false); [line 18]\n NULLIFY(&x,false); [line 18]\n APPLY_ABSTRACTION; [line 18]\n " shape="box"]
7 [label="7: Start X_div\nFormals: this:class X *\nLocals: \n DECLARE_LOCALS(&return); [line 15]\n " color=yellow style=filled]
7 -> 6 ;
6 [label="6: Exit get \n " color=yellow style=filled]
7 -> 9 ;
6 [label="6: BinaryOperatorStmt: Assign \n n$0=*&this:class X * [line 14]\n *n$0.f:int =1 [line 14]\n REMOVE_TEMPS(n$0); [line 14]\n NULLIFY(&this,false); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"]
5 [label="5: Start get\nFormals: a:int __return_param:struct X *\nLocals: x:struct X \n DECLARE_LOCALS(&return,&x); [line 15]\n " color=yellow style=filled]
6 -> 5 ;
5 [label="5: Exit X_X \n " color=yellow style=filled]
5 -> 9 ;
4 [label="4: Exit X_X \n " color=yellow style=filled]
4 [label="4: Start X_X\nFormals: this:class X *\nLocals: \n DECLARE_LOCALS(&return); [line 14]\n " color=yellow style=filled]
3 [label="3: Start X_X\nFormals: this:class X * :void \nLocals: \n DECLARE_LOCALS(&return); [line 10]\n NULLIFY(&,false); [line 10]\n NULLIFY(&this,false); [line 10]\n " color=yellow style=filled]
4 -> 6 ;
3 [label="3: BinaryOperatorStmt: Assign \n n$0=*&this:class X * [line 13]\n n$1=*&x:class X & [line 13]\n n$2=*n$1.f:int [line 13]\n *n$0.f:int =n$2 [line 13]\n REMOVE_TEMPS(n$0,n$1,n$2); [line 13]\n NULLIFY(&this,false); [line 13]\n NULLIFY(&x,false); [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"]
3 -> 4 ;
3 -> 2 ;
2 [label="2: Exit X_X \n " color=yellow style=filled]
1 [label="1: Start X_X\nFormals: this:class X *\nLocals: \n DECLARE_LOCALS(&return); [line 10]\n NULLIFY(&this,false); [line 10]\n " color=yellow style=filled]
1 [label="1: Start X_X\nFormals: this:class X * x:class X &\nLocals: \n DECLARE_LOCALS(&return); [line 13]\n " color=yellow style=filled]
1 -> 2 ;
1 -> 3 ;
}

@ -0,0 +1,65 @@
/*
* Copyright (c) 2016 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package endtoend.cpp;
import static org.hamcrest.MatcherAssert.assertThat;
import static utils.matchers.ResultContainsExactly.containsExactly;
import com.google.common.collect.ImmutableList;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import java.io.IOException;
import utils.DebuggableTemporaryFolder;
import utils.InferException;
import utils.InferResults;
import utils.InferRunner;
public class ReturnStructTest {
public static final String FILE =
"infer/tests/codetoanalyze/cpp/frontend/types/return_struct.cpp";
private static ImmutableList<String> inferCmd;
public static final String DIVIDE_BY_ZERO = "DIVIDE_BY_ZERO";
@ClassRule
public static DebuggableTemporaryFolder folder =
new DebuggableTemporaryFolder();
@BeforeClass
public static void runInfer() throws InterruptedException, IOException {
inferCmd = InferRunner.createCPPInferCommand(folder, FILE);
}
@Test
public void whenInferRunsOnDiv0MethodsErrorIsFound()
throws InterruptedException, IOException, InferException {
InferResults inferResults = InferRunner.runInferCPP(inferCmd);
String[] procedures = {
"get_div0",
"get_field_div0",
"get_method_div0",
};
assertThat(
"Results should contain the expected divide by zero",
inferResults,
containsExactly(
DIVIDE_BY_ZERO,
FILE,
procedures
)
);
}
}
Loading…
Cancel
Save