From 44a6ec4f10f0ead56ed1af83bd0857ae3e137d61 Mon Sep 17 00:00:00 2001 From: Andrzej Kotulski Date: Fri, 29 Jan 2016 05:41:48 -0800 Subject: [PATCH] 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 --- infer/src/clang/cTrans.ml | 95 +++++++++----- .../frontend/methods/return_struct.cpp.dot | 2 +- .../cpp/frontend/types/return_struct.cpp | 29 ++++- .../cpp/frontend/types/return_struct.cpp.dot | 118 +++++++++++++++--- .../tests/endtoend/cpp/ReturnStructTest.java | 65 ++++++++++ 5 files changed, 259 insertions(+), 50 deletions(-) create mode 100644 infer/tests/endtoend/cpp/ReturnStructTest.java diff --git a/infer/src/clang/cTrans.ml b/infer/src/clang/cTrans.ml index de13c387c..9adbc70d2 100644 --- a/infer/src/clang/cTrans.ml +++ b/infer/src/clang/cTrans.ml @@ -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 = diff --git a/infer/tests/codetoanalyze/cpp/frontend/methods/return_struct.cpp.dot b/infer/tests/codetoanalyze/cpp/frontend/methods/return_struct.cpp.dot index fb45ab368..2657b2081 100644 --- a/infer/tests/codetoanalyze/cpp/frontend/methods/return_struct.cpp.dot +++ b/infer/tests/codetoanalyze/cpp/frontend/methods/return_struct.cpp.dot @@ -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 ; diff --git a/infer/tests/codetoanalyze/cpp/frontend/types/return_struct.cpp b/infer/tests/codetoanalyze/cpp/frontend/types/return_struct.cpp index e5dcae04a..05b03edaa 100644 --- a/infer/tests/codetoanalyze/cpp/frontend/types/return_struct.cpp +++ b/infer/tests/codetoanalyze/cpp/frontend/types/return_struct.cpp @@ -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(); +} diff --git a/infer/tests/codetoanalyze/cpp/frontend/types/return_struct.cpp.dot b/infer/tests/codetoanalyze/cpp/frontend/types/return_struct.cpp.dot index 0ad911ecf..fef73b1a6 100644 --- a/infer/tests/codetoanalyze/cpp/frontend/types/return_struct.cpp.dot +++ b/infer/tests/codetoanalyze/cpp/frontend/types/return_struct.cpp.dot @@ -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 ; } diff --git a/infer/tests/endtoend/cpp/ReturnStructTest.java b/infer/tests/endtoend/cpp/ReturnStructTest.java new file mode 100644 index 000000000..e7ef84e1a --- /dev/null +++ b/infer/tests/endtoend/cpp/ReturnStructTest.java @@ -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 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 + ) + ); + } +}