[nullsafe][refactor] Restructure code around node typechecking

Summary:
`TypeCheck.typecheck_node` is one of central parts in nullsafe
typechecking.

Lets make the code clearer:
1. Clear contract of the method - return a named record instead of a
tuple of lists.
2. Comments.
3. Making code functional, use List.fold and aggregate all needed data
there instead of storing information in references
4. Helper functions.

Reviewed By: skcho

Differential Revision: D20246273

fbshipit-source-id: 75f56497e
master
Mitya Lyubarskiy 5 years ago committed by Facebook Github Bot
parent 6c01157cd4
commit d71e2f0d84

@ -95,11 +95,14 @@ module MkCallback (Extension : ExtensionT) : CallBackT = struct
NodePrinter.with_session ~pp_name node ~f:(fun () -> NodePrinter.with_session ~pp_name node ~f:(fun () ->
State.set_node node ; State.set_node node ;
if Config.write_html then L.d_printfln "before:@\n%a@\n" TypeState.pp typestate ; if Config.write_html then L.d_printfln "before:@\n%a@\n" TypeState.pp typestate ;
let typestates_succ, typestates_exn = let TypeCheck.{normal_flow_typestate; exception_flow_typestates} =
TypeCheck.typecheck_node tenv calls_this checks idenv curr_pname curr_pdesc TypeCheck.typecheck_node tenv calls_this checks idenv curr_pname curr_pdesc
find_canonical_duplicate annotated_signature typestate node linereader find_canonical_duplicate annotated_signature typestate node linereader
in in
(typestates_succ, typestates_exn) ) let normal_flow_typestates =
Option.value_map normal_flow_typestate ~f:(fun a -> [a]) ~default:[]
in
(normal_flow_typestates, exception_flow_typestates) )
let proc_throws _ = DontKnow let proc_throws _ = DontKnow

@ -10,7 +10,8 @@ module F = Format
module L = Logging module L = Logging
module DExp = DecompiledExp module DExp = DecompiledExp
(** Module for type checking. *) type typecheck_result =
{normal_flow_typestate: TypeState.t option; exception_flow_typestates: TypeState.t list}
(** Module to treat selected complex expressions as constants. *) (** Module to treat selected complex expressions as constants. *)
module ComplexExpressions = struct module ComplexExpressions = struct
@ -1176,61 +1177,79 @@ let typecheck_instr tenv calls_this checks (node : Procdesc.Node.t) idenv curr_p
~nullsafe_mode ~node:node' ~original_node:node normalized_cond ~nullsafe_mode ~node:node' ~original_node:node normalized_cond
let can_instrunction_throw tenv node instr =
match instr with
| Sil.Call (_, Exp.Const (Const.Cfun callee_pname), _, _, _) -> (
let callee_attributes_opt = PatternMatch.lookup_attributes tenv callee_pname in
(* We assume if the function is not annotated with throws(), it can not throw an exception.
This is unsound.
TODO(T63305137) nullsafe should assume all methods can throw.
*)
match callee_attributes_opt with
| Some callee_attributes ->
not (List.is_empty callee_attributes.ProcAttributes.exceptions)
| None ->
false )
| Sil.Store {e1= Exp.Lvar pv}
when Pvar.is_return pv
&& Procdesc.Node.equal_nodekind (Procdesc.Node.get_kind node) Procdesc.Node.throw_kind ->
(* Explicit throw instruction *)
true
| _ ->
false
(* true if after this instruction the program interrupts *)
let is_noreturn_instruction = function
| Sil.Call (_, Exp.Const (Const.Cfun callee_pname), _, _, _) when Models.is_noreturn callee_pname
->
true
| _ ->
false
(** Typecheck the instructions in a cfg node. *) (** Typecheck the instructions in a cfg node. *)
let typecheck_node tenv calls_this checks idenv curr_pname curr_pdesc find_canonical_duplicate let typecheck_node tenv calls_this checks idenv curr_pname curr_pdesc find_canonical_duplicate
annotated_signature typestate node linereader = annotated_signature typestate node linereader =
let instrs = Procdesc.Node.get_instrs node in if Procdesc.Node.equal_nodekind (Procdesc.Node.get_kind node) Procdesc.Node.exn_sink_kind then
let instr_ref_gen = TypeErr.InstrRef.create_generator node in {normal_flow_typestate= None; exception_flow_typestates= []}
let typestates_exn = ref [] in else
let noreturn = ref false in let instrs = Procdesc.Node.get_instrs node in
let handle_exceptions typestate instr = let instr_ref_gen = TypeErr.InstrRef.create_generator node in
match instr with let canonical_node = find_canonical_duplicate node in
| Sil.Call (_, Exp.Const (Const.Cfun callee_pname), _, _, _) (* typecheck the instruction and accumulate result *)
when Models.is_noreturn callee_pname -> let fold_instruction
noreturn := true ( { normal_flow_typestate= normal_typestate_prev_opt
| Sil.Call (_, Exp.Const (Const.Cfun callee_pname), _, _, _) -> ; exception_flow_typestates= exception_flow_typestates_prev } as prev_result ) instr =
let callee_attributes_opt = PatternMatch.lookup_attributes tenv callee_pname in match normal_typestate_prev_opt with
(* check if the call might throw an exception *) | None ->
let has_exceptions = (* no input typestate - abort typechecking and propagate the current result *)
match callee_attributes_opt with prev_result
| Some callee_attributes -> | Some normal_flow_typestate_prev ->
not (List.is_empty callee_attributes.ProcAttributes.exceptions) let instr_ref =
| None -> (* keep unique instruction reference per-node *)
false TypeErr.InstrRef.gen instr_ref_gen
in in
if has_exceptions then typestates_exn := typestate :: !typestates_exn let normal_flow_typestate =
| Sil.Store {e1= Exp.Lvar pv} typecheck_instr tenv calls_this checks node idenv curr_pname curr_pdesc
when Pvar.is_return pv find_canonical_duplicate annotated_signature instr_ref linereader
&& Procdesc.Node.equal_nodekind (Procdesc.Node.get_kind node) Procdesc.Node.throw_kind -> normal_flow_typestate_prev instr
(* throw instruction *) in
typestates_exn := typestate :: !typestates_exn if is_noreturn_instruction instr then {prev_result with normal_flow_typestate= None}
| _ -> else
() let exception_flow_typestates =
in if can_instrunction_throw tenv node instr then
let canonical_node = find_canonical_duplicate node in (* add the typestate after this instruction to the list of exception typestates *)
let do_instruction typestate instr = normal_flow_typestate :: exception_flow_typestates_prev
let instr_ref = else exception_flow_typestates_prev
(* keep unique instruction reference per-node *) in
TypeErr.InstrRef.gen instr_ref_gen if Config.write_html then (
in L.d_printfln "instr: %a@\n" (Sil.pp_instr ~print_types:true Pp.text) instr ;
let post = L.d_printfln "new state:@\n%a@\n" TypeState.pp normal_flow_typestate ) ;
typecheck_instr tenv calls_this checks node idenv curr_pname curr_pdesc {normal_flow_typestate= Some normal_flow_typestate; exception_flow_typestates}
find_canonical_duplicate annotated_signature instr_ref linereader typestate instr
in in
if Config.write_html then ( (* Reset 'always' field for forall errors to false. *)
L.d_printfln "instr: %a@\n" (Sil.pp_instr ~print_types:true Pp.text) instr ; (* This is used to track if it is set to true for all visit to the node. *)
L.d_printfln "new state:@\n%a@\n" TypeState.pp post ) ; TypeErr.node_reset_forall canonical_node ;
handle_exceptions typestate instr ; Instrs.fold instrs ~f:fold_instruction
post ~init:{normal_flow_typestate= Some typestate; exception_flow_typestates= []}
in
(* Reset 'always' field for forall errors to false. *)
(* This is used to track if it is set to true for all visit to the node. *)
TypeErr.node_reset_forall canonical_node ;
let typestate_succ = Instrs.fold ~f:do_instruction ~init:typestate instrs in
let dont_propagate =
Procdesc.Node.equal_nodekind (Procdesc.Node.get_kind node) Procdesc.Node.exn_sink_kind
(* don't propagate exceptions *)
|| !noreturn
in
if dont_propagate then ([], []) (* don't propagate to exit node *)
else ([typestate_succ], !typestates_exn)

@ -15,6 +15,16 @@ type find_canonical_duplicate = Procdesc.Node.t -> Procdesc.Node.t
type checks = {eradicate: bool; check_ret_type: check_return_type list} type checks = {eradicate: bool; check_ret_type: check_return_type list}
type typecheck_result =
{ normal_flow_typestate: TypeState.t option
(** Typestate at the exit of the node. [None] if node is determined dead end (e.g. noreturn
function). Will be passed to all output nodes of the current node. *)
; exception_flow_typestates: TypeState.t list
(** If an exception might be thrown after this node, this list should contain all possible
states at which the exception can be thrown. (Can be several states because different
instructions in the single node can potentially throw). These typestates (joined
together) will be passed to all "exception output" nodes of the current node. *) }
val typecheck_node : val typecheck_node :
Tenv.t Tenv.t
-> bool ref -> bool ref
@ -27,4 +37,5 @@ val typecheck_node :
-> TypeState.t -> TypeState.t
-> Procdesc.Node.t -> Procdesc.Node.t
-> Printer.LineReader.t -> Printer.LineReader.t
-> TypeState.t list * TypeState.t list -> typecheck_result
(** Main entry point. Typecheck the CFG node given input typestate, and report issues, if found *)

Loading…
Cancel
Save