You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
8.3 KiB
229 lines
8.3 KiB
(*
|
|
* Copyright (c) 2017-present, Facebook, Inc.
|
|
*
|
|
* 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 F = Format
|
|
module L = Logging
|
|
|
|
type t =
|
|
| AccessExpression of AccessExpression.t
|
|
| UnaryOperator of Unop.t * t * Typ.t option
|
|
| BinaryOperator of Binop.t * t * t
|
|
| Exception of t
|
|
| Closure of Typ.Procname.t * (AccessPath.base * t) list
|
|
| Constant of Const.t
|
|
| Cast of Typ.t * t
|
|
| Sizeof of Typ.t * t option
|
|
[@@deriving compare]
|
|
|
|
let rec pp fmt = function
|
|
| AccessExpression access_expr ->
|
|
AccessExpression.pp fmt access_expr
|
|
| UnaryOperator (op, e, _) ->
|
|
F.fprintf fmt "%s%a" (Unop.to_string op) pp e
|
|
| BinaryOperator (op, e1, e2) ->
|
|
F.fprintf fmt "%a %s %a" pp e1 (Binop.str Pp.text op) pp e2
|
|
| Exception e ->
|
|
F.fprintf fmt "exception %a" pp e
|
|
| Closure (pname, captured) ->
|
|
let pp_item fmt (base, exp) =
|
|
match exp with
|
|
| AccessExpression (Base b) when AccessPath.equal_base b base ->
|
|
F.fprintf fmt "%a captured" AccessPath.pp_base b
|
|
| _ ->
|
|
F.fprintf fmt "%a captured as %a" AccessPath.pp_base base pp exp
|
|
in
|
|
F.fprintf fmt "closure(%a, %a)" Typ.Procname.pp pname
|
|
(PrettyPrintable.pp_collection ~pp_item)
|
|
captured
|
|
| Constant c ->
|
|
Const.pp Pp.text fmt c
|
|
| Cast (typ, e) ->
|
|
F.fprintf fmt "(%a) %a" (Typ.pp_full Pp.text) typ pp e
|
|
| Sizeof (typ, length) ->
|
|
let pp_length fmt = Option.iter ~f:(F.fprintf fmt "[%a]" pp) in
|
|
F.fprintf fmt "sizeof(%a%a)" (Typ.pp_full Pp.text) typ pp_length length
|
|
|
|
|
|
let rec get_typ tenv = function
|
|
| AccessExpression access_expr ->
|
|
AccessExpression.get_typ access_expr tenv
|
|
| UnaryOperator (_, _, typ_opt) ->
|
|
typ_opt
|
|
| BinaryOperator ((Lt | Gt | Le | Ge | Eq | Ne | LAnd | LOr), _, _) ->
|
|
Some (Typ.mk (Typ.Tint Typ.IBool))
|
|
| BinaryOperator (_, e1, e2) -> (
|
|
match
|
|
(* TODO: doing this properly will require taking account of language-specific coercion
|
|
semantics. Only return a type when the operands have the same type for now *)
|
|
(get_typ tenv e1, get_typ tenv e2)
|
|
with
|
|
| Some typ1, Some typ2 when Typ.equal typ1 typ2 ->
|
|
Some typ1
|
|
| _ ->
|
|
None )
|
|
| Exception t ->
|
|
get_typ tenv t
|
|
| Closure _ | Constant (Cfun _) ->
|
|
(* We don't have a way to represent function types *)
|
|
None
|
|
| Constant (Cint _) ->
|
|
(* TODO: handle signedness *)
|
|
Some (Typ.mk (Typ.Tint Typ.IInt))
|
|
| Constant (Cfloat _) ->
|
|
Some (Typ.mk (Typ.Tfloat Typ.FFloat))
|
|
| Constant (Cclass _) ->
|
|
(* TODO: this only happens in Java. We probably need to change it to `Cclass of Typ.Name.t`
|
|
to give a useful result here *)
|
|
None
|
|
| Constant (Cstr _) ->
|
|
(* TODO: this will need to behave differently depending on whether we're in C++ or Java *)
|
|
None
|
|
| Cast (typ, _) ->
|
|
Some typ
|
|
| Sizeof _ ->
|
|
(* sizeof returns a size_t, which is an unsigned int *)
|
|
Some (Typ.mk (Typ.Tint Typ.IUInt))
|
|
|
|
|
|
let get_access_exprs exp0 =
|
|
let rec get_access_exprs_ exp acc =
|
|
match exp with
|
|
| AccessExpression ae ->
|
|
ae :: acc
|
|
| Cast (_, e) | UnaryOperator (_, e, _) | Exception e | Sizeof (_, Some e) ->
|
|
get_access_exprs_ e acc
|
|
| BinaryOperator (_, e1, e2) ->
|
|
get_access_exprs_ e1 acc |> get_access_exprs_ e2
|
|
| Closure (_, captured) ->
|
|
List.fold captured ~f:(fun acc (_, e) -> get_access_exprs_ e acc) ~init:acc
|
|
| Constant _ | Sizeof _ ->
|
|
acc
|
|
in
|
|
get_access_exprs_ exp0 []
|
|
|
|
|
|
(* convert an SIL expression into an HIL expression. the [f_resolve_id] function should map an SSA
|
|
temporary variable to the access path it represents. evaluating the HIL expression should
|
|
produce the same result as evaluating the SIL expression and replacing the temporary variables
|
|
using [f_resolve_id] *)
|
|
let of_sil ~include_array_indexes ~f_resolve_id ~add_deref exp typ =
|
|
let rec of_sil_ (exp: Exp.t) typ =
|
|
match exp with
|
|
| Var id ->
|
|
let ae =
|
|
match f_resolve_id (Var.of_id id) with
|
|
| Some access_expr ->
|
|
if add_deref then AccessExpression.normalize (Dereference access_expr)
|
|
else access_expr
|
|
| None ->
|
|
let access_expr = AccessExpression.of_id id typ in
|
|
if add_deref then AccessExpression.normalize (Dereference access_expr)
|
|
else access_expr
|
|
in
|
|
AccessExpression ae
|
|
| UnOp (op, e, typ_opt) ->
|
|
UnaryOperator (op, of_sil_ e typ, typ_opt)
|
|
| BinOp (op, e0, e1) ->
|
|
BinaryOperator (op, of_sil_ e0 typ, of_sil_ e1 typ)
|
|
| Exn e ->
|
|
Exception (of_sil_ e typ)
|
|
| Const c ->
|
|
Constant c
|
|
| Cast (cast_typ, e) ->
|
|
Cast (cast_typ, of_sil_ e typ)
|
|
| Sizeof {typ; dynamic_length} ->
|
|
Sizeof (typ, Option.map ~f:(fun e -> of_sil_ e typ) dynamic_length)
|
|
| Closure closure ->
|
|
let environment =
|
|
List.map
|
|
~f:(fun (value, pvar, typ) ->
|
|
(* TODO: this is a hack than can disappear once we have proper AccessExpressions (t23176430) *)
|
|
let typ' =
|
|
match value with
|
|
| Exp.Lvar pvar1 when Pvar.equal pvar1 pvar ->
|
|
(* captured by reference, change type to pointer *)
|
|
{typ with Typ.desc= Typ.Tptr (typ, Typ.Pk_pointer)}
|
|
| _ ->
|
|
(* capture by value *)
|
|
typ
|
|
in
|
|
(AccessPath.base_of_pvar pvar typ', of_sil_ value typ') )
|
|
closure.captured_vars
|
|
in
|
|
Closure (closure.name, environment)
|
|
| Lfield (root_exp, fld, root_exp_typ) -> (
|
|
match
|
|
AccessExpression.of_lhs_exp ~include_array_indexes ~add_deref exp typ ~f_resolve_id
|
|
with
|
|
| Some access_expr ->
|
|
AccessExpression access_expr
|
|
| None ->
|
|
(* unsupported field expression: represent with a dummy variable *)
|
|
of_sil_
|
|
(Exp.Lfield
|
|
( Var (Ident.create_normal (Ident.string_to_name (Exp.to_string root_exp)) 0)
|
|
, fld
|
|
, root_exp_typ )) typ )
|
|
| Lindex (Const (Cstr s), index_exp) ->
|
|
(* indexed string literal (e.g., "foo"[1]). represent this by introducing a dummy variable
|
|
for the string literal. if you actually need to see the value of the string literal in the
|
|
analysis, you should probably be using SIL. this is unsound if the code modifies the
|
|
literal, e.g. using `const_cast<char*>` *)
|
|
of_sil_ (Exp.Lindex (Var (Ident.create_normal (Ident.string_to_name s) 0), index_exp)) typ
|
|
| Lindex (root_exp, index_exp) -> (
|
|
match
|
|
AccessExpression.of_lhs_exp ~include_array_indexes ~add_deref exp typ ~f_resolve_id
|
|
with
|
|
| Some access_expr ->
|
|
AccessExpression access_expr
|
|
| None ->
|
|
(* unsupported index expression: represent with a dummy variable *)
|
|
of_sil_
|
|
(Exp.Lindex
|
|
( Var (Ident.create_normal (Ident.string_to_name (Exp.to_string root_exp)) 0)
|
|
, index_exp )) typ )
|
|
| Lvar _ ->
|
|
match
|
|
AccessExpression.of_lhs_exp ~include_array_indexes ~add_deref exp typ ~f_resolve_id
|
|
with
|
|
| Some access_expr ->
|
|
AccessExpression access_expr
|
|
| None ->
|
|
L.(die InternalError) "Couldn't convert var expression %a to access path" Exp.pp exp
|
|
in
|
|
of_sil_ exp typ
|
|
|
|
|
|
let is_null_literal = function Constant (Cint n) -> IntLit.isnull n | _ -> false
|
|
|
|
let is_int_zero = function Constant (Const.Cint i) -> IntLit.iszero i | _ -> false
|
|
|
|
let rec eval_arithmetic_binop op e1 e2 =
|
|
match (eval e1, eval e2) with
|
|
| Some (Const.Cint i1), Some (Const.Cint i2) ->
|
|
Some (Const.Cint (op i1 i2))
|
|
| _ ->
|
|
None
|
|
|
|
and eval = function
|
|
| Constant c ->
|
|
Some c
|
|
| BinaryOperator (Binop.Div, e1, e2) -> (
|
|
try eval_arithmetic_binop IntLit.div e1 e2 with Division_by_zero -> None )
|
|
| BinaryOperator (Binop.MinusA, e1, e2) ->
|
|
eval_arithmetic_binop IntLit.sub e1 e2
|
|
| BinaryOperator (Binop.Mod, e1, e2) ->
|
|
eval_arithmetic_binop IntLit.rem e1 e2
|
|
| BinaryOperator (Binop.Mult, e1, e2) ->
|
|
eval_arithmetic_binop IntLit.mul e1 e2
|
|
| BinaryOperator (Binop.PlusA, e1, e2) ->
|
|
eval_arithmetic_binop IntLit.add e1 e2
|
|
| _ ->
|
|
(* TODO: handle bitshifting cases, port eval_binop from RacerD.ml *)
|
|
None
|