[erl-frontend] Erlang AST, and parser for JSON AST.

Summary:
The wrapper in `infer/lib/erlang/erlang.sh` dumps Erlang AST forms [1]
in a JSON format. The current commit parses that JSON to obtain an
internal representation (ErlangAst). The main parts of the commit are:
 - data structures for Erlang AST
 - parser (Erlang abstract forms in JSON format -> Eralng AST)
 - Rebar3.ml now drives the parser

[1] https://erlang.org/doc/apps/erts/absform.html

Reviewed By: mmarescotti, jvillard

Differential Revision: D28096896

fbshipit-source-id: b21263817
master
Radu Grigore 4 years ago committed by Facebook GitHub Bot
parent 1c87f61728
commit 1bd741b070

@ -278,6 +278,14 @@ CLANG OPTIONS
analyze an iOS app. xcpretty just needs to be in the path, infer analyze an iOS app. xcpretty just needs to be in the path, infer
command is still just `infer -- <xcodebuild command>`. command is still just `infer -- <xcodebuild command>`.
(Conversely: --no-xcpretty) (Conversely: --no-xcpretty)
ERLANG OPTIONS
--erlang-ast-dir dir
Also load AST from all .json files in the given path. These .json
files usually come from a previous run with --debug.
--erlang-skip-rebar3
Activates: Skip running rebar, to save time. It is useful together
with --erlang-ast-dir. (Conversely: --no-erlang-skip-rebar3)
JAVA OPTIONS JAVA OPTIONS
--bootclasspath string --bootclasspath string
Specify the Java bootclasspath Specify the Java bootclasspath

@ -613,6 +613,16 @@ OPTIONS
Activates: Enable eradicate and disable all other checkers Activates: Enable eradicate and disable all other checkers
(Conversely: --no-eradicate-only) See also infer-analyze(1). (Conversely: --no-eradicate-only) See also infer-analyze(1).
--erlang-ast-dir dir
Also load AST from all .json files in the given path. These .json
files usually come from a previous run with --debug.
See also infer-capture(1).
--erlang-skip-rebar3
Activates: Skip running rebar, to save time. It is useful together
with --erlang-ast-dir. (Conversely: --no-erlang-skip-rebar3)
See also infer-capture(1).
--external-java-packages +prefix --external-java-packages +prefix
Specify a list of Java package prefixes for external Java Specify a list of Java package prefixes for external Java
packages. If set, the analysis will not report non-actionable packages. If set, the analysis will not report non-actionable
@ -1637,6 +1647,9 @@ INTERNAL OPTIONS
Activates: Print initial and final typestates (Conversely: Activates: Print initial and final typestates (Conversely:
--no-eradicate-verbose) --no-eradicate-verbose)
--erlang-ast-dir-reset
Cancel the effect of --erlang-ast-dir.
--export-changed-functions --export-changed-functions
Activates: Make infer output changed functions, similar to Activates: Make infer output changed functions, similar to
test-determinator. It is used together with the --modified-lines. test-determinator. It is used together with the --modified-lines.

@ -613,6 +613,16 @@ OPTIONS
Activates: Enable eradicate and disable all other checkers Activates: Enable eradicate and disable all other checkers
(Conversely: --no-eradicate-only) See also infer-analyze(1). (Conversely: --no-eradicate-only) See also infer-analyze(1).
--erlang-ast-dir dir
Also load AST from all .json files in the given path. These .json
files usually come from a previous run with --debug.
See also infer-capture(1).
--erlang-skip-rebar3
Activates: Skip running rebar, to save time. It is useful together
with --erlang-ast-dir. (Conversely: --no-erlang-skip-rebar3)
See also infer-capture(1).
--external-java-packages +prefix --external-java-packages +prefix
Specify a list of Java package prefixes for external Java Specify a list of Java package prefixes for external Java
packages. If set, the analysis will not report non-actionable packages. If set, the analysis will not report non-actionable

@ -139,6 +139,8 @@ let manual_clang = "CLANG OPTIONS"
let manual_clang_linters = "CLANG LINTERS OPTIONS" let manual_clang_linters = "CLANG LINTERS OPTIONS"
let manual_erlang = "ERLANG OPTIONS"
let manual_explore_bugs = "EXPLORE BUGS" let manual_explore_bugs = "EXPLORE BUGS"
let manual_debug_procedures = "DEBUG PROCEDURES" let manual_debug_procedures = "DEBUG PROCEDURES"
@ -1384,6 +1386,20 @@ and eradicate_return_over_annotated =
and eradicate_verbose = CLOpt.mk_bool ~long:"eradicate-verbose" "Print initial and final typestates" and eradicate_verbose = CLOpt.mk_bool ~long:"eradicate-verbose" "Print initial and final typestates"
and erlang_ast_dir =
CLOpt.mk_path_opt ~long:"erlang-ast-dir"
~in_help:InferCommand.[(Capture, manual_erlang)]
~meta:"dir"
"Also load AST from all .json files in the given path. These .json files usually come from a \
previous run with $(b,--debug)."
and erlang_skip_rebar3 =
CLOpt.mk_bool ~long:"erlang-skip-rebar3"
~in_help:InferCommand.[(Capture, manual_erlang)]
"Skip running rebar, to save time. It is useful together with $(b,--erlang-ast-dir)."
and export_changed_functions = and export_changed_functions =
CLOpt.mk_bool ~deprecated:["test-determinator-clang"] ~long:"export-changed-functions" CLOpt.mk_bool ~deprecated:["test-determinator-clang"] ~long:"export-changed-functions"
~default:false ~default:false
@ -3012,6 +3028,10 @@ and eradicate_return_over_annotated = !eradicate_return_over_annotated
and eradicate_verbose = !eradicate_verbose and eradicate_verbose = !eradicate_verbose
and erlang_ast_dir = !erlang_ast_dir
and erlang_skip_rebar3 = !erlang_skip_rebar3
and external_java_packages = !external_java_packages and external_java_packages = !external_java_packages
and fail_on_bug = !fail_on_bug and fail_on_bug = !fail_on_bug

@ -294,6 +294,10 @@ val eradicate_return_over_annotated : bool
val eradicate_verbose : bool val eradicate_verbose : bool
val erlang_ast_dir : string option
val erlang_skip_rebar3 : bool
val fail_on_bug : bool val fail_on_bug : bool
val fcp_apple_clang : string option val fcp_apple_clang : string option

@ -210,6 +210,10 @@ let read_json_file path =
try Ok (Yojson.Basic.from_file path) with Sys_error msg | Yojson.Json_error msg -> Error msg try Ok (Yojson.Basic.from_file path) with Sys_error msg | Yojson.Json_error msg -> Error msg
let read_safe_json_file path =
try Ok (Yojson.Safe.from_file path) with Sys_error msg | Yojson.Json_error msg -> Error msg
let do_finally_swallow_timeout ~f ~finally = let do_finally_swallow_timeout ~f ~finally =
let res = let res =
try f () try f ()

@ -52,16 +52,18 @@ val close_outf : outfile -> unit
(** close an outfile *) (** close an outfile *)
val directory_fold : ('a -> string -> 'a) -> 'a -> string -> 'a val directory_fold : ('a -> string -> 'a) -> 'a -> string -> 'a
(** Functional fold function over all the file of a directory *) (** Functional fold function over all the files of a directory *)
val directory_iter : (string -> unit) -> string -> unit val directory_iter : (string -> unit) -> string -> unit
(** Functional iter function over all the file of a directory *) (** Functional iter function over all the files of a directory *)
val directory_is_empty : string -> bool val directory_is_empty : string -> bool
(** Returns true if a given directory is empty. The directory is assumed to exist. *) (** Returns true if a given directory is empty. The directory is assumed to exist. *)
val read_json_file : string -> (Yojson.Basic.t, string) Result.t val read_json_file : string -> (Yojson.Basic.t, string) Result.t
val read_safe_json_file : string -> (Yojson.Safe.t, string) Result.t
val with_file_in : string -> f:(In_channel.t -> 'a) -> 'a val with_file_in : string -> f:(In_channel.t -> 'a) -> 'a
val with_file_out : string -> f:(Out_channel.t -> 'a) -> 'a val with_file_out : string -> f:(Out_channel.t -> 'a) -> 'a

@ -50,8 +50,8 @@ endif
# Note that we run find under _build directory. Since we copy some # Note that we run find under _build directory. Since we copy some
# sources from subfolders to src/ folder to avoid duplicates we use # sources from subfolders to src/ folder to avoid duplicates we use
# $(DEPTH_ONE) and iteration over main and library folders. # $(DEPTH_ONE) and iteration over main and library folders.
LIBRARY_FOLDERS = . ./IR ./absint ./al ./atd ./backend ./base ./biabduction ./bufferoverrun ./c_stubs ./checkers ./clang ./clang/unit ./concurrency ./cost ./integration ./istd ./java ./labs ./dotnet ./nullsafe ./nullsafe/unit ./pulse ./quandary ./scripts ./test_determinator ./topl ./unit LIBRARY_FOLDERS = . ./IR ./absint ./al ./atd ./backend ./base ./biabduction ./bufferoverrun ./c_stubs ./checkers ./clang ./clang/unit ./concurrency ./cost ./erlang ./integration ./istd ./java ./labs ./dotnet ./nullsafe ./nullsafe/unit ./pulse ./quandary ./scripts ./test_determinator ./topl ./unit
INCLUDE_FOLDERS = -I IR -I absint -I al -I atd -I backend -I base -I biabduction -I bufferoverrun -I c_stubs -I checkers -I clang -I clang/unit -I concurrency -I cost -I integration -I istd -I java -I labs -I dotnet -I nullsafe -I nullsafe/unit -I pulse -I quandary -I scripts -I test_determinator -I topl -I unit INCLUDE_FOLDERS = -I IR -I absint -I al -I atd -I backend -I base -I biabduction -I bufferoverrun -I c_stubs -I checkers -I clang -I clang/unit -I concurrency -I cost -I erlang -I integration -I istd -I java -I labs -I dotnet -I nullsafe -I nullsafe/unit -I pulse -I quandary -I scripts -I test_determinator -I topl -I unit
ml_src_files = $(shell \ ml_src_files = $(shell \
cd $(INFER_BUILD_DIR); \ cd $(INFER_BUILD_DIR); \

@ -0,0 +1,140 @@
(*
* 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.
*)
(** Erlang abstract forms, following https://erlang.org/doc/apps/erts/absform.html *)
open! IStd
(* TODO: validation, including basic type-checking *)
(** {2 Basics} *)
type module_reference = ModuleName of string | ModuleMissing | ModuleVariable of string
type function_reference = FunctionName of string | FunctionVariable of string
(* NOTE: Arity could be an expression but we don't handle that case, yet. *)
type function_ = {module_: module_reference; function_: function_reference; arity: int}
type line = int
type record_name = string
type binary_operator =
| Add
| And
| AndAlso
| AtLeast
| AtMost
| BAnd
| BOr
| Bsl
| Bsr
| BXor
| Equal
| ExactlyEqual
| ExactlyNotEqual
| FDiv
| Greater
| IDiv
| Less
| ListAdd
| ListSub
| Mul
| NotEqual
| Or
| OrElse
| Rem
| Send
| Sub
| Xor
type unary_operator = UBNot | UMinus | UNot
type association_kind = Arrow | Exact
type exception_ = Atom of string | Pattern of string
type type_specifier = (* TODO *) unit
(** {2 S8.2: Atomic literals} *)
type literal = Atom of string | Char of string | Float of float | Int of string | String of string
(** {2 S8.4: Expressions} *)
type body = expression list
and simple_expression =
| BinaryOperator of expression * binary_operator * expression
| BitstringComprehension of {expression: expression; qualifiers: qualifier list}
| BitstringConstructor of bin_element list
| Block of body
| Call of {module_: expression option; function_: expression; args: expression list}
| Case of {expression: expression; cases: case_clause list}
| Catch of expression
| Cons of {head: expression; tail: expression}
| Fun of function_
| If of case_clause list
| Lambda of {name: string option; cases: case_clause list}
| ListComprehension of {expression: expression; qualifiers: qualifier list}
| Literal of literal
| Map of {map: expression option; updates: association list}
| Match of {pattern: pattern; body: (* body is a pattern within patterns *) expression}
| Nil
| Receive of {cases: case_clause list; timeout: timeout option}
| RecordAccess of {record: expression; name: record_name; field: string}
| RecordIndex of {name: record_name; field: string} (* factor from above? *)
| RecordUpdate of {record: expression option; name: record_name; updates: record_update list}
| TryCatch of {body: body; ok_cases: case_clause list; catch_cases: catch_clause list; after: body}
| Tuple of expression list
| UnaryOperator of unary_operator * expression
| Variable of string
and expression = {line: line; simple_expression: simple_expression}
and qualifier =
| BitsGenerator of {pattern: pattern; expression: expression}
| Filter of expression
| Generator of {pattern: pattern; expression: expression}
and timeout = {time: expression; handler: body}
and bin_element =
{expression: expression; size: expression option; types: type_specifier list option}
(* A [None] field stands for _, which means "all other fields". *)
and record_update = {field: string option; expression: expression}
and association = {kind: association_kind; key: expression; value: expression}
and pattern = expression
and guard = expression
(** {2 S8.5 Clauses} *)
and 'pat clause = {line: line; patterns: 'pat list; guards: guard list; body: body}
and case_clause = pattern clause
and catch_clause = catch_pattern clause
and catch_pattern = {exception_: exception_; pattern: pattern; variable: string}
(** {2 S8.1: Module declarations and forms} *)
(* TODO: Add records, types, and specs. *)
type simple_form =
| Export of function_ list
| Import of {module_name: string; functions: function_ list}
| Module of string
| File of {path: string}
| Function of {function_: function_; clauses: case_clause list}
type form = {line: line; simple_form: simple_form}
type module_ = form list

@ -0,0 +1,524 @@
(*
* 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.
*)
open! IStd
module L = Logging
module YSU = Yojson.Safe.Util
module Ast = ErlangAst
open IOption.Let_syntax
type json = Yojson.Safe.t
type 'a parser = json -> 'a option
let unknown name json =
L.debug Capture Verbose "ErlangAst.Parse: unknown %s: %s@." name (Yojson.Safe.show json) ;
None
(* Takes a json of the form `List [j1;...;jn] and returns [f j1;...;f jn]. If skip_errors, then
elements that fail to parse are filtered out; otherwise, a parsing failure propagates up. *)
let to_list ?(skip_errors = false) ~(f : 'a parser) : 'a list parser =
fun json ->
try
let xs = List.map ~f (YSU.to_list json) in
if skip_errors then Some (List.filter_opt xs) else Option.all xs
with YSU.Type_error (error, json) -> unknown error json
let default_or (to_a : 'a parser) : 'a option parser = function
| `String "default" ->
Some None
| a ->
let* a = to_a a in
Some (Some a)
let to_line json : Ast.line option =
(* TODO: When OTP is upgraded, add case for line&col locations. *)
match json with
| `Int line ->
Some line
| `List [`List (`String "generated" :: _); `List [`String "location"; `Int line]] ->
Some line
| _ ->
unknown "line" json
let rec kill_singleton_list json =
match json with
| `List (`String _ :: _) ->
json (* an expression *)
| `List [x] ->
kill_singleton_list x (* singleton list dropped *)
| x ->
x
let one_list json =
match kill_singleton_list json with
| `List (`String _ :: _) as json ->
`List [json]
| `List _ as json ->
json (* already (nonsingleton) list *)
| json ->
`List [json]
let to_intlit json =
match json with
| `Int x ->
Some (Printf.sprintf "%d" x)
| `Intlit s ->
Some s
| _ ->
unknown "intlit" json
let to_binary_operator json : Ast.binary_operator option =
match json with
| `String "!" ->
Some Send
| `String "*" ->
Some Mul
| `String "+" ->
Some Add
| `String "++" ->
Some ListAdd
| `String "-" ->
Some Sub
| `String "--" ->
Some ListSub
| `String "/" ->
Some FDiv
| `String "/=" ->
Some NotEqual
| `String "<" ->
Some Less
| `String "=/=" ->
Some ExactlyNotEqual
| `String "=:=" ->
Some ExactlyEqual
| `String "=<" ->
Some AtMost
| `String "==" ->
Some Equal
| `String ">" ->
Some Greater
| `String ">=" ->
Some AtLeast
| `String "and" ->
Some And
| `String "andalso" ->
Some AndAlso
| `String "band" ->
Some BAnd
| `String "bor" ->
Some BOr
| `String "bsl" ->
Some Bsl
| `String "bsr" ->
Some Bsr
| `String "bxor" ->
Some BXor
| `String "div" ->
Some IDiv
| `String "or" ->
Some Or
| `String "orelse" ->
Some OrElse
| `String "rem" ->
Some Rem
| `String "xor" ->
Some Xor
| _ ->
unknown "binary_operator" json
let to_unary_operator json : Ast.unary_operator option =
match json with
| `String "-" ->
Some UMinus
| `String "bnot" ->
Some UBNot
| `String "not" ->
Some UNot
| _ ->
unknown "binary_operator" json
let to_exception json : Ast.exception_ option =
match json with
| `List [`String "atom"; _anno; `String atom] ->
Some (Atom atom)
| `List [`String "var"; _anno; `String variable] ->
Some (Pattern variable)
| _ ->
unknown "exception" json
let to_arity json : int option =
match json with
| `Int arity ->
Some arity
| `List [`String "integer"; _anno; `Int arity] ->
Some arity
| _ ->
unknown "arity" json
let to_module_reference json : Ast.module_reference option =
match json with
| `List [`String "atom"; _anno; `String name] ->
Some (ModuleName name)
| `List [`String "var"; _anno; `String variable] ->
Some (ModuleVariable variable)
| `String name ->
Some (ModuleName name)
| _ ->
unknown "module_reference" json
let to_function_reference json : Ast.function_reference option =
match json with
| `List [`String "atom"; _anno; `String name] ->
Some (FunctionName name)
| `List [`String "var"; _anno; `String variable] ->
Some (FunctionVariable variable)
| `String name ->
Some (FunctionName name)
| _ ->
unknown "function_reference" json
let rec to_expression json : Ast.expression option =
let expr line simple_expression : Ast.expression option = Some {line; simple_expression} in
match json with
| `List [`String "atom"; anno; `Bool atom] ->
let* line = to_line anno in
expr line (Literal (Atom (Printf.sprintf "%b" atom)))
| `List [`String "atom"; anno; `Null] ->
let* line = to_line anno in
expr line (Literal (Atom "null"))
| `List [`String "atom"; anno; `String atom] ->
let* line = to_line anno in
expr line (Literal (Atom atom))
| `List [`String "bc"; anno; expression; qualifiers] ->
let* line = to_line anno in
let* expression = to_expression expression in
let* qualifiers = to_list ~f:to_qualifier qualifiers in
expr line (BitstringComprehension {expression; qualifiers})
| `List [`String "bin"; anno; elements] ->
let* line = to_line anno in
let* elements = to_list ~f:to_bin_element elements in
expr line (BitstringConstructor elements)
| `List [`String "block"; anno; body] ->
let* line = to_line anno in
let* body = to_body body in
expr line (Block body)
| `List [`String "call"; anno; `List [`String "remote"; `Int _line2; module_; function_]; args] ->
let* line = to_line anno in
let* module_ = to_expression module_ in
let* function_ = to_expression function_ in
let* args = to_body args in
expr line (Call {module_= Some module_; function_; args})
| `List [`String "call"; anno; function_; args] ->
let* line = to_line anno in
let* function_ = to_expression function_ in
let* args = to_body args in
expr line (Call {module_= None; function_; args})
| `List [`String "case"; anno; expression; cases] ->
let* line = to_line anno in
let* expression = to_expression expression in
let* cases = to_list ~f:to_case_clause cases in
expr line (Case {expression; cases})
| `List [`String "catch"; anno; expression] ->
let* line = to_line anno in
let* expression = to_expression expression in
expr line (Catch expression)
| `List [`String "char"; anno; charlit] ->
let* line = to_line anno in
let* charlit = to_intlit charlit in
expr line (Literal (Char charlit))
| `List [`String "cons"; anno; head; tail] ->
let* line = to_line anno in
let* head = to_expression head in
let* tail = to_expression tail in
expr line (Cons {head; tail})
| `List [`String "float"; anno; `Float floatlit] ->
let* line = to_line anno in
expr line (Literal (Float floatlit))
| `List [`String "fun"; anno; `List [`String "clauses"; cases]] ->
let* line = to_line anno in
let* cases = to_list ~f:to_case_clause cases in
expr line (Lambda {name= None; cases})
| `List [`String "fun"; anno; `List [`String "function"; function_; arity]] ->
let* line = to_line anno in
let* function_ = to_function_reference function_ in
let* arity = to_arity arity in
expr line (Fun {module_= ModuleMissing; function_; arity})
| `List [`String "fun"; anno; `List [`String "function"; module_; function_; arity]] ->
let* line = to_line anno in
let* module_ = to_module_reference module_ in
let* function_ = to_function_reference function_ in
let* arity = to_arity arity in
expr line (Fun {module_; function_; arity})
| `List [`String "if"; anno; cases] ->
let* line = to_line anno in
let* cases = to_list ~f:to_case_clause cases in
expr line (If cases)
| `List [`String "integer"; anno; intlit] ->
let* line = to_line anno in
let* intlit = to_intlit intlit in
expr line (Literal (Int intlit))
| `List [`String "lc"; anno; expression; qualifiers] ->
let* line = to_line anno in
let* expression = to_expression expression in
let* qualifiers = to_list ~f:to_qualifier qualifiers in
expr line (ListComprehension {expression; qualifiers})
| `List [`String "map"; anno; map; updates] ->
let* line = to_line anno in
let* map = to_expression map in
let* updates = to_list ~f:to_association updates in
expr line (Map {map= Some map; updates})
| `List [`String "map"; anno; updates] ->
let* line = to_line anno in
let* updates = to_list ~f:to_association updates in
expr line (Map {map= None; updates})
| `List [`String "match"; anno; pattern; body] ->
let* line = to_line anno in
let* pattern = to_expression pattern in
let* body = to_expression body in
expr line (Match {pattern; body})
| `List [`String "named_fun"; anno; `String name; cases] ->
let* line = to_line anno in
let* cases = to_list ~f:to_case_clause cases in
expr line (Lambda {name= Some name; cases})
| `List [`String "nil"; anno] ->
let* line = to_line anno in
expr line Nil
| `List [`String "op"; _anno; `String "+"; argument] ->
to_expression argument
| `List [`String "op"; anno; op; argument] ->
let* line = to_line anno in
let* op = to_unary_operator op in
let* argument = to_expression argument in
expr line (UnaryOperator (op, argument))
| `List [`String "op"; anno; op; left; right] ->
let* line = to_line anno in
let* op = to_binary_operator op in
let* left = to_expression left in
let* right = to_expression right in
expr line (BinaryOperator (left, op, right))
| `List [`String "receive"; anno; cases; time; handler] ->
let* line = to_line anno in
let* cases = to_list ~f:to_case_clause cases in
let* time = to_expression time in
let* handler = to_body handler in
expr line (Receive {cases; timeout= Some {time; handler}})
| `List [`String "receive"; anno; cases] ->
let* line = to_line anno in
let* cases = to_list ~f:to_case_clause cases in
expr line (Receive {cases; timeout= None})
| `List [`String "record"; anno; `String name; updates] ->
let* line = to_line anno in
let* updates = to_list ~f:to_record_update updates in
expr line (RecordUpdate {record= None; name; updates})
| `List [`String "record"; anno; record; `String name; updates] ->
let* line = to_line anno in
let* record = to_expression record in
let* updates = to_list ~f:to_record_update updates in
expr line (RecordUpdate {record= Some record; name; updates})
| `List
[ `String "record_field"
; anno
; record
; `String name
; `List [`String "atom"; _anno; `String field] ] ->
let* line = to_line anno in
let* record = to_expression record in
expr line (RecordAccess {record; name; field})
| `List
[ `String "record_index"
; anno
; `String name
; `List [`String "atom"; _anno_field; `String field] ] ->
let* line = to_line anno in
expr line (RecordIndex {name; field})
| `List [`String "string"; anno; `List []] ->
let* line = to_line anno in
expr line (Literal (String ""))
| `List [`String "string"; anno; `String s] ->
let* line = to_line anno in
expr line (Literal (String s))
| `List [`String "try"; anno; body; ok_cases; catch_cases; after] ->
let* line = to_line anno in
let* body = to_body body in
let* ok_cases = to_list ~f:to_case_clause ok_cases in
let* catch_cases = to_list ~f:to_catch_clause catch_cases in
let* after = to_body after in
expr line (TryCatch {body; ok_cases; catch_cases; after})
| `List [`String "tuple"; anno; tuple] ->
let* line = to_line anno in
let* xs = to_list ~f:to_expression tuple in
expr line (Tuple xs)
| `List [`String "var"; anno; `String variable] ->
let* line = to_line anno in
expr line (Variable variable)
| _ ->
unknown "expression" json
and to_body json : Ast.expression list option = to_list ~f:to_expression json
and to_association json : Ast.association option =
match json with
| `List [`String "map_field_assoc"; _anno; key; value] ->
let* key = to_expression key in
let* value = to_expression value in
Some {Ast.kind= Arrow; key; value}
| `List [`String "map_field_exact"; _anno; key; value] ->
let* key = to_expression key in
let* value = to_expression value in
Some {Ast.kind= Exact; key; value}
| _ ->
unknown "association" json
and to_record_update json : Ast.record_update option =
match json with
| `List
[ `String "record_field"
; _anno_update
; `List [`String "var"; _anno_field; `String "_"]
; expression ] ->
let* expression = to_expression expression in
Some {Ast.field= None; expression}
| `List
[ `String "record_field"
; _anno_update
; `List [`String "atom"; _anno_field; `String field]
; expression ] ->
let* expression = to_expression expression in
Some {Ast.field= Some field; expression}
| _ ->
unknown "record_update" json
and to_bin_element json : Ast.bin_element option =
match json with
| `List [`String "bin_element"; _anno; expression; size; (* TODO *) _type_specifier_list] ->
let* expression = to_expression expression in
let* size = default_or to_expression size in
Some {Ast.expression; size; types= None}
| _ ->
unknown "bin_element" json
and to_catch_pattern json : Ast.catch_pattern option =
match json with
| `List
[ `String "tuple"
; _anno
; `List [exception_; pattern; `List [`String "var"; _var_anno; `String variable]] ] ->
let* exception_ = to_exception exception_ in
let* pattern = to_expression pattern in
Some {Ast.exception_; pattern; variable}
| _ ->
unknown "catch_pattern" json
and to_guards line json : Ast.expression list option =
let andalso e1 e2 = {Ast.line; simple_expression= BinaryOperator (e1, AndAlso, e2)} in
let to_dnf_term xs =
let* atom_guards = to_list ~f:to_expression (one_list xs) in
match atom_guards with
| [] ->
unknown "empty_guard?" json
| [e] ->
Some e
| e :: es ->
Some (List.fold ~init:e ~f:andalso es)
in
json |> one_list |> to_list ~f:to_dnf_term
and to_clause : 'pat. 'pat parser -> 'pat Ast.clause parser =
fun to_pat json ->
match json with
| `List [`String "clause"; anno; patterns; guards; body] ->
let* line = to_line anno in
let* patterns = to_list ~f:to_pat patterns in
let* guards = to_guards line guards in
let body = one_list body in
let* body = to_body body in
Some {Ast.line; patterns; guards; body}
| json ->
unknown "clause" json
and to_qualifier json : Ast.qualifier option =
match json with
| `List [`String "b_generate"; _anno; pattern; expression] ->
let* pattern = to_expression pattern in
let* expression = to_expression expression in
Some (Ast.BitsGenerator {pattern; expression})
| `List [`String "generate"; _anno; pattern; expression] ->
let* pattern = to_expression pattern in
let* expression = to_expression expression in
Some (Ast.Generator {pattern; expression})
| filter ->
let* filter = to_expression filter in
Some (Ast.Filter filter)
and to_case_clause json : Ast.case_clause option = to_clause to_expression json
and to_catch_clause json : Ast.catch_clause option = to_clause to_catch_pattern json
let to_function json : Ast.function_ option =
match json with
| `List [`String function_; `Int arity] ->
let function_ = Ast.FunctionName function_ in
Some {module_= ModuleMissing; function_; arity}
| _ ->
unknown "function" json
let to_line_form json : Ast.form option =
let form line simple_form : Ast.form option = Some {line; simple_form} in
match json with
| `List [`String "attribute"; anno; `String "file"; `List [`String path; _anno_file]] ->
let* line = to_line anno in
form line (File {path})
| `List [`String "attribute"; anno; `String "module"; `String module_name] ->
let* line = to_line anno in
form line (Module module_name)
| `List [`String "attribute"; anno; `String "import"; `List [`String module_name; functions]] ->
let* line = to_line anno in
let* functions = to_list ~f:to_function functions in
form line (Import {module_name; functions})
| `List [`String "attribute"; anno; `String "export"; function_] ->
let* line = to_line anno in
let* func_list = to_list ~f:to_function function_ in
form line (Export func_list)
| `List [`String "function"; anno; `String function_; `Int arity; case_clause] ->
let* line = to_line anno in
let* clauses = to_list ~f:to_case_clause case_clause in
let function_ : Ast.function_reference = FunctionName function_ in
let function_ : Ast.function_ = {module_= ModuleMissing; function_; arity} in
form line (Function {function_; clauses})
| `List [`String "attribute"; _anno; `String _unknown_attribute; _] ->
(* TODO: handle types (spec, record, ...) *)
None
| `List [`String "eof"; _] ->
None
| _ ->
unknown "form" json
let to_module json : Ast.module_ option = to_list ~skip_errors:true ~f:to_line_form json

@ -0,0 +1,10 @@
(*
* 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.
*)
open! IStd
val to_module : Yojson.Safe.t -> ErlangAst.module_ option

@ -0,0 +1,12 @@
; 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.
(library
(name ErlangFrontend)
(public_name infer.ErlangFrontend)
(flags
(:standard -open Core -open IStdlib -open IStd -open OpenSource -open IBase
-open IR))
(libraries core memtrace IStdlib IBase IR))

@ -4,7 +4,6 @@
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*) *)
open! IStd open! IStd
module L = Logging module L = Logging
@ -15,9 +14,30 @@ let run_rebar result_dir args =
Process.create_process_and_wait ~prog ~args Process.create_process_and_wait ~prog ~args
let parse_and_store result_dir =
let process json =
let _ast = ErlangJsonParser.to_module json in
(* TODO: Translate to Sil, load source, call [SourceFiles.add]. *)
()
in
let log error = L.progress "E: %s@." error in
let read_one_ast json_file =
if Filename.check_suffix json_file ".json" then (
L.progress "P: parsing %s@." json_file ;
match Utils.read_safe_json_file json_file with
| Ok json ->
process json
| Error error ->
log error )
in
Utils.directory_iter read_one_ast result_dir
let capture ~args = let capture ~args =
let in_dir = ResultsDir.get_path Temporary in Option.iter ~f:parse_and_store Config.erlang_ast_dir ;
let rebar_result_dir = Filename.temp_dir ~in_dir "rebar3infer" "" in if not Config.erlang_skip_rebar3 then (
run_rebar rebar_result_dir args ; let in_dir = ResultsDir.get_path Temporary in
(* TODO: parse the JSON files *) let rebar_result_dir = Filename.temp_dir ~in_dir "rebar3infer" "" in
if not Config.debug_mode then Utils.rmtree rebar_result_dir run_rebar rebar_result_dir args ;
parse_and_store rebar_result_dir ;
if not Config.debug_mode then Utils.rmtree rebar_result_dir )

@ -17,9 +17,9 @@ let library =
(:standard (:standard
-open Core -open IStdlib -open IStd -open OpenSource -open ATDGenerated -open Core -open IStdlib -open IStd -open OpenSource -open ATDGenerated
-open IBase -open IR -open Absint -open BO -open Checkers -open Costlib -open Concurrency -open Backend -open IBase -open IR -open Absint -open BO -open Checkers -open Costlib -open Concurrency -open Backend
-open TestDeterminators -open ClangFrontend -open ASTLanguage -open JavaFrontend %s %s)) -open TestDeterminators -open ClangFrontend -open ASTLanguage -open JavaFrontend -open ErlangFrontend %s %s))
(libraries xmlm core IStdlib ATDGenerated IBase IR Absint BO Checkers Costlib Concurrency Backend (libraries xmlm core IStdlib ATDGenerated IBase IR Absint BO Checkers Costlib Concurrency Backend
TestDeterminators ClangFrontend ASTLanguage JavaFrontend) TestDeterminators ClangFrontend ASTLanguage JavaFrontend ErlangFrontend)
(preprocess (pps ppx_compare)) (preprocess (pps ppx_compare))
) )

Loading…
Cancel
Save