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.
435 lines
13 KiB
435 lines
13 KiB
(*
|
|
* 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 F = Format
|
|
module L = Logging
|
|
|
|
module BoundEnd = struct
|
|
type t = LowerBound | UpperBound [@@deriving compare]
|
|
|
|
let equal = [%compare.equal: t]
|
|
|
|
let neg = function LowerBound -> UpperBound | UpperBound -> LowerBound
|
|
|
|
let to_string = function LowerBound -> "lb" | UpperBound -> "ub"
|
|
end
|
|
|
|
module SymbolPath = struct
|
|
type deref_kind = Deref_ArrayIndex | Deref_COneValuePointer | Deref_CPointer | Deref_JavaPointer
|
|
|
|
let compare_deref_kind _ _ = 0
|
|
|
|
type field_typ = Typ.t option
|
|
|
|
let compare_field_typ _ _ = 0
|
|
|
|
let is_field_depth_beyond_limit =
|
|
match Config.bo_field_depth_limit with
|
|
| None ->
|
|
fun _depth -> false
|
|
| Some limit ->
|
|
fun depth -> depth > limit
|
|
|
|
|
|
include (* Enforce invariants on Field and StarField *) (
|
|
struct
|
|
type partial =
|
|
| Pvar of Pvar.t
|
|
| Deref of deref_kind * partial
|
|
| Field of {fn: Fieldname.t; prefix: partial; typ: field_typ}
|
|
| Callsite of {ret_typ: Typ.t; cs: CallSite.t; obj_path: partial option [@compare.ignore]}
|
|
| StarField of {last_field: Fieldname.t; prefix: partial}
|
|
[@@deriving compare]
|
|
|
|
let of_pvar pvar = Pvar pvar
|
|
|
|
let of_callsite ?obj_path ~ret_typ cs = Callsite {ret_typ; cs; obj_path}
|
|
|
|
let deref ~deref_kind p = Deref (deref_kind, p)
|
|
|
|
let star_field p0 fn =
|
|
let rec aux = function
|
|
| Pvar _ | Callsite _ ->
|
|
StarField {last_field= fn; prefix= p0}
|
|
| Deref (_, p) | Field {prefix= p} ->
|
|
aux p
|
|
| StarField {last_field} as p when Fieldname.equal fn last_field ->
|
|
p
|
|
| StarField {prefix} ->
|
|
StarField {last_field= fn; prefix}
|
|
in
|
|
aux p0
|
|
|
|
|
|
let field ?typ p0 fn =
|
|
let rec aux ~depth p =
|
|
if is_field_depth_beyond_limit depth then star_field p0 fn
|
|
else
|
|
match p with
|
|
| Pvar _ | Callsite _ ->
|
|
Field {fn; prefix= p0; typ}
|
|
| Field {fn= fn'} when Fieldname.equal fn fn' ->
|
|
StarField {last_field= fn; prefix= p0}
|
|
| Field {prefix= p} | Deref (_, p) ->
|
|
aux ~depth:(depth + 1) p
|
|
| StarField {last_field} as p when Fieldname.equal fn last_field ->
|
|
p
|
|
| StarField {prefix} ->
|
|
StarField {last_field= fn; prefix}
|
|
in
|
|
aux ~depth:0 p0
|
|
end :
|
|
sig
|
|
type partial = private
|
|
| Pvar of Pvar.t
|
|
| Deref of deref_kind * partial
|
|
| Field of {fn: Fieldname.t; prefix: partial; typ: field_typ}
|
|
| Callsite of {ret_typ: Typ.t; cs: CallSite.t; obj_path: partial option}
|
|
| StarField of {last_field: Fieldname.t; prefix: partial}
|
|
[@@deriving compare]
|
|
|
|
val of_pvar : Pvar.t -> partial
|
|
|
|
val of_callsite : ?obj_path:partial -> ret_typ:Typ.t -> CallSite.t -> partial
|
|
|
|
val deref : deref_kind:deref_kind -> partial -> partial
|
|
|
|
val field : ?typ:Typ.t -> partial -> Fieldname.t -> partial
|
|
|
|
val star_field : partial -> Fieldname.t -> partial
|
|
end )
|
|
|
|
type t =
|
|
| Normal of partial
|
|
| Offset of {p: partial; is_void: bool}
|
|
| Length of {p: partial; is_void: bool}
|
|
| Modeled of {p: partial; is_expensive: bool}
|
|
[@@deriving compare]
|
|
|
|
let equal = [%compare.equal: t]
|
|
|
|
let equal_partial = [%compare.equal: partial]
|
|
|
|
let normal p = Normal p
|
|
|
|
let offset p ~is_void = Offset {p; is_void}
|
|
|
|
let length p ~is_void = Length {p; is_void}
|
|
|
|
let modeled p ~is_expensive = Modeled {p; is_expensive}
|
|
|
|
let is_this = function Pvar pvar -> Pvar.is_this pvar || Pvar.is_self pvar | _ -> false
|
|
|
|
let rec get_pvar = function
|
|
| Pvar pvar ->
|
|
Some pvar
|
|
| Deref (_, partial) | Field {prefix= partial} | StarField {prefix= partial} ->
|
|
get_pvar partial
|
|
| Callsite _ ->
|
|
None
|
|
|
|
|
|
let is_request x =
|
|
Option.exists (get_pvar x) ~f:(fun pvar ->
|
|
String.equal (Pvar.get_simplified_name pvar) "request" )
|
|
|
|
|
|
let rec pp_partial_paren ~paren fmt = function
|
|
| Pvar pvar ->
|
|
if Config.bo_debug >= 3 then Pvar.pp_value fmt pvar else Pvar.pp_value_non_verbose fmt pvar
|
|
| Deref (Deref_JavaPointer, p) when Config.bo_debug < 3 ->
|
|
pp_partial_paren ~paren fmt p
|
|
| Deref (Deref_ArrayIndex, p) ->
|
|
F.fprintf fmt "%a[*]" (pp_partial_paren ~paren:true) p
|
|
| Deref ((Deref_COneValuePointer | Deref_CPointer | Deref_JavaPointer), p) ->
|
|
pp_pointer ~paren fmt p
|
|
| Field {fn; prefix= Deref ((Deref_COneValuePointer | Deref_CPointer), p)} ->
|
|
BufferOverrunField.pp ~pp_lhs:(pp_partial_paren ~paren:true) ~sep:"->" fmt p fn
|
|
| Field {fn; prefix= p} ->
|
|
BufferOverrunField.pp ~pp_lhs:(pp_partial_paren ~paren:true) ~sep:"." fmt p fn
|
|
| Callsite {cs; obj_path= Some obj_path} ->
|
|
if paren then F.pp_print_string fmt "(" ;
|
|
F.fprintf fmt "%a.%a" (pp_partial_paren ~paren:false) obj_path
|
|
(Procname.pp_simplified_string ~withclass:false)
|
|
(CallSite.pname cs) ;
|
|
if paren then F.pp_print_string fmt ")"
|
|
| Callsite {cs; obj_path= None} ->
|
|
Procname.pp_simplified_string ~withclass:true fmt (CallSite.pname cs)
|
|
| StarField {last_field; prefix} ->
|
|
BufferOverrunField.pp ~pp_lhs:(pp_star ~paren:true) ~sep:"." fmt prefix last_field
|
|
|
|
|
|
and pp_pointer ~paren fmt p =
|
|
if paren then F.fprintf fmt "(" ;
|
|
F.fprintf fmt "*%a" (pp_partial_paren ~paren:false) p ;
|
|
if paren then F.fprintf fmt ")"
|
|
|
|
|
|
and pp_star ~paren fmt p = pp_partial_paren ~paren fmt p ; F.pp_print_string fmt ".*"
|
|
|
|
let pp_partial = pp_partial_paren ~paren:false
|
|
|
|
let pp_is_void fmt is_void = if is_void then F.fprintf fmt "(v)"
|
|
|
|
let pp fmt = function
|
|
| Modeled {p; is_expensive} ->
|
|
if is_expensive then F.fprintf fmt "%a(expensive call)" pp_partial p
|
|
else F.fprintf fmt "%a.modeled" pp_partial p
|
|
| Normal p ->
|
|
pp_partial fmt p
|
|
| Offset {p; is_void} ->
|
|
F.fprintf fmt "%a.offset%a" pp_partial p pp_is_void is_void
|
|
| Length {p= Field {fn; prefix= p}; is_void}
|
|
when BufferOverrunField.is_java_collection_internal_array fn ->
|
|
F.fprintf fmt "%a.length%a" pp_partial p pp_is_void is_void
|
|
| Length {p= StarField {last_field= fn; prefix= p}; is_void}
|
|
when BufferOverrunField.is_java_collection_internal_array fn ->
|
|
F.fprintf fmt "%a.length%a" (pp_star ~paren:false) p pp_is_void is_void
|
|
| Length {p; is_void} ->
|
|
F.fprintf fmt "%a.length%a" pp_partial p pp_is_void is_void
|
|
|
|
|
|
let pp_mark ~markup = if markup then MarkupFormatter.wrap_monospaced pp else pp
|
|
|
|
let rec represents_multiple_values = function
|
|
(* TODO depending on the result, the call might represent multiple values *)
|
|
| Callsite _ | Pvar _ ->
|
|
false
|
|
| Deref (Deref_ArrayIndex, _) | StarField _ ->
|
|
true
|
|
| Deref (Deref_CPointer, p)
|
|
(* Deref_CPointer is unsound here but avoids many FPs for non-array pointers *)
|
|
| Deref ((Deref_COneValuePointer | Deref_JavaPointer), p)
|
|
| Field {prefix= p} ->
|
|
represents_multiple_values p
|
|
|
|
|
|
let rec represents_multiple_values_sound = function
|
|
| Callsite _ | StarField _ ->
|
|
true
|
|
| Pvar _ ->
|
|
false
|
|
| Deref ((Deref_ArrayIndex | Deref_CPointer), _) ->
|
|
true
|
|
| Deref ((Deref_COneValuePointer | Deref_JavaPointer), p) | Field {prefix= p} ->
|
|
represents_multiple_values_sound p
|
|
|
|
|
|
let rec represents_callsite_sound_partial = function
|
|
| Callsite _ ->
|
|
true
|
|
| Pvar _ ->
|
|
false
|
|
| Deref (_, p) | Field {prefix= p} | StarField {prefix= p} ->
|
|
represents_callsite_sound_partial p
|
|
|
|
|
|
let rec exists_pvar_partial ~f = function
|
|
| Pvar pvar ->
|
|
f pvar
|
|
| Deref (_, p) | Field {prefix= p} | StarField {prefix= p} ->
|
|
exists_pvar_partial ~f p
|
|
| Callsite _ ->
|
|
false
|
|
|
|
|
|
let rec exists_str_partial ~f = function
|
|
| Pvar pvar ->
|
|
f (Pvar.to_string pvar)
|
|
| Deref (_, x) ->
|
|
exists_str_partial ~f x
|
|
| Field {fn= fld; prefix= x} | StarField {last_field= fld; prefix= x} ->
|
|
f (Fieldname.to_string fld) || exists_str_partial ~f x
|
|
| Callsite _ ->
|
|
false
|
|
|
|
|
|
let exists_str ~f = function
|
|
| Modeled {p} | Normal p | Offset {p} | Length {p} ->
|
|
exists_str_partial ~f p
|
|
|
|
|
|
let is_void_ptr_path = function
|
|
| Offset {is_void} | Length {is_void} ->
|
|
is_void
|
|
| Normal _ | Modeled _ ->
|
|
false
|
|
|
|
|
|
let is_cpp_vector_elem = function
|
|
| Field {fn} ->
|
|
BufferOverrunField.is_cpp_vector_elem fn
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let rec is_global_partial = function
|
|
| Pvar pvar ->
|
|
Pvar.is_global pvar
|
|
| Deref (_, x) | Field {prefix= x} | StarField {prefix= x} ->
|
|
is_global_partial x
|
|
| Callsite _ ->
|
|
false
|
|
|
|
|
|
let is_global = function Normal p | Offset {p} | Length {p} | Modeled {p} -> is_global_partial p
|
|
end
|
|
|
|
module Symbol = struct
|
|
type extra_bool = bool
|
|
|
|
let compare_extra_bool _ _ = 0
|
|
|
|
(* NOTE: non_int represents the symbols that are not integer type,
|
|
so that their ranges are not used in the cost checker. *)
|
|
type t =
|
|
| PulseValue of PulseAbstractValue.t
|
|
| OneValue of {unsigned: extra_bool; non_int: extra_bool; path: SymbolPath.t}
|
|
| BoundEnd of
|
|
{unsigned: extra_bool; non_int: extra_bool; path: SymbolPath.t; bound_end: BoundEnd.t}
|
|
[@@deriving compare]
|
|
|
|
let pp : F.formatter -> t -> unit =
|
|
fun fmt s ->
|
|
match s with
|
|
| PulseValue v ->
|
|
PulseAbstractValue.pp fmt v
|
|
| OneValue {unsigned; non_int; path} | BoundEnd {unsigned; non_int; path} ->
|
|
SymbolPath.pp fmt path ;
|
|
( if Config.developer_mode then
|
|
match s with
|
|
| BoundEnd {bound_end} ->
|
|
Format.fprintf fmt ".%s" (BoundEnd.to_string bound_end)
|
|
| PulseValue _ | OneValue _ ->
|
|
() ) ;
|
|
if Config.bo_debug > 1 then
|
|
F.fprintf fmt "(%c%s)" (if unsigned then 'u' else 's') (if non_int then "n" else "")
|
|
|
|
|
|
let compare s1 s2 =
|
|
match (s1, s2) with
|
|
| PulseValue _, (OneValue _ | BoundEnd _) ->
|
|
-1
|
|
| (OneValue _ | BoundEnd _), PulseValue _ ->
|
|
1
|
|
| PulseValue x, PulseValue y ->
|
|
PulseAbstractValue.compare x y
|
|
| OneValue _, BoundEnd _ ->
|
|
-1
|
|
| BoundEnd _, OneValue _ ->
|
|
1
|
|
| OneValue {unsigned= unsigned1}, OneValue {unsigned= unsigned2}
|
|
| BoundEnd {unsigned= unsigned1}, BoundEnd {unsigned= unsigned2} ->
|
|
let r = compare s1 s2 in
|
|
if Int.equal r 0 && not (Bool.equal unsigned1 unsigned2) then (
|
|
L.internal_error "values are equal but their signs are different: %a <> %a" pp s1 pp s2 ;
|
|
Bool.compare unsigned1 unsigned2 )
|
|
else r
|
|
|
|
|
|
type 'res eval = t -> BoundEnd.t -> 'res AbstractDomain.Types.bottom_lifted
|
|
|
|
let equal = [%compare.equal: t]
|
|
|
|
let paths_equal s1 s2 =
|
|
match (s1, s2) with
|
|
| PulseValue _, _ | _, PulseValue _ | OneValue _, BoundEnd _ | BoundEnd _, OneValue _ ->
|
|
false
|
|
| OneValue {path= path1}, OneValue {path= path2} | BoundEnd {path= path1}, BoundEnd {path= path2}
|
|
->
|
|
SymbolPath.equal path1 path2
|
|
|
|
|
|
type make_t = unsigned:bool -> ?non_int:bool -> SymbolPath.t -> t
|
|
|
|
let make_onevalue : make_t =
|
|
fun ~unsigned ?(non_int = false) path -> OneValue {unsigned; non_int; path}
|
|
|
|
|
|
let make_boundend : BoundEnd.t -> make_t =
|
|
fun bound_end ~unsigned ?(non_int = false) path -> BoundEnd {unsigned; non_int; path; bound_end}
|
|
|
|
|
|
let of_pulse_value v = PulseValue v
|
|
|
|
let pp_mark ~markup = if markup then MarkupFormatter.wrap_monospaced pp else pp
|
|
|
|
let is_unsigned : t -> bool = function
|
|
| PulseValue _ ->
|
|
false
|
|
| OneValue {unsigned} | BoundEnd {unsigned} ->
|
|
unsigned
|
|
|
|
|
|
let is_non_int : t -> bool = function
|
|
| PulseValue _ ->
|
|
false
|
|
| OneValue {non_int} | BoundEnd {non_int} ->
|
|
non_int
|
|
|
|
|
|
let is_global : t -> bool = function
|
|
| PulseValue _ ->
|
|
false
|
|
| OneValue {path} | BoundEnd {path} ->
|
|
SymbolPath.is_global path
|
|
|
|
|
|
let get_pulse_value_exn : t -> PulseAbstractValue.t = function
|
|
| PulseValue v ->
|
|
v
|
|
| OneValue _ | BoundEnd _ ->
|
|
assert false
|
|
|
|
|
|
(* This should be called on non-pulse bound as of now. *)
|
|
let path = function PulseValue _ -> assert false | OneValue {path} | BoundEnd {path} -> path
|
|
|
|
(* NOTE: This may not be satisfied in the cost checker for simplifying its results. *)
|
|
let check_bound_end s be =
|
|
if Config.bo_debug >= 3 then
|
|
match s with
|
|
| PulseValue _ | OneValue _ ->
|
|
()
|
|
| BoundEnd {bound_end} ->
|
|
if not (BoundEnd.equal be bound_end) then
|
|
L.d_printfln_escaped
|
|
"Mismatch of symbol's boundend and its position: %a is in a place for %s." pp s
|
|
(BoundEnd.to_string be)
|
|
|
|
|
|
let exists_str ~f = function
|
|
| PulseValue _ ->
|
|
false
|
|
| OneValue {path} | BoundEnd {path} ->
|
|
SymbolPath.exists_str ~f path
|
|
end
|
|
|
|
module SymbolSet = struct
|
|
include PrettyPrintable.MakePPSet (Symbol)
|
|
|
|
let union3 x y z = union (union x y) z
|
|
end
|
|
|
|
module SymbolMap = struct
|
|
include PrettyPrintable.MakePPMap (Symbol)
|
|
|
|
let for_all2 : f:(key -> 'a option -> 'b option -> bool) -> 'a t -> 'b t -> bool =
|
|
fun ~f x y ->
|
|
match merge (fun k x y -> if f k x y then None else raise Exit) x y with
|
|
| _ ->
|
|
true
|
|
| exception Exit ->
|
|
false
|
|
end
|
|
|
|
module SymbolPathSet = PrettyPrintable.MakePPSet (struct
|
|
type t = SymbolPath.partial [@@deriving compare]
|
|
|
|
let pp = SymbolPath.pp_partial
|
|
end)
|