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.
271 lines
10 KiB
271 lines
10 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 L = Logging
|
|
|
|
type scope_kind =
|
|
| Breakable (** loop or switch statement within which it's ok to [break;] *)
|
|
| Compound (** inside a CompoundStmt *)
|
|
| InitialScope (** should be only one of these at the bottom of the stack *)
|
|
[@@deriving compare]
|
|
|
|
let equal_scope_kind = [%compare.equal: scope_kind]
|
|
|
|
let string_of_kind = function
|
|
| Compound ->
|
|
"Compound"
|
|
| Breakable ->
|
|
"Breakable"
|
|
| InitialScope ->
|
|
"InitialScope"
|
|
|
|
|
|
(** the [current] scope is distinguished to make it easy to add elements to it *)
|
|
type 'a scope = {current: 'a list; current_kind: scope_kind; outers: ('a list * scope_kind) list}
|
|
|
|
(** executes [f] in new scope where [kind] has been pushed on top *)
|
|
let in_ kind scope ~f =
|
|
L.debug Capture Verbose "<@[<v2>%s|@," (string_of_kind kind) ;
|
|
let scope =
|
|
{current= []; current_kind= kind; outers= (scope.current, scope.current_kind) :: scope.outers}
|
|
in
|
|
let scope, x = f scope in
|
|
let (current, current_kind), outers =
|
|
match scope.outers with [] -> assert false | top :: rest -> (top, rest)
|
|
in
|
|
L.debug Capture Verbose "@]@;/%s>" (string_of_kind scope.current_kind) ;
|
|
(scope.current, {current; current_kind; outers}, x)
|
|
|
|
|
|
let rev_append xs scope = {scope with current= List.rev_append xs scope.current}
|
|
|
|
let collect_until kind scope =
|
|
let rec aux kind rev_vars_to_destroy = function
|
|
| [] ->
|
|
assert false
|
|
| (in_scope, kind') :: outers ->
|
|
let rev_vars_to_destroy = List.rev_append in_scope rev_vars_to_destroy in
|
|
if equal_scope_kind kind' kind then List.rev rev_vars_to_destroy
|
|
else aux kind rev_vars_to_destroy outers
|
|
in
|
|
aux kind [] ((scope.current, scope.current_kind) :: scope.outers)
|
|
|
|
|
|
let breaks_control_flow = function
|
|
| Clang_ast_t.(ReturnStmt _ | BreakStmt _ | ContinueStmt _) ->
|
|
true
|
|
| _ ->
|
|
false
|
|
|
|
|
|
module Variables = struct
|
|
let pp_var_decl f = function
|
|
| Clang_ast_t.VarDecl (_, {ni_name}, _, _) ->
|
|
Format.pp_print_string f ni_name
|
|
| _ ->
|
|
assert false
|
|
|
|
|
|
type scope =
|
|
{ outer_scope: Clang_ast_t.stmt list (** statements that are under the new scope *)
|
|
; breakable_scope: Clang_ast_t.stmt list
|
|
(** the body of a loop or switch statement that defines the scope that [BreakStmt] and
|
|
[ContinueStmt] will exit *)
|
|
; swallow_destructors: bool
|
|
(** That scope does not generate destructor calls (eg because it ends in an instruction
|
|
that will already do so like [ReturnStmt]). We still want to generate a scope to catch
|
|
variables declared in that scope and avoid them being destroyed elsewhere. *)
|
|
}
|
|
|
|
(** get which statements define a variable scope and possible a breakable scope *)
|
|
let get_scopes stmt =
|
|
let is_compound_stmt_ending_in_control_flow_break stmt =
|
|
match (stmt : Clang_ast_t.stmt) with
|
|
| CompoundStmt (_, stmt_list) ->
|
|
List.last stmt_list |> Option.exists ~f:breaks_control_flow
|
|
| _ ->
|
|
false
|
|
in
|
|
match (stmt : Clang_ast_t.stmt) with
|
|
| CompoundStmt (_, stmt_list) | IfStmt (_, stmt_list, _) ->
|
|
Some
|
|
{ outer_scope= stmt_list
|
|
; breakable_scope= []
|
|
; swallow_destructors= is_compound_stmt_ending_in_control_flow_break stmt }
|
|
| CXXForRangeStmt
|
|
( _
|
|
, [ _init
|
|
(* TODO: ignored here because ignored in [CTrans] *)
|
|
; iterator_decl
|
|
; begin_stmt
|
|
; end_stmt
|
|
; exit_cond
|
|
; increment
|
|
; assign_current_index
|
|
; loop_body ] ) ->
|
|
Some
|
|
{ outer_scope= [iterator_decl; begin_stmt; end_stmt; exit_cond; increment]
|
|
; breakable_scope= [assign_current_index; loop_body]
|
|
; swallow_destructors= false }
|
|
| ObjCForCollectionStmt (_, [item; items; body]) ->
|
|
Some {outer_scope= [item; items]; breakable_scope= [body]; swallow_destructors= false}
|
|
| ForStmt (_stmt_info, [init; decl_stmt; condition; increment; body]) ->
|
|
Some
|
|
{ outer_scope= [init; decl_stmt; condition; increment]
|
|
; breakable_scope= [body]
|
|
; swallow_destructors= false }
|
|
| DoStmt (_, [body; condition]) | WhileStmt (_, [condition; body]) ->
|
|
Some {outer_scope= [condition]; breakable_scope= [body]; swallow_destructors= false}
|
|
| WhileStmt (_, [decls; condition; body]) ->
|
|
Some {outer_scope= [decls; condition]; breakable_scope= [body]; swallow_destructors= false}
|
|
| SwitchStmt (stmt_info, _stmt_list, switch_stmt_info) ->
|
|
let condition =
|
|
CAst_utils.get_stmt_exn switch_stmt_info.Clang_ast_t.ssi_cond
|
|
stmt_info.Clang_ast_t.si_source_range
|
|
in
|
|
let body =
|
|
CAst_utils.get_stmt_exn switch_stmt_info.Clang_ast_t.ssi_body
|
|
stmt_info.Clang_ast_t.si_source_range
|
|
in
|
|
let cond_var = switch_stmt_info.Clang_ast_t.ssi_cond_var in
|
|
Some
|
|
{ outer_scope= condition :: Option.to_list cond_var
|
|
; breakable_scope= [body]
|
|
; swallow_destructors= false }
|
|
| _ ->
|
|
None
|
|
|
|
|
|
let rec visit_stmt stmt ((scope, map) as scope_map) =
|
|
L.debug Capture Verbose "%a{%a}@;"
|
|
(Pp.of_string ~f:Clang_ast_proj.get_stmt_kind_string)
|
|
stmt (Pp.seq ~sep:"," pp_var_decl) scope.current ;
|
|
match (stmt : Clang_ast_t.stmt) with
|
|
| ReturnStmt (stmt_info, _)
|
|
| BreakStmt (stmt_info, _)
|
|
| ContinueStmt (stmt_info, _) (* TODO: GotoStmt *) ->
|
|
let break_until =
|
|
match stmt with Clang_ast_t.ReturnStmt _ -> InitialScope | _ -> Breakable
|
|
in
|
|
let vars_to_destroy = collect_until break_until scope in
|
|
L.debug Capture Verbose "~[%d:%a]" stmt_info.Clang_ast_t.si_pointer
|
|
(Pp.seq ~sep:"," pp_var_decl) vars_to_destroy ;
|
|
let map =
|
|
ClangPointers.Map.set map ~key:stmt_info.Clang_ast_t.si_pointer ~data:vars_to_destroy
|
|
in
|
|
(scope, map)
|
|
| DeclStmt (_, _, decl_list) ->
|
|
let new_vars =
|
|
List.filter decl_list ~f:(function Clang_ast_t.VarDecl _ -> true | _ -> false)
|
|
in
|
|
(* the reverse order is the one we want to destroy the variables in at the end of the scope
|
|
*)
|
|
L.debug Capture Verbose "+%a@," (Pp.seq ~sep:"," pp_var_decl) new_vars ;
|
|
(rev_append new_vars scope, map)
|
|
| _ -> (
|
|
let stmt_info, stmt_list = Clang_ast_proj.get_stmt_tuple stmt in
|
|
match get_scopes stmt with
|
|
| None ->
|
|
visit_stmt_list stmt_list scope_map
|
|
| Some {outer_scope; breakable_scope; swallow_destructors} ->
|
|
with_scope ~inject_destructors:(not swallow_destructors) Compound
|
|
stmt_info.Clang_ast_t.si_pointer scope ~f:(fun scope ->
|
|
let scope_map = visit_stmt_list outer_scope (scope, map) in
|
|
match breakable_scope with
|
|
| [] ->
|
|
scope_map
|
|
| _ :: _ as body ->
|
|
let body_ptr =
|
|
List.last_exn body |> Clang_ast_proj.get_stmt_tuple
|
|
|> function {Clang_ast_t.si_pointer}, _ -> si_pointer
|
|
in
|
|
let scope, map = scope_map in
|
|
with_scope Breakable ~inject_destructors:false body_ptr scope ~f:(fun scope ->
|
|
visit_stmt_list body (scope, map) ) ) )
|
|
|
|
|
|
and with_scope ?(inject_destructors = true) kind pointer scope ~f =
|
|
let vars_to_destroy, scope, map = in_ kind scope ~f:(fun scope -> f scope) in
|
|
let map =
|
|
if inject_destructors then (
|
|
L.debug Capture Verbose "~[%d:%a]" pointer (Pp.seq ~sep:"," pp_var_decl) vars_to_destroy ;
|
|
ClangPointers.Map.set map ~key:pointer ~data:vars_to_destroy )
|
|
else (
|
|
L.debug Capture Verbose "~[%d:skip]" pointer ;
|
|
map )
|
|
in
|
|
(scope, map)
|
|
|
|
|
|
and visit_stmt_list stmt_list scope_map =
|
|
List.fold stmt_list ~init:scope_map ~f:(fun scope_map stmt ->
|
|
L.debug Capture Verbose "@;" ; visit_stmt stmt scope_map )
|
|
|
|
|
|
let empty_scope = {current= []; current_kind= InitialScope; outers= []}
|
|
|
|
let compute_vars_to_destroy_map body =
|
|
visit_stmt body (empty_scope, ClangPointers.Map.empty) |> snd
|
|
end
|
|
|
|
module CXXTemporaries = struct
|
|
let rec visit_stmt_aux context stmt temporaries =
|
|
match (stmt : Clang_ast_t.stmt) with
|
|
| MaterializeTemporaryExpr
|
|
( stmt_info
|
|
, stmt_list
|
|
, expr_info
|
|
, { mtei_decl_ref=
|
|
(* C++ temporaries bound to a const reference see their lifetimes extended to that of
|
|
the reference *)
|
|
None } ) ->
|
|
let pvar, typ = CVar_decl.materialize_cpp_temporary context stmt_info expr_info in
|
|
L.debug Capture Verbose "+%a@," (Pvar.pp Pp.text) pvar ;
|
|
let temporaries = (pvar, typ, expr_info.ei_qual_type) :: temporaries in
|
|
visit_stmt_list context stmt_list temporaries
|
|
| ExprWithCleanups _ ->
|
|
(* huho, we're stepping on someone else's toes (eg, a lambda literal); stop accumulating *)
|
|
temporaries
|
|
| ConditionalOperator _
|
|
| BinaryOperator (_, _, _, {boi_kind= `LAnd | `LOr | `LT | `GT | `LE | `GE | `EQ | `NE}) ->
|
|
(* Do not destroy temporaries created under a conditional operator. This is incorrect but
|
|
better than destroying temporaries that are created in only one branch unconditionally
|
|
after the conditional.
|
|
|
|
Note that destroying the variable inside the branch of the conditional would also be
|
|
incorrect since the conditional operator may be only part of the enclosing full
|
|
expression.
|
|
|
|
Example of tricky case: [foo(x?y:z, w)] or [cond && y] where [y] generates a C++
|
|
temporary. *)
|
|
temporaries
|
|
| LambdaExpr _ ->
|
|
(* do not analyze the code of another function *) temporaries
|
|
| _ ->
|
|
let _, stmt_list = Clang_ast_proj.get_stmt_tuple stmt in
|
|
visit_stmt_list context stmt_list temporaries
|
|
|
|
|
|
and visit_stmt context stmt temporaries =
|
|
L.debug Capture Verbose "<@[<hv2>%a|@,"
|
|
(Pp.of_string ~f:Clang_ast_proj.get_stmt_kind_string)
|
|
stmt ;
|
|
let r = visit_stmt_aux context stmt temporaries in
|
|
L.debug Capture Verbose "@]@;/%a>" (Pp.of_string ~f:Clang_ast_proj.get_stmt_kind_string) stmt ;
|
|
r
|
|
|
|
|
|
and visit_stmt_list context stmt_list temporaries =
|
|
List.fold stmt_list ~init:temporaries ~f:(fun temporaries stmt ->
|
|
L.debug Capture Verbose "@;" ;
|
|
visit_stmt context stmt temporaries )
|
|
|
|
|
|
let get_destroyable_temporaries context stmt_list = visit_stmt_list context stmt_list []
|
|
end
|