From 158bfa7a7ad140286be058a916d473d7d7c9b20a Mon Sep 17 00:00:00 2001 From: Radu Grigore Date: Tue, 22 Jun 2021 03:20:12 -0700 Subject: [PATCH] [erl-frontend] Translation of some expressions Summary: Generates Sil instructions for Erlang some expressions: - empty list: e.g. [] - cons: e.g. [1,2] - atom literals: e.g. ok - string literals: e.g. "foo" - calls to particular (no higher-order), unqualified functions Differential Revision: D28996467 fbshipit-source-id: fc7f7e3e9 --- infer/src/IR/BuiltinDecl.ml | 4 + infer/src/IR/BuiltinDecl.mli | 6 +- infer/src/IR/Procdesc.ml | 3 + infer/src/IR/Procdesc.mli | 1 + infer/src/erlang/ErlangAst.ml | 46 +++++++----- infer/src/erlang/ErlangTranslator.ml | 107 ++++++++++++++++++++++++++- 6 files changed, 144 insertions(+), 23 deletions(-) diff --git a/infer/src/IR/BuiltinDecl.ml b/infer/src/IR/BuiltinDecl.ml index a1525459b..8fe2c0b8e 100644 --- a/infer/src/IR/BuiltinDecl.ml +++ b/infer/src/IR/BuiltinDecl.ml @@ -76,6 +76,10 @@ let __delete_array = create_procname "__delete_array" let __delete_locked_attribute = create_procname "__delete_locked_attribute" +let __erlang_make_cons = create_procname "__erlang_make_cons" + +let __erlang_make_nil = create_procname "__erlang_make_nil" + let __erlang_pattern_fail = create_procname "__erlang_pattern_fail" let __exit = create_procname "_exit" diff --git a/infer/src/IR/BuiltinDecl.mli b/infer/src/IR/BuiltinDecl.mli index 9d9cace63..496dab5fd 100644 --- a/infer/src/IR/BuiltinDecl.mli +++ b/infer/src/IR/BuiltinDecl.mli @@ -13,7 +13,11 @@ include BUILTINS.S with type t = Procname.t val is_declared : Procname.t -> bool -val __erlang_pattern_fail : t +val __erlang_make_cons : Procname.t + +val __erlang_make_nil : Procname.t + +val __erlang_pattern_fail : Procname.t (** all patterns in a function failed to match *) val __infer_initializer_list : Procname.t diff --git a/infer/src/IR/Procdesc.ml b/infer/src/IR/Procdesc.ml index 1d794ae64..84c244935 100644 --- a/infer/src/IR/Procdesc.ml +++ b/infer/src/IR/Procdesc.ml @@ -80,6 +80,7 @@ module Node = struct | Destruction of destruction_kind | Erlang | ErlangCaseClause + | ErlangExpression | ExceptionHandler | ExceptionsSink | ExprWithCleanups @@ -334,6 +335,8 @@ module Node = struct F.pp_print_string fmt "Erlang (generic)" | ErlangCaseClause -> F.pp_print_string fmt "ErlangCaseClause" + | ErlangExpression -> + F.pp_print_string fmt "ErlangExpression" | ExceptionHandler -> F.pp_print_string fmt "exception handler" | ExceptionsSink -> diff --git a/infer/src/IR/Procdesc.mli b/infer/src/IR/Procdesc.mli index 9484152b8..0575165bb 100644 --- a/infer/src/IR/Procdesc.mli +++ b/infer/src/IR/Procdesc.mli @@ -61,6 +61,7 @@ module Node : sig | Destruction of destruction_kind | Erlang | ErlangCaseClause + | ErlangExpression | ExceptionHandler | ExceptionsSink | ExprWithCleanups diff --git a/infer/src/erlang/ErlangAst.ml b/infer/src/erlang/ErlangAst.ml index a1871cbe2..c374cd545 100644 --- a/infer/src/erlang/ErlangAst.ml +++ b/infer/src/erlang/ErlangAst.ml @@ -13,15 +13,17 @@ open! IStd (** {2 Basics} *) type module_reference = ModuleName of string | ModuleMissing | ModuleVariable of string +[@@deriving sexp_of] -type function_reference = FunctionName of string | FunctionVariable of string +type function_reference = FunctionName of string | FunctionVariable of string [@@deriving sexp_of] (* NOTE: Arity could be an expression but we don't handle that case, yet. *) type function_ = {module_: module_reference; function_: function_reference; arity: int} +[@@deriving sexp_of] -type line = int +type line = int [@@deriving sexp_of] -type record_name = string +type record_name = string [@@deriving sexp_of] type binary_operator = | Add @@ -51,22 +53,24 @@ type binary_operator = | Send | Sub | Xor +[@@deriving sexp_of] -type unary_operator = UBNot | UMinus | UNot +type unary_operator = UBNot | UMinus | UNot [@@deriving sexp_of] -type association_kind = Arrow | Exact +type association_kind = Arrow | Exact [@@deriving sexp_of] -type exception_ = Atom of string | Pattern of string +type exception_ = Atom of string | Pattern of string [@@deriving sexp_of] -type type_specifier = (* TODO *) unit +type type_specifier = (* TODO *) unit [@@deriving sexp_of] (** {2 S8.2: Atomic literals} *) type literal = Atom of string | Char of string | Float of float | Int of string | String of string +[@@deriving sexp_of] (** {2 S8.4: Expressions} *) -type body = expression list +type body = expression list [@@deriving sexp_of] and simple_expression = | BinaryOperator of expression * binary_operator * expression @@ -93,37 +97,42 @@ and simple_expression = | Tuple of expression list | UnaryOperator of unary_operator * expression | Variable of string +[@@deriving sexp_of] -and expression = {line: line; simple_expression: simple_expression} +and expression = {line: line; simple_expression: simple_expression} [@@deriving sexp_of] and qualifier = | BitsGenerator of {pattern: pattern; expression: expression} | Filter of expression | Generator of {pattern: pattern; expression: expression} +[@@deriving sexp_of] -and timeout = {time: expression; handler: body} +and timeout = {time: expression; handler: body} [@@deriving sexp_of] and bin_element = {expression: expression; size: expression option; types: type_specifier list option} +[@@deriving sexp_of] (* A [None] field stands for _, which means "all other fields". *) -and record_update = {field: string option; expression: expression} +and record_update = {field: string option; expression: expression} [@@deriving sexp_of] -and association = {kind: association_kind; key: expression; value: expression} +and association = {kind: association_kind; key: expression; value: expression} [@@deriving sexp_of] -and pattern = expression +and pattern = expression [@@deriving sexp_of] -and guard_test = expression +and guard_test = expression [@@deriving sexp_of] (** {2 S8.5 Clauses} *) and 'pat clause = {line: line; patterns: 'pat list; guards: guard_test list list; body: body} +[@@deriving sexp_of] -and case_clause = pattern clause +and case_clause = pattern clause [@@deriving sexp_of] -and catch_clause = catch_pattern clause +and catch_clause = catch_pattern clause [@@deriving sexp_of] and catch_pattern = {exception_: exception_; pattern: pattern; variable: string} +[@@deriving sexp_of] (** {2 S8.1: Module declarations and forms} *) @@ -134,7 +143,8 @@ type simple_form = | Module of string | File of {path: string} | Function of {function_: function_; clauses: case_clause list} +[@@deriving sexp_of] -type form = {line: line; simple_form: simple_form} +type form = {line: line; simple_form: simple_form} [@@deriving sexp_of] -type module_ = form list +type module_ = form list [@@deriving sexp_of] diff --git a/infer/src/erlang/ErlangTranslator.ml b/infer/src/erlang/ErlangTranslator.ml index 54b34cffd..b63abe5f8 100644 --- a/infer/src/erlang/ErlangTranslator.ml +++ b/infer/src/erlang/ErlangTranslator.ml @@ -246,14 +246,113 @@ let rec translate_pattern env (value : Ident.t) {Ast.line; simple_expression} = let exit_success = Node.make_stmt env [store] in let exit_failure = Node.make_nop env in {start= exit_success; exit_success; exit_failure} - | _ -> + | e -> (* TODO: Cover all cases. *) + L.debug Capture Verbose "@[todo ErlangTranslate.translate_pattern %s@." + (Sexp.to_string (Ast.sexp_of_simple_expression e)) ; make_block_failure env -let translate_body env _body = - (* TODO: This is a dummy implementation. *) - make_block_success env +let rec instructions_of_expression env {Ast.line; simple_expression} : Sil.instr list = + let env = update_location line env in + let any = typ_of_name Any in + let procdesc = Option.value_exn env.procdesc in + let procname = Procdesc.get_proc_name procdesc in + let ret_var = + match env.result with Some (Var ret_var) -> ret_var | _ -> Ident.create_fresh Ident.knormal + in + let instructions = + match simple_expression with + | Call + { module_= None + ; function_= {Ast.line= _; simple_expression= Literal (Atom function_name)} + ; args } -> + let arity = List.length args in + let callee_procname = + let module_name = + let uf_name = {UnqualifiedFunction.name= function_name; arity} in + match UnqualifiedFunction.Map.find env.imports uf_name with + | Some module_name -> + module_name + | None -> + env.current_module + in + Procname.make_erlang ~module_name ~function_name ~arity + in + let args_with_ids = List.map ~f:(fun a -> (a, Ident.create_fresh Ident.knormal)) args in + let args_instructions = + let f (one_arg_expression, one_arg_ret_var) = + let result = Some (Exp.Var one_arg_ret_var) in + instructions_of_expression {env with result} one_arg_expression + in + List.concat_map ~f args_with_ids + in + let fun_exp = Exp.Const (Cfun callee_procname) in + let args_ids_and_types = + List.map ~f:(function _, id -> (Exp.Var id, any)) args_with_ids + in + args_instructions + @ [Sil.Call ((ret_var, any), fun_exp, args_ids_and_types, env.location, CallFlags.default)] + | Cons {head; tail} -> + let head_var = Ident.create_fresh Ident.knormal in + let tail_var = Ident.create_fresh Ident.knormal in + let head_instructions = + instructions_of_expression {env with result= Some (Var head_var)} head + in + let tail_instructions = + instructions_of_expression {env with result= Some (Var tail_var)} tail + in + let fun_exp = Exp.Const (Cfun BuiltinDecl.__erlang_make_cons) in + let args : (Exp.t * Typ.t) list = [(Var head_var, any); (Var tail_var, any)] in + head_instructions @ tail_instructions + @ [Sil.Call ((ret_var, any), fun_exp, args, env.location, CallFlags.default)] + | Literal (Atom atom) -> + let hash = + (* With this hack, an atom may accidentaly be considered equal to an unrelated integer. + The [lsl] below makes this less likely. Proper fix is TODO (T93513105). *) + String.hash atom lsl 16 + in + let e = Exp.Const (Cint (IntLit.of_int hash)) in + [Sil.Load {id= ret_var; e; root_typ= any; typ= any; loc= env.location}] + | Literal (String s) -> + let e = Exp.Const (Cstr s) in + [Sil.Load {id= ret_var; e; root_typ= any; typ= any; loc= env.location}] + | Nil -> + let fun_exp = Exp.Const (Cfun BuiltinDecl.__erlang_make_nil) in + [Sil.Call ((ret_var, any), fun_exp, [], env.location, CallFlags.default)] + | Variable vname -> + let e = Exp.Lvar (Pvar.mk (Mangled.from_string vname) procname) in + [Sil.Load {id= ret_var; e; root_typ= any; typ= any; loc= env.location}] + | todo -> + L.debug Capture Verbose "@[todo ErlangTranslate.instructions_of_expression %s@." + (Sexp.to_string (Ast.sexp_of_simple_expression todo)) ; + [] + in + match env.result with + | None | Some (Var _) -> + instructions + | Some result -> + instructions + @ [Sil.Store {e1= result; root_typ= any; typ= any; e2= Var ret_var; loc= env.location}] + + +let translate_expression env expression = + let instructions = instructions_of_expression env expression in + let exit_success = Node.make_stmt env ~kind:ErlangExpression instructions in + let exit_failure = Node.make_nop env in + {start= exit_success; exit_success; exit_failure} + + +let translate_body env body : block = + let blocks = + let f rev_blocks one_expression = + let env = {env with result= None} in + translate_expression env one_expression :: rev_blocks + in + let f_last rev_blocks one_expression = translate_expression env one_expression :: rev_blocks in + List.rev (IList.fold_last body ~init:[] ~f ~f_last) + in + all_blocks env blocks (** Assumes that the values on which patterns should be matched have been loaded into the