From 9f9bc53361a10dbc76b040d58ec3c1f590ae4bf0 Mon Sep 17 00:00:00 2001 From: Andrzej Kotulski Date: Tue, 30 Jun 2015 13:14:35 -0100 Subject: [PATCH] [Frontend][C++] Add support for default params Summary: @public Add support for default function arguments. As a side change - always create cmethod_signature for a function Test Plan: 1. Call function with default parameter and confirm that it gets parsed and reports null dereference (B5 but still). It didn't before. 2. Created a test case --- infer/src/clang/cMethod_decl.ml | 9 ++-- infer/src/clang/cMethod_signature.ml | 4 +- infer/src/clang/cMethod_signature.mli | 4 +- infer/src/clang/cMethod_trans.ml | 2 +- infer/src/clang/cTrans.ml | 23 ++++++++- infer/src/clang/cTrans_models.ml | 8 +-- .../cpp/frontend/types/functions.cpp | 18 +++++++ .../cpp/frontend/types/functions.dot | 51 +++++++++++++++++++ infer/tests/frontend/cpp/FunctionsTest.java | 47 +++++++++++++++++ 9 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 infer/tests/codetoanalyze/cpp/frontend/types/functions.cpp create mode 100644 infer/tests/codetoanalyze/cpp/frontend/types/functions.dot create mode 100644 infer/tests/frontend/cpp/FunctionsTest.java diff --git a/infer/src/clang/cMethod_decl.ml b/infer/src/clang/cMethod_decl.ml index 794f6e50d..ebb330b85 100644 --- a/infer/src/clang/cMethod_decl.ml +++ b/infer/src/clang/cMethod_decl.ml @@ -50,7 +50,7 @@ struct | ParmVarDecl(decl_info, name, qtype, var_decl_info) -> Printing.log_out "Adding param '%s' " name; Printing.log_out "with pointer %s@." decl_info.Clang_ast_t.di_pointer; - (name, CTypes.get_type qtype) + (name, CTypes.get_type qtype, var_decl_info.vdi_init_expr) | _ -> assert false in match function_method_decl_info with | Func_decl_info (function_decl_info, _) -> @@ -58,7 +58,7 @@ struct | Meth_decl_info (method_decl_info, class_name) -> let pars = list_map par_to_ms_par method_decl_info.Clang_ast_t.omdi_parameters in if (is_instance_method function_method_decl_info false false) then - ("self", class_name):: pars + ("self", class_name, None):: pars else pars let get_return_type function_method_decl_info = @@ -154,6 +154,7 @@ struct let is_objc_method = is_anonym_block in let curr_class = if is_anonym_block then curr_class else CContext.ContextNoCls in let attributes = CMethod_signature.ms_get_attributes ms in + CMethod_signature.add ms; add_method tenv cg cfg curr_class procname namespace [body] is_objc_method is_instance captured_vars is_anonym_block fdecl_info.Clang_ast_t.fdi_parameters attributes | None, ms -> @@ -166,6 +167,7 @@ struct let method_decl = Meth_decl_info (method_decl_info, class_name) in let ms = build_method_signature decl_info procname method_decl false false in Printing.log_out " ....Processing implementation for method '%s'\n" (Procname.to_string procname); + CMethod_signature.add ms; (match method_body_to_translate decl_info ms method_decl_info.Clang_ast_t.omdi_body with | Some body -> let is_instance = CMethod_signature.ms_is_instance ms in @@ -173,8 +175,7 @@ struct CMethod_trans.create_local_procdesc cfg tenv ms [body] [] is_instance; add_method tenv cg cfg curr_class procname namespace [body] true is_instance [] false method_decl_info.Clang_ast_t.omdi_parameters attributes - | None -> - CMethod_signature.add ms) + | None -> ()) let rec process_one_method_decl tenv cg cfg curr_class namespace dec = match dec with diff --git a/infer/src/clang/cMethod_signature.ml b/infer/src/clang/cMethod_signature.ml index 2ceb99d1c..49680e734 100644 --- a/infer/src/clang/cMethod_signature.ml +++ b/infer/src/clang/cMethod_signature.ml @@ -8,7 +8,7 @@ type method_signature = { _name : Procname.t; - _args : (string * string) list; (* (name, type) *) + _args : (string * string * Clang_ast_t.stmt option) list; (* (name, type, default value) *) _ret_type : string; _attributes : Clang_ast_t.attribute list; _loc : Clang_ast_t.source_range; @@ -52,7 +52,7 @@ let replace_name_ms ms name = let ms_to_string ms = "Method "^(Procname.to_string ms._name)^" "^ - (Utils.list_to_string (fun (s1, s2) -> s1^", "^s2) ms._args)^"->"^ms._ret_type^" "^ + (Utils.list_to_string (fun (s1, s2, _) -> s1^", "^s2) ms._args)^"->"^ms._ret_type^" "^ Clang_ast_j.string_of_source_range ms._loc let find ms = diff --git a/infer/src/clang/cMethod_signature.mli b/infer/src/clang/cMethod_signature.mli index d10ee122c..3385fbdd3 100644 --- a/infer/src/clang/cMethod_signature.mli +++ b/infer/src/clang/cMethod_signature.mli @@ -16,7 +16,7 @@ val reset_map : unit -> unit val ms_get_name : method_signature -> Procname.t -val ms_get_args : method_signature -> (string * string) list +val ms_get_args : method_signature -> (string * string * Clang_ast_t.stmt option) list val ms_get_ret_type : method_signature -> string @@ -26,7 +26,7 @@ val ms_get_loc : method_signature -> Clang_ast_t.source_range val ms_is_instance : method_signature -> bool -val make_ms : Procname.t -> (string * string) list -> string -> Clang_ast_t.attribute list -> +val make_ms : Procname.t -> (string * string * Clang_ast_t.stmt option) list -> string -> Clang_ast_t.attribute list -> Clang_ast_t.source_range -> bool -> method_signature val replace_name_ms : method_signature -> Procname.t -> method_signature diff --git a/infer/src/clang/cMethod_trans.ml b/infer/src/clang/cMethod_trans.ml index 6593cd52e..81703eec9 100644 --- a/infer/src/clang/cMethod_trans.ml +++ b/infer/src/clang/cMethod_trans.ml @@ -87,7 +87,7 @@ let get_formal_parameters tenv ms = let rec defined_parameters pl = match pl with | [] -> [] - | (name, raw_type):: pl' -> + | (name, raw_type, _):: pl' -> let qt = if (name = CFrontend_config.self && CMethod_signature.ms_is_instance ms) then (Ast_expressions.create_pointer_type raw_type) diff --git a/infer/src/clang/cTrans.ml b/infer/src/clang/cTrans.ml index 3ab68795d..f7682bb7e 100644 --- a/infer/src/clang/cTrans.ml +++ b/infer/src/clang/cTrans.ml @@ -539,7 +539,28 @@ struct (* verbatim from a call to a different function, and they might be side-effecting *) (Procname.to_string pn) <> CFrontend_config.builtin_object_size | _ -> true in - let params_stmt = if should_translate_args then params_stmt else [] in + + let assign_default_params params_stmt = + match callee_pname_opt with + | None -> params_stmt + | Some callee_pname -> + try + let callee_ms = CMethod_signature.find callee_pname in + let args = CMethod_signature.ms_get_args callee_ms in + let params_args = list_combine params_stmt args in + let replace_default_arg param = + match param with + | CXXDefaultArgExpr(_, _, _), (_, _, Some default_instr) -> default_instr + | instr, _ -> instr in + list_map replace_default_arg params_args + with + | Invalid_argument _ -> + (* list_combine failed because of different list lengths *) + Printing.log_err "Param count doesn't match %s\n" (Procname.to_string callee_pname); + params_stmt + | Not_found -> params_stmt in + + let params_stmt = if should_translate_args then assign_default_params params_stmt else [] in let res_trans_par = let l = list_map (fun i -> exec_with_self_exception instruction trans_state_param i) params_stmt in let rt = collect_res_trans (res_trans_callee :: l) in diff --git a/infer/src/clang/cTrans_models.ml b/infer/src/clang/cTrans_models.ml index fc12d0690..8b8a0c50c 100644 --- a/infer/src/clang/cTrans_models.ml +++ b/infer/src/clang/cTrans_models.ml @@ -130,7 +130,7 @@ let get_predefined_ms_method condition class_name method_name mk_procname let get_predefined_ms_stringWithUTF8String class_name method_name mk_procname = let condition = class_name = nsstring_cl && method_name = string_with_utf8_m in - get_predefined_ms_method condition class_name method_name mk_procname [("x", "char *")] + get_predefined_ms_method condition class_name method_name mk_procname [("x", "char *", None)] id_cl [] None let get_predefined_ms_retain_release class_name method_name mk_procname = @@ -139,17 +139,17 @@ let get_predefined_ms_retain_release class_name method_name mk_procname = if is_retain_method method_name || is_autorelease_method method_name then id_cl else void in get_predefined_ms_method condition nsobject_cl method_name mk_procname - [(self, class_name)] return_type [] (get_builtinname method_name) + [(self, class_name, None)] return_type [] (get_builtinname method_name) let get_predefined_ms_autoreleasepool_init class_name method_name mk_procname = let condition = (method_name = init) && (class_name = nsautorelease_pool_cl) in get_predefined_ms_method condition class_name method_name mk_procname - [(self, class_name)] void [] None + [(self, class_name, None)] void [] None let get_predefined_ms_nsautoreleasepool_release class_name method_name mk_procname = let condition = (method_name = release || method_name = drain) && (class_name = nsautorelease_pool_cl) in - get_predefined_ms_method condition class_name method_name mk_procname [(self, class_name)] + get_predefined_ms_method condition class_name method_name mk_procname [(self, class_name, None)] void [] (Some SymExec.ModelBuiltins.__objc_release_autorelease_pool) let get_predefined_model_method_signature class_name method_name mk_procname = diff --git a/infer/tests/codetoanalyze/cpp/frontend/types/functions.cpp b/infer/tests/codetoanalyze/cpp/frontend/types/functions.cpp new file mode 100644 index 000000000..ab8968d39 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/frontend/types/functions.cpp @@ -0,0 +1,18 @@ +int fun_default(int a = 3, int b = 5) { + return a + b; +} + +int fun_default_decl(int a, int b = 5); +// note that b is default param, but function was declared earlier +int fun_default_decl(int a, int b) { + return a + b; +} + +void test() { + fun_default(1, 2); + fun_default(1); + fun_default(); + + fun_default_decl(6); + fun_default_decl(6,6); +} diff --git a/infer/tests/codetoanalyze/cpp/frontend/types/functions.dot b/infer/tests/codetoanalyze/cpp/frontend/types/functions.dot new file mode 100644 index 000000000..5b7a119e8 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/frontend/types/functions.dot @@ -0,0 +1,51 @@ +digraph iCFG { +13 [label="13: Call _fun_fun_default \n n$8=_fun_fun_default(1:int ,2:int ) [line 12]\n REMOVE_TEMPS(n$8); [line 12]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Call _fun_fun_default \n n$7=_fun_fun_default(1:int ,5:int ) [line 13]\n REMOVE_TEMPS(n$7); [line 13]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: Call _fun_fun_default \n n$6=_fun_fun_default(3:int ,5:int ) [line 14]\n REMOVE_TEMPS(n$6); [line 14]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: Call _fun_fun_default_decl \n n$5=_fun_fun_default_decl(6:int ,5:int ) [line 16]\n REMOVE_TEMPS(n$5); [line 16]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: Call _fun_fun_default_decl \n n$4=_fun_fun_default_decl(6:int ,6:int ) [line 17]\n REMOVE_TEMPS(n$4); [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: Exit test \n " color=yellow style=filled] + + +7 [label="7: Start test\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 11]\n " color=yellow style=filled] + + + 7 -> 13 ; +6 [label="6: Return Stmt \n n$2=*&a:int [line 8]\n n$3=*&b:int [line 8]\n *&return:int =(n$2 + n$3) [line 8]\n REMOVE_TEMPS(n$2,n$3); [line 8]\n NULLIFY(&a,false); [line 8]\n NULLIFY(&b,false); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit fun_default_decl \n " color=yellow style=filled] + + +4 [label="4: Start fun_default_decl\nFormals: a:int b:int \nLocals: \n DECLARE_LOCALS(&return); [line 7]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Return Stmt \n n$0=*&a:int [line 2]\n n$1=*&b:int [line 2]\n *&return:int =(n$0 + n$1) [line 2]\n REMOVE_TEMPS(n$0,n$1); [line 2]\n NULLIFY(&a,false); [line 2]\n NULLIFY(&b,false); [line 2]\n APPLY_ABSTRACTION; [line 2]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit fun_default \n " color=yellow style=filled] + + +1 [label="1: Start fun_default\nFormals: a:int b:int \nLocals: \n DECLARE_LOCALS(&return); [line 1]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/frontend/cpp/FunctionsTest.java b/infer/tests/frontend/cpp/FunctionsTest.java new file mode 100644 index 000000000..d6bb203c4 --- /dev/null +++ b/infer/tests/frontend/cpp/FunctionsTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.cpp; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class FunctionsTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + @Test + public void whenCaptureRunCommaThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String literal_src = + "infer/tests/codetoanalyze/cpp/frontend/types/functions.cpp"; + + String literal_dotty = + "infer/tests/codetoanalyze/cpp/frontend/types/functions.dot"; + + ImmutableList inferCmd = + InferRunner.createCPPInferCommandFrontend( + folder, + literal_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + literal_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(literal_dotty)); + } +}