(* * 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 (* Boilerplate to write/read our summaries alongside the summaries of other analyzers *) module Payload = SummaryPayload.Make (struct type t = ResourceLeakDomain.t let field = Payloads.Fields.lab_resource_leaks end) module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = ResourceLeakDomain type extras = unit let is_closeable_typename tenv typename = let is_closable_interface typename _ = match Typ.Name.name typename with | "java.io.AutoCloseable" | "java.io.Closeable" -> true | _ -> false in PatternMatch.supertype_exists tenv is_closable_interface typename let is_closeable_procname tenv procname = match procname with | Procname.Java java_procname -> is_closeable_typename tenv (Procname.Java.get_class_type_name java_procname) | _ -> false let acquires_resource tenv procname = (* We assume all constructors of a subclass of Closeable acquire a resource *) Procname.is_constructor procname && is_closeable_procname tenv procname let releases_resource tenv procname = (* We assume the close method of a Closeable releases all of its resources *) String.equal "close" (Procname.get_method procname) && is_closeable_procname tenv procname (** Take an abstract state and instruction, produce a new abstract state *) let exec_instr (astate : ResourceLeakDomain.t) {ProcData.pdesc; tenv} _ (instr : HilInstr.t) = match instr with | Call (_return_opt, Direct callee_procname, _actuals, _, _loc) -> ( if (* function call [return_opt] := invoke [callee_procname]([actuals]) *) acquires_resource tenv callee_procname then ResourceLeakDomain.acquire_resource astate else if releases_resource tenv callee_procname then ResourceLeakDomain.release_resource astate else match Payload.read pdesc callee_procname with | Some summary -> (* interprocedural analysis produced a summary: use it *) ResourceLeakDomain.apply_summary ~summary astate | None -> (* No summary for [callee_procname]; it's native code or missing for some reason *) astate ) | Assign (Base (ret, {Typ.desc= Typ.Tptr ({Typ.desc= Typ.Tstruct ret_typename}, _)}), _rhs_exp, _loc) when Var.is_return ret && is_closeable_typename tenv ret_typename -> (* return a resource *) ResourceLeakDomain.record_return_resource astate |> ResourceLeakDomain.release_resource | Assign (_lhs_access_path, _rhs_exp, _loc) -> (* an assignment [lhs_access_path] := [rhs_exp] *) astate | Assume (_assume_exp, _, _, _loc) -> (* a conditional assume([assume_exp]). blocks if [assume_exp] evaluates to false *) astate | Call (_, Indirect _, _, _, _) -> (* This should never happen in Java. Fail if it does. *) L.(die InternalError) "Unexpected indirect call %a" HilInstr.pp instr | ExitScope _ -> astate let pp_session_name _node fmt = F.pp_print_string fmt "resource leaks lab" end (** 5(a) Type of CFG to analyze--Exceptional to follow exceptional control-flow edges, Normal to ignore them *) module CFG = ProcCfg.Normal (* Create an intraprocedural abstract interpreter from the transfer functions we defined *) module Analyzer = LowerHil.MakeAbstractInterpreter (TransferFunctions (CFG)) (** Report an error when we have acquired more resources than we have released *) let report_if_leak post summary (proc_data : unit ProcData.t) = if ResourceLeakDomain.has_leak post then let last_loc = Procdesc.Node.get_loc (Procdesc.get_exit_node proc_data.pdesc) in let message = F.asprintf "Leaked %a resource(s)" ResourceLeakDomain.pp post in Reporting.log_error summary ~loc:last_loc IssueType.resource_leak message (* Callback for invoking the checker from the outside--registered in RegisterCheckers *) let checker {Callbacks.summary; proc_desc; tenv} : Summary.t = let proc_data = ProcData.make proc_desc tenv () in match Analyzer.compute_post proc_data ~initial:ResourceLeakDomain.initial with | Some post -> report_if_leak post summary proc_data ; Payload.update_summary post summary | None -> L.(die InternalError) "Analyzer failed to compute post for %a" Procname.pp (Procdesc.get_proc_name proc_data.pdesc)