Summary: Publish solutions to the lab, and a Docker file and image to get started more quickly with infer hacking. Reviewed By: mbouaziz Differential Revision: D13648847 fbshipit-source-id: daf48ad03master
parent
9e91c9298b
commit
16c0c03050
@ -0,0 +1,65 @@
|
|||||||
|
FROM debian:stretch-slim
|
||||||
|
|
||||||
|
LABEL maintainer "Infer team"
|
||||||
|
|
||||||
|
# mkdir the man/man1 directory due to Debian bug #863199
|
||||||
|
RUN apt-get update && \
|
||||||
|
mkdir -p /usr/share/man/man1 && \
|
||||||
|
apt-get install --yes --no-install-recommends \
|
||||||
|
autoconf \
|
||||||
|
automake \
|
||||||
|
bzip2 \
|
||||||
|
cmake \
|
||||||
|
curl \
|
||||||
|
gcc \
|
||||||
|
git \
|
||||||
|
libc6-dev \
|
||||||
|
libgmp-dev \
|
||||||
|
libmpfr-dev \
|
||||||
|
libsqlite3-dev \
|
||||||
|
make \
|
||||||
|
openjdk-8-jdk-headless \
|
||||||
|
patch \
|
||||||
|
pkg-config \
|
||||||
|
python2.7 \
|
||||||
|
unzip \
|
||||||
|
zlib1g-dev && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Some scripts in facebook-clang-plugins assume "python" is available
|
||||||
|
RUN cd /usr/local/bin && ln -s /usr/bin/python2.7 python
|
||||||
|
|
||||||
|
# Install opam 2
|
||||||
|
RUN curl -sL https://github.com/ocaml/opam/releases/download/2.0.2/opam-2.0.2-x86_64-linux > /usr/bin/opam && \
|
||||||
|
chmod +x /usr/bin/opam
|
||||||
|
|
||||||
|
# Disable sandboxing
|
||||||
|
# Without this opam fails to compile OCaml for some reason. We don't need sandboxing inside a Docker container anyway.
|
||||||
|
RUN opam init --reinit --bare --disable-sandboxing
|
||||||
|
|
||||||
|
# Download the latest Infer master
|
||||||
|
RUN cd / && \
|
||||||
|
git clone https://github.com/facebook/infer/
|
||||||
|
|
||||||
|
# Build opam deps first, then infer. This way if any step fails we
|
||||||
|
# don't lose the significant amount of work done in the previous
|
||||||
|
# steps.
|
||||||
|
RUN cd /infer && \
|
||||||
|
INFER_OPAM_SWITCH=4.07.1 ./build-infer.sh --only-setup-opam --no-opam-lock java && \
|
||||||
|
opam clean
|
||||||
|
|
||||||
|
# Make sure clang is disabled
|
||||||
|
RUN eval $(opam env) && \
|
||||||
|
cd /infer && \
|
||||||
|
SKIP_SUBMODULES=true ./autogen.sh && \
|
||||||
|
./configure --disable-c-analyzers
|
||||||
|
|
||||||
|
# Install Infer
|
||||||
|
ENV INFER_HOME /infer/infer
|
||||||
|
ENV PATH ${INFER_HOME}/bin:${PATH}
|
||||||
|
|
||||||
|
# build in non-optimized mode by default to speed up build times
|
||||||
|
ENV BUILD_MODE=default
|
||||||
|
|
||||||
|
# prevent exiting by compulsively hitting Control-D
|
||||||
|
ENV IGNOREEOF=9
|
@ -0,0 +1,73 @@
|
|||||||
|
FROM debian:stretch-slim
|
||||||
|
|
||||||
|
LABEL maintainer "Infer team"
|
||||||
|
|
||||||
|
# mkdir the man/man1 directory due to Debian bug #863199
|
||||||
|
RUN apt-get update && \
|
||||||
|
mkdir -p /usr/share/man/man1 && \
|
||||||
|
apt-get install --yes --no-install-recommends \
|
||||||
|
autoconf \
|
||||||
|
automake \
|
||||||
|
bubblewrap \
|
||||||
|
bzip2 \
|
||||||
|
cmake \
|
||||||
|
curl \
|
||||||
|
g++ \
|
||||||
|
gcc \
|
||||||
|
git \
|
||||||
|
libc6-dev \
|
||||||
|
libgmp-dev \
|
||||||
|
libmpfr-dev \
|
||||||
|
libsqlite3-dev \
|
||||||
|
make \
|
||||||
|
openjdk-8-jdk-headless \
|
||||||
|
patch \
|
||||||
|
pkg-config \
|
||||||
|
python2.7 \
|
||||||
|
unzip \
|
||||||
|
xz-utils \
|
||||||
|
zlib1g-dev && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Some scripts in facebook-clang-plugins assume "python" is available
|
||||||
|
RUN cd /usr/local/bin && ln -s /usr/bin/python2.7 python
|
||||||
|
|
||||||
|
# Install opam 2
|
||||||
|
RUN curl -sL https://github.com/ocaml/opam/releases/download/2.0.2/opam-2.0.2-x86_64-linux > /usr/bin/opam && \
|
||||||
|
chmod +x /usr/bin/opam
|
||||||
|
|
||||||
|
# Disable sandboxing
|
||||||
|
# Without this opam fails to compile OCaml for some reason. We don't need sandboxing inside a Docker container anyway.
|
||||||
|
RUN opam init --reinit --bare --disable-sandboxing
|
||||||
|
|
||||||
|
# Download the latest Infer master
|
||||||
|
RUN cd / && \
|
||||||
|
git clone --recurse-submodules https://github.com/facebook/infer/
|
||||||
|
|
||||||
|
# Build opam deps first, then clang, then infer. This way if any step
|
||||||
|
# fails we don't lose the significant amount of work done in the
|
||||||
|
# previous steps.
|
||||||
|
RUN cd /infer && ./build-infer.sh --only-setup-opam
|
||||||
|
RUN cd /infer && \
|
||||||
|
eval $(opam env) && \
|
||||||
|
./autogen.sh && \
|
||||||
|
./configure && \
|
||||||
|
./facebook-clang-plugins/clang/setup.sh
|
||||||
|
|
||||||
|
# Hackish for now: pull to get the latest version
|
||||||
|
RUN cd /infer && git pull
|
||||||
|
|
||||||
|
# if called with /infer-host mounted then copy infer there
|
||||||
|
RUN if test -d /infer-host; then \
|
||||||
|
cp -av /infer/. /infer-host; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install Infer
|
||||||
|
ENV INFER_HOME /infer/infer
|
||||||
|
ENV PATH ${INFER_HOME}/bin:${PATH}
|
||||||
|
|
||||||
|
# build in non-optimized mode by default to speed up build times
|
||||||
|
ENV BUILD_MODE=default
|
||||||
|
|
||||||
|
# prevent exiting by compulsively hitting Control-D
|
||||||
|
ENV IGNOREEOF=9
|
@ -0,0 +1,23 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
type t = unit
|
||||||
|
|
||||||
|
let ( <= ) ~lhs:_ ~rhs:_ = assert false
|
||||||
|
|
||||||
|
let join _a _b = assert false
|
||||||
|
|
||||||
|
let widen ~prev:_ ~next:_ ~num_iters:_ = assert false
|
||||||
|
|
||||||
|
let pp fmt () = F.fprintf fmt "(nothing)"
|
||||||
|
|
||||||
|
let initial = ()
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,14 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
include AbstractDomain.S
|
||||||
|
|
||||||
|
val initial : t
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,102 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
(* Boilerplate to write/read our summaries alongside the summaries of other analyzers *)
|
||||||
|
module Payload = SummaryPayload.Make (struct
|
||||||
|
type t = ResourceLeakDomain.t
|
||||||
|
|
||||||
|
let update_payloads resources_payload (payloads : Payloads.t) =
|
||||||
|
{payloads with lab_resource_leaks= Some resources_payload}
|
||||||
|
|
||||||
|
|
||||||
|
let of_payloads {Payloads.lab_resource_leaks} = 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
|
||||||
|
| Typ.Procname.Java java_procname ->
|
||||||
|
is_closeable_typename tenv
|
||||||
|
(Typ.Name.Java.from_string (Typ.Procname.Java.get_class_name java_procname))
|
||||||
|
| _ ->
|
||||||
|
false
|
||||||
|
|
||||||
|
|
||||||
|
let _acquires_resource tenv procname =
|
||||||
|
(* We assume all constructors of a subclass of Closeable acquire a resource *)
|
||||||
|
Typ.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" (Typ.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) ->
|
||||||
|
(* function call [return_opt] := invoke [callee_procname]([actuals]) *)
|
||||||
|
astate
|
||||||
|
| 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"
|
||||||
|
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) = ()
|
||||||
|
|
||||||
|
(* 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" Typ.Procname.pp
|
||||||
|
(Procdesc.get_proc_name proc_data.pdesc)
|
@ -0,0 +1,10 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
val checker : Callbacks.proc_callback_t
|
@ -0,0 +1,29 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
type t = int
|
||||||
|
|
||||||
|
let ( <= ) ~lhs ~rhs = lhs <= rhs
|
||||||
|
|
||||||
|
let join _a _b = assert false
|
||||||
|
|
||||||
|
let widen ~prev:_ ~next:_ ~num_iters:_ = assert false
|
||||||
|
|
||||||
|
let pp fmt astate = F.fprintf fmt "%d" astate
|
||||||
|
|
||||||
|
let initial = 0
|
||||||
|
|
||||||
|
let acquire_resource held = held + 1
|
||||||
|
|
||||||
|
let release_resource held = held - 1
|
||||||
|
|
||||||
|
let has_leak held = held > 0
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,20 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
include AbstractDomain.S
|
||||||
|
|
||||||
|
val initial : t
|
||||||
|
|
||||||
|
val acquire_resource : t -> t
|
||||||
|
|
||||||
|
val release_resource : t -> t
|
||||||
|
|
||||||
|
val has_leak : t -> bool
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,109 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
(* Boilerplate to write/read our summaries alongside the summaries of other analyzers *)
|
||||||
|
module Payload = SummaryPayload.Make (struct
|
||||||
|
type t = ResourceLeakDomain.t
|
||||||
|
|
||||||
|
let update_payloads resources_payload (payloads : Payloads.t) =
|
||||||
|
{payloads with lab_resource_leaks= Some resources_payload}
|
||||||
|
|
||||||
|
|
||||||
|
let of_payloads {Payloads.lab_resource_leaks} = 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
|
||||||
|
| Typ.Procname.Java java_procname ->
|
||||||
|
is_closeable_typename tenv
|
||||||
|
(Typ.Name.Java.from_string (Typ.Procname.Java.get_class_name java_procname))
|
||||||
|
| _ ->
|
||||||
|
false
|
||||||
|
|
||||||
|
|
||||||
|
let acquires_resource tenv procname =
|
||||||
|
(* We assume all constructors of a subclass of Closeable acquire a resource *)
|
||||||
|
Typ.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" (Typ.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) ->
|
||||||
|
(* function call [return_opt] := invoke [callee_procname]([actuals]) *)
|
||||||
|
if acquires_resource tenv callee_procname then ResourceLeakDomain.acquire_resource astate
|
||||||
|
else if releases_resource tenv callee_procname then
|
||||||
|
ResourceLeakDomain.release_resource astate
|
||||||
|
else astate
|
||||||
|
| 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"
|
||||||
|
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" Typ.Procname.pp
|
||||||
|
(Procdesc.get_proc_name proc_data.pdesc)
|
@ -0,0 +1,10 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
val checker : Callbacks.proc_callback_t
|
@ -0,0 +1,29 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
type t = int
|
||||||
|
|
||||||
|
let ( <= ) ~lhs ~rhs = lhs <= rhs
|
||||||
|
|
||||||
|
let join a b = max a b
|
||||||
|
|
||||||
|
let widen ~prev:_ ~next:_ ~num_iters:_ = assert false
|
||||||
|
|
||||||
|
let pp fmt astate = F.fprintf fmt "%d" astate
|
||||||
|
|
||||||
|
let initial = 0
|
||||||
|
|
||||||
|
let acquire_resource held = held + 1
|
||||||
|
|
||||||
|
let release_resource held = held - 1
|
||||||
|
|
||||||
|
let has_leak held = held > 0
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,20 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
include AbstractDomain.S
|
||||||
|
|
||||||
|
val initial : t
|
||||||
|
|
||||||
|
val acquire_resource : t -> t
|
||||||
|
|
||||||
|
val release_resource : t -> t
|
||||||
|
|
||||||
|
val has_leak : t -> bool
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,109 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
(* Boilerplate to write/read our summaries alongside the summaries of other analyzers *)
|
||||||
|
module Payload = SummaryPayload.Make (struct
|
||||||
|
type t = ResourceLeakDomain.t
|
||||||
|
|
||||||
|
let update_payloads resources_payload (payloads : Payloads.t) =
|
||||||
|
{payloads with lab_resource_leaks= Some resources_payload}
|
||||||
|
|
||||||
|
|
||||||
|
let of_payloads {Payloads.lab_resource_leaks} = 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
|
||||||
|
| Typ.Procname.Java java_procname ->
|
||||||
|
is_closeable_typename tenv
|
||||||
|
(Typ.Name.Java.from_string (Typ.Procname.Java.get_class_name java_procname))
|
||||||
|
| _ ->
|
||||||
|
false
|
||||||
|
|
||||||
|
|
||||||
|
let acquires_resource tenv procname =
|
||||||
|
(* We assume all constructors of a subclass of Closeable acquire a resource *)
|
||||||
|
Typ.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" (Typ.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) ->
|
||||||
|
(* function call [return_opt] := invoke [callee_procname]([actuals]) *)
|
||||||
|
if acquires_resource tenv callee_procname then ResourceLeakDomain.acquire_resource astate
|
||||||
|
else if releases_resource tenv callee_procname then
|
||||||
|
ResourceLeakDomain.release_resource astate
|
||||||
|
else astate
|
||||||
|
| 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"
|
||||||
|
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" Typ.Procname.pp
|
||||||
|
(Procdesc.get_proc_name proc_data.pdesc)
|
@ -0,0 +1,10 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
val checker : Callbacks.proc_callback_t
|
@ -0,0 +1,53 @@
|
|||||||
|
(*
|
||||||
|
* 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 FiniteBounds = struct
|
||||||
|
type t = int
|
||||||
|
|
||||||
|
let ( <= ) ~lhs ~rhs = lhs <= rhs
|
||||||
|
|
||||||
|
let join a b = max a b
|
||||||
|
|
||||||
|
let widen ~prev ~next ~num_iters:_ = join prev next
|
||||||
|
|
||||||
|
let pp fmt astate = F.fprintf fmt "%d" astate
|
||||||
|
end
|
||||||
|
|
||||||
|
include AbstractDomain.TopLifted (FiniteBounds)
|
||||||
|
open AbstractDomain.Types
|
||||||
|
|
||||||
|
let widening_threshold = 5
|
||||||
|
|
||||||
|
let widen ~prev ~next ~num_iters =
|
||||||
|
match (prev, next) with
|
||||||
|
| Top, _ | _, Top ->
|
||||||
|
Top
|
||||||
|
| NonTop prev, NonTop next when num_iters < widening_threshold ->
|
||||||
|
NonTop (FiniteBounds.join prev next)
|
||||||
|
| NonTop _, NonTop _ (* num_iters >= widening_threshold *) ->
|
||||||
|
Top
|
||||||
|
|
||||||
|
|
||||||
|
let initial = NonTop 0
|
||||||
|
|
||||||
|
let acquire_resource = function Top -> Top | NonTop held -> NonTop (held + 1)
|
||||||
|
|
||||||
|
let release_resource = function Top -> Top | NonTop held -> NonTop (held - 1)
|
||||||
|
|
||||||
|
let has_leak = function
|
||||||
|
| Top ->
|
||||||
|
(* UNSOUND but likely that the analyzer got confused *) false
|
||||||
|
| NonTop x when x > 0 ->
|
||||||
|
true
|
||||||
|
| NonTop _ ->
|
||||||
|
false
|
||||||
|
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,20 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
include AbstractDomain.S
|
||||||
|
|
||||||
|
val initial : t
|
||||||
|
|
||||||
|
val acquire_resource : t -> t
|
||||||
|
|
||||||
|
val release_resource : t -> t
|
||||||
|
|
||||||
|
val has_leak : t -> bool
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,109 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
(* Boilerplate to write/read our summaries alongside the summaries of other analyzers *)
|
||||||
|
module Payload = SummaryPayload.Make (struct
|
||||||
|
type t = ResourceLeakDomain.t
|
||||||
|
|
||||||
|
let update_payloads resources_payload (payloads : Payloads.t) =
|
||||||
|
{payloads with lab_resource_leaks= Some resources_payload}
|
||||||
|
|
||||||
|
|
||||||
|
let of_payloads {Payloads.lab_resource_leaks} = 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
|
||||||
|
| Typ.Procname.Java java_procname ->
|
||||||
|
is_closeable_typename tenv
|
||||||
|
(Typ.Name.Java.from_string (Typ.Procname.Java.get_class_name java_procname))
|
||||||
|
| _ ->
|
||||||
|
false
|
||||||
|
|
||||||
|
|
||||||
|
let acquires_resource tenv procname =
|
||||||
|
(* We assume all constructors of a subclass of Closeable acquire a resource *)
|
||||||
|
Typ.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" (Typ.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) ->
|
||||||
|
(* function call [return_opt] := invoke [callee_procname]([actuals]) *)
|
||||||
|
if acquires_resource tenv callee_procname then ResourceLeakDomain.acquire_resource astate
|
||||||
|
else if releases_resource tenv callee_procname then
|
||||||
|
ResourceLeakDomain.release_resource astate
|
||||||
|
else astate
|
||||||
|
| 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"
|
||||||
|
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" Typ.Procname.pp
|
||||||
|
(Procdesc.get_proc_name proc_data.pdesc)
|
@ -0,0 +1,10 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
val checker : Callbacks.proc_callback_t
|
@ -0,0 +1,86 @@
|
|||||||
|
(*
|
||||||
|
* 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 FiniteBounds = struct
|
||||||
|
type t = int
|
||||||
|
|
||||||
|
let ( <= ) ~lhs ~rhs = lhs <= rhs
|
||||||
|
|
||||||
|
let join a b = max a b
|
||||||
|
|
||||||
|
let widen ~prev ~next ~num_iters:_ = join prev next
|
||||||
|
|
||||||
|
let pp fmt astate = F.fprintf fmt "%d" astate
|
||||||
|
end
|
||||||
|
|
||||||
|
module BoundsWithTop = struct
|
||||||
|
open AbstractDomain.Types
|
||||||
|
include AbstractDomain.TopLifted (FiniteBounds)
|
||||||
|
|
||||||
|
let widening_threshold = 5
|
||||||
|
|
||||||
|
let widen ~prev ~next ~num_iters =
|
||||||
|
match (prev, next) with
|
||||||
|
| Top, _ | _, Top ->
|
||||||
|
Top
|
||||||
|
| NonTop prev, NonTop next when num_iters < widening_threshold ->
|
||||||
|
NonTop (FiniteBounds.join prev next)
|
||||||
|
| NonTop _, NonTop _ (* num_iters >= widening_threshold *) ->
|
||||||
|
Top
|
||||||
|
end
|
||||||
|
|
||||||
|
module ReturnsResource = AbstractDomain.BooleanOr
|
||||||
|
include AbstractDomain.Pair (BoundsWithTop) (ReturnsResource)
|
||||||
|
open AbstractDomain.Types
|
||||||
|
|
||||||
|
let initial = (NonTop 0, false)
|
||||||
|
|
||||||
|
let acquire_resource = function
|
||||||
|
| (Top, _) as astate ->
|
||||||
|
astate
|
||||||
|
| NonTop held, returns_resource ->
|
||||||
|
(NonTop (held + 1), returns_resource)
|
||||||
|
|
||||||
|
|
||||||
|
let release_resource = function
|
||||||
|
| (Top, _) as astate ->
|
||||||
|
astate
|
||||||
|
| NonTop held, returns_resource ->
|
||||||
|
(NonTop (held - 1), returns_resource)
|
||||||
|
|
||||||
|
|
||||||
|
let has_leak = function
|
||||||
|
| Top, _ ->
|
||||||
|
(* UNSOUND but likely that the analyzer got confused *) false
|
||||||
|
| NonTop x, _ when x > 0 ->
|
||||||
|
true
|
||||||
|
| NonTop _, _ ->
|
||||||
|
false
|
||||||
|
|
||||||
|
|
||||||
|
let apply_summary ~summary:(summary_count, summary_returns_resource)
|
||||||
|
(current_count, current_returns_resource) =
|
||||||
|
let new_count =
|
||||||
|
match current_count with
|
||||||
|
| Top ->
|
||||||
|
Top
|
||||||
|
| NonTop current_count ->
|
||||||
|
let return_count = if summary_returns_resource then 1 else 0 in
|
||||||
|
let summary_count =
|
||||||
|
match summary_count with Top -> (* confusion => ignore *) 0 | NonTop count -> count
|
||||||
|
in
|
||||||
|
NonTop (current_count + summary_count + return_count)
|
||||||
|
in
|
||||||
|
(new_count, current_returns_resource)
|
||||||
|
|
||||||
|
|
||||||
|
let record_return_resource (count, _) = (count, true)
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,24 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
include AbstractDomain.S
|
||||||
|
|
||||||
|
val initial : t
|
||||||
|
|
||||||
|
val acquire_resource : t -> t
|
||||||
|
|
||||||
|
val release_resource : t -> t
|
||||||
|
|
||||||
|
val has_leak : t -> bool
|
||||||
|
|
||||||
|
val apply_summary : summary:t -> t -> t
|
||||||
|
|
||||||
|
val record_return_resource : t -> t
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,123 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
(* Boilerplate to write/read our summaries alongside the summaries of other analyzers *)
|
||||||
|
module Payload = SummaryPayload.Make (struct
|
||||||
|
type t = ResourceLeakDomain.t
|
||||||
|
|
||||||
|
let update_payloads resources_payload (payloads : Payloads.t) =
|
||||||
|
{payloads with lab_resource_leaks= Some resources_payload}
|
||||||
|
|
||||||
|
|
||||||
|
let of_payloads {Payloads.lab_resource_leaks} = 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
|
||||||
|
| Typ.Procname.Java java_procname ->
|
||||||
|
is_closeable_typename tenv
|
||||||
|
(Typ.Name.Java.from_string (Typ.Procname.Java.get_class_name java_procname))
|
||||||
|
| _ ->
|
||||||
|
false
|
||||||
|
|
||||||
|
|
||||||
|
let acquires_resource tenv procname =
|
||||||
|
(* We assume all constructors of a subclass of Closeable acquire a resource *)
|
||||||
|
Typ.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" (Typ.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" Typ.Procname.pp
|
||||||
|
(Procdesc.get_proc_name proc_data.pdesc)
|
@ -0,0 +1,10 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
val checker : Callbacks.proc_callback_t
|
@ -0,0 +1,179 @@
|
|||||||
|
(*
|
||||||
|
* 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 FiniteBounds = struct
|
||||||
|
type t = int
|
||||||
|
|
||||||
|
let ( <= ) ~lhs ~rhs = lhs <= rhs
|
||||||
|
|
||||||
|
let join a b = max a b
|
||||||
|
|
||||||
|
let widen ~prev ~next ~num_iters:_ = join prev next
|
||||||
|
|
||||||
|
let pp fmt astate = F.fprintf fmt "%d" astate
|
||||||
|
end
|
||||||
|
|
||||||
|
module BoundsWithTop = struct
|
||||||
|
open AbstractDomain.Types
|
||||||
|
include AbstractDomain.TopLifted (FiniteBounds)
|
||||||
|
|
||||||
|
let widening_threshold = 5
|
||||||
|
|
||||||
|
let widen ~prev ~next ~num_iters =
|
||||||
|
match (prev, next) with
|
||||||
|
| Top, _ | _, Top ->
|
||||||
|
Top
|
||||||
|
| NonTop prev, NonTop next when num_iters < widening_threshold ->
|
||||||
|
NonTop (FiniteBounds.join prev next)
|
||||||
|
| NonTop _, NonTop _ (* num_iters >= widening_threshold *) ->
|
||||||
|
Top
|
||||||
|
end
|
||||||
|
|
||||||
|
module ResourcesHeld = AbstractDomain.Map (AccessPath) (BoundsWithTop)
|
||||||
|
open AbstractDomain.Types
|
||||||
|
|
||||||
|
let initial = ResourcesHeld.empty
|
||||||
|
|
||||||
|
let update_count count n = match count with Top -> Top | NonTop held -> NonTop (held + n)
|
||||||
|
|
||||||
|
let incr_count count = update_count count 1
|
||||||
|
|
||||||
|
let decr_count count = update_count count (-1)
|
||||||
|
|
||||||
|
let find_count access_path held =
|
||||||
|
match ResourcesHeld.find_opt access_path held with Some count -> count | None -> NonTop 0
|
||||||
|
|
||||||
|
|
||||||
|
let acquire_resource access_path held =
|
||||||
|
let old_count = find_count access_path held in
|
||||||
|
ResourcesHeld.add access_path (incr_count old_count) held
|
||||||
|
|
||||||
|
|
||||||
|
let release_resource access_path held =
|
||||||
|
let old_count = find_count access_path held in
|
||||||
|
ResourcesHeld.add access_path (decr_count old_count) held
|
||||||
|
|
||||||
|
|
||||||
|
let assign lhs_access_path rhs_access_path held =
|
||||||
|
let one_binding access_path count held =
|
||||||
|
match AccessPath.replace_prefix ~prefix:rhs_access_path lhs_access_path access_path with
|
||||||
|
| Some base_access_path ->
|
||||||
|
ResourcesHeld.add base_access_path count held
|
||||||
|
| None ->
|
||||||
|
ResourcesHeld.add access_path count held
|
||||||
|
in
|
||||||
|
ResourcesHeld.fold one_binding held ResourcesHeld.empty
|
||||||
|
|
||||||
|
|
||||||
|
let has_leak formal_map held =
|
||||||
|
(* test if we acquired resources that we do not return to the caller *)
|
||||||
|
let is_local_leak access_path count =
|
||||||
|
let base, _ = access_path in
|
||||||
|
match (count, base) with
|
||||||
|
| Top, _ ->
|
||||||
|
false
|
||||||
|
| NonTop count, _ when count > 1 ->
|
||||||
|
true
|
||||||
|
| NonTop count, _ when count <= 0 ->
|
||||||
|
false
|
||||||
|
(* count = 1 *)
|
||||||
|
| _, (var, _) when Var.is_global var ->
|
||||||
|
false
|
||||||
|
| _, (ret, _) when Var.is_return ret ->
|
||||||
|
false
|
||||||
|
| _, base when FormalMap.is_formal base formal_map ->
|
||||||
|
false
|
||||||
|
| _ ->
|
||||||
|
true
|
||||||
|
in
|
||||||
|
ResourcesHeld.exists is_local_leak held
|
||||||
|
|
||||||
|
|
||||||
|
module Summary = struct
|
||||||
|
module InterfaceAccessPath = struct
|
||||||
|
type base = Return | Formal of int [@@deriving compare]
|
||||||
|
|
||||||
|
let pp_base f = function
|
||||||
|
| Return ->
|
||||||
|
F.pp_print_string f "Return"
|
||||||
|
| Formal i ->
|
||||||
|
F.fprintf f "Formal(%d)" i
|
||||||
|
|
||||||
|
|
||||||
|
type t = base * AccessPath.access list [@@deriving compare]
|
||||||
|
|
||||||
|
let pp f = function
|
||||||
|
| base, [] ->
|
||||||
|
pp_base f base
|
||||||
|
| base, accesses ->
|
||||||
|
F.fprintf f "%a.%a" pp_base base AccessPath.pp_access_list accesses
|
||||||
|
end
|
||||||
|
|
||||||
|
module ResourcesFromFormals = PrettyPrintable.MakePPMap (InterfaceAccessPath)
|
||||||
|
|
||||||
|
type t = BoundsWithTop.t ResourcesFromFormals.t
|
||||||
|
|
||||||
|
let pp = ResourcesFromFormals.pp ~pp_value:BoundsWithTop.pp
|
||||||
|
|
||||||
|
let make formal_map held =
|
||||||
|
let to_interface access_path =
|
||||||
|
let base, accesses = access_path in
|
||||||
|
match FormalMap.get_formal_index base formal_map with
|
||||||
|
| Some i ->
|
||||||
|
Some (InterfaceAccessPath.Formal i, accesses)
|
||||||
|
| None ->
|
||||||
|
if Var.is_return (fst base) then Some (InterfaceAccessPath.Return, accesses) else None
|
||||||
|
in
|
||||||
|
ResourcesHeld.fold
|
||||||
|
(fun access_path count acquired ->
|
||||||
|
match to_interface access_path with
|
||||||
|
| Some interface_access_path ->
|
||||||
|
ResourcesFromFormals.add interface_access_path count acquired
|
||||||
|
| None ->
|
||||||
|
acquired )
|
||||||
|
held ResourcesFromFormals.empty
|
||||||
|
|
||||||
|
|
||||||
|
let apply ~summary ~return ~actuals held =
|
||||||
|
let apply_one (base, accesses) callee_count held =
|
||||||
|
let access_path_opt =
|
||||||
|
match (base : InterfaceAccessPath.base) with
|
||||||
|
| Return ->
|
||||||
|
Some (return, accesses)
|
||||||
|
| Formal i -> (
|
||||||
|
match List.nth actuals i with
|
||||||
|
| Some (HilExp.AccessExpression actual_expr) ->
|
||||||
|
Some
|
||||||
|
(AccessPath.append (HilExp.AccessExpression.to_access_path actual_expr) accesses)
|
||||||
|
| _ ->
|
||||||
|
None )
|
||||||
|
in
|
||||||
|
match access_path_opt with
|
||||||
|
| None ->
|
||||||
|
held
|
||||||
|
| Some access_path ->
|
||||||
|
let new_count =
|
||||||
|
match callee_count with
|
||||||
|
| Top ->
|
||||||
|
Top
|
||||||
|
| NonTop callee_count ->
|
||||||
|
let old_count =
|
||||||
|
ResourcesHeld.find_opt access_path held |> Option.value ~default:(NonTop 0)
|
||||||
|
in
|
||||||
|
update_count old_count callee_count
|
||||||
|
in
|
||||||
|
ResourcesHeld.add access_path new_count held
|
||||||
|
in
|
||||||
|
ResourcesFromFormals.fold apply_one summary held
|
||||||
|
end
|
||||||
|
|
||||||
|
type summary = Summary.t
|
||||||
|
|
||||||
|
include ResourcesHeld
|
@ -0,0 +1,30 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
include AbstractDomain.S
|
||||||
|
|
||||||
|
val initial : t
|
||||||
|
|
||||||
|
val acquire_resource : AccessPath.t -> t -> t
|
||||||
|
|
||||||
|
val release_resource : AccessPath.t -> t -> t
|
||||||
|
|
||||||
|
val assign : AccessPath.t -> AccessPath.t -> t -> t
|
||||||
|
|
||||||
|
val has_leak : FormalMap.t -> t -> bool
|
||||||
|
|
||||||
|
type summary
|
||||||
|
|
||||||
|
module Summary : sig
|
||||||
|
val apply : summary:summary -> return:AccessPath.base -> actuals:HilExp.t list -> t -> t
|
||||||
|
|
||||||
|
val make : FormalMap.t -> t -> summary
|
||||||
|
|
||||||
|
val pp : Format.formatter -> summary -> unit
|
||||||
|
end
|
@ -0,0 +1,131 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
(* Boilerplate to write/read our summaries alongside the summaries of other analyzers *)
|
||||||
|
module Payload = SummaryPayload.Make (struct
|
||||||
|
type t = ResourceLeakDomain.summary
|
||||||
|
|
||||||
|
let update_payloads resources_payload (payloads : Payloads.t) =
|
||||||
|
{payloads with lab_resource_leaks= Some resources_payload}
|
||||||
|
|
||||||
|
|
||||||
|
let of_payloads {Payloads.lab_resource_leaks} = 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
|
||||||
|
| Typ.Procname.Java java_procname ->
|
||||||
|
is_closeable_typename tenv
|
||||||
|
(Typ.Name.Java.from_string (Typ.Procname.Java.get_class_name java_procname))
|
||||||
|
| _ ->
|
||||||
|
false
|
||||||
|
|
||||||
|
|
||||||
|
let acquires_resource tenv procname =
|
||||||
|
(* We assume all constructors of a subclass of Closeable acquire a resource *)
|
||||||
|
Typ.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" (Typ.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, Direct callee_procname, HilExp.AccessExpression allocated :: _, _, _loc)
|
||||||
|
when acquires_resource tenv callee_procname ->
|
||||||
|
ResourceLeakDomain.acquire_resource
|
||||||
|
(HilExp.AccessExpression.to_access_path allocated)
|
||||||
|
astate
|
||||||
|
| Call (_, Direct callee_procname, [actual], _, _loc)
|
||||||
|
when releases_resource tenv callee_procname -> (
|
||||||
|
match actual with
|
||||||
|
| HilExp.AccessExpression access_expr ->
|
||||||
|
ResourceLeakDomain.release_resource
|
||||||
|
(HilExp.AccessExpression.to_access_path access_expr)
|
||||||
|
astate
|
||||||
|
| _ ->
|
||||||
|
astate )
|
||||||
|
| Call (return, Direct callee_procname, actuals, _, _loc) -> (
|
||||||
|
match Payload.read pdesc callee_procname with
|
||||||
|
| Some summary ->
|
||||||
|
(* interprocedural analysis produced a summary: use it *)
|
||||||
|
ResourceLeakDomain.Summary.apply ~summary ~return ~actuals astate
|
||||||
|
| None ->
|
||||||
|
(* No summary for [callee_procname]; it's native code or missing for some reason *)
|
||||||
|
astate )
|
||||||
|
| Assign (access_expr, AccessExpression rhs_access_expr, _loc) ->
|
||||||
|
ResourceLeakDomain.assign
|
||||||
|
(HilExp.AccessExpression.to_access_path access_expr)
|
||||||
|
(HilExp.AccessExpression.to_access_path rhs_access_expr)
|
||||||
|
astate
|
||||||
|
| 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 formal_map (proc_data : unit ProcData.t) =
|
||||||
|
if ResourceLeakDomain.has_leak formal_map 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 ->
|
||||||
|
let formal_map = FormalMap.make proc_desc in
|
||||||
|
report_if_leak post summary formal_map proc_data ;
|
||||||
|
Payload.update_summary (ResourceLeakDomain.Summary.make formal_map post) summary
|
||||||
|
| None ->
|
||||||
|
L.(die InternalError)
|
||||||
|
"Analyzer failed to compute post for %a" Typ.Procname.pp
|
||||||
|
(Procdesc.get_proc_name proc_data.pdesc)
|
@ -0,0 +1,10 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
val checker : Callbacks.proc_callback_t
|
@ -0,0 +1,150 @@
|
|||||||
|
# Build your own Resource Leak analysis
|
||||||
|
|
||||||
|
This is a lab exercise designed to take the participant through the basics of using the Infer.AI framework for building compositional abstract interpreters. We provide the skeleton for a simple intraprocedural resource leak analysis. During this exercise, you will identify limitations in the existing approach and work on extending it to a create a more powerful interprocedural analysis.
|
||||||
|
|
||||||
|
The files to work on are [ResourceLeaks.ml](./ResourceLeaks.ml) and [ResourceLeakDomain.ml](./ResourceLeakDomain.ml), and their corresponding .mli files.
|
||||||
|
|
||||||
|
## (0) Quick Start
|
||||||
|
|
||||||
|
### (a) With Docker...
|
||||||
|
|
||||||
|
Using Docker is the fastest way: you do not need to clone the Infer repository and every thing is set up in the Docker image for you to start hacking on Infer straight away.
|
||||||
|
|
||||||
|
1. Get Docker: https://www.docker.com/get-started
|
||||||
|
|
||||||
|
2. Run the infer Docker image with `docker run -it infer/infer:infer-latest-java-dev -v $HOME/infer-docker:/infer-host /bin/bash`. This will give you a prompt *inside the Docker image*. Do not close that terminal for the duration of the lab.
|
||||||
|
|
||||||
|
3. Within Docker, copy the /infer directory to your mount point: `cp -av /infer/. /infer-host`
|
||||||
|
|
||||||
|
4. You can now edit the files *locally* in `$HOME/infer-docker` and build infer *inside Docker* in `/infer-host` using the shell prompt gotten in step 2. The quickest way is to run `make byte` from `/infer-host`.
|
||||||
|
|
||||||
|
### (a') ...or, alternatively, build Infer locally from scratch
|
||||||
|
|
||||||
|
Clone the Infer repository at https://github.com/facebook/infer and read the instructions in [INSTALL.md](https://github.com/facebook/infer/blob/master/INSTALL.md) to build Infer from source. Note that you only need Infer for Java for this lab: `./build-infer.sh java`.
|
||||||
|
|
||||||
|
### (b) Set up your Development Environment
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](https://github.com/facebook/infer/blob/master/CONTRIBUTING.md#hacking-on-the-code) to set your editor and for tips and tricks on how to hack on Infer more efficiently. One of the most useful things to install in your editor to navigate OCaml source code efficiently is [Merlin](https://github.com/ocaml/merlin/wiki).
|
||||||
|
|
||||||
|
## (1) Warm up: running, testing, and debugging Infer
|
||||||
|
|
||||||
|
(a) Change to the test directory (`cd infer/tests/codetoanalyze/java/lab`) and run the checker on a single file:
|
||||||
|
|
||||||
|
```
|
||||||
|
infer --resource-leak-only -- javac Leaks.java
|
||||||
|
```
|
||||||
|
|
||||||
|
Infer should report 3 bugs.
|
||||||
|
|
||||||
|
(b) Run the analyzer on a single test file to produce the debug HTML:
|
||||||
|
|
||||||
|
```
|
||||||
|
infer -g --resource-leak-only -- javac Leaks.java
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, open the debug HTML:
|
||||||
|
|
||||||
|
```
|
||||||
|
firefox infer-out/captured/*.html
|
||||||
|
```
|
||||||
|
|
||||||
|
This helpful artifact shows the Infer warnings alongside the code they are complaining about. It also displays the CFG node(s) associated with each line of code. Clicking a CFG node shows the Infer IR associated with the node, and the pre/post state computed while analyzing the instruction. Come back to the debug HTML early and often when you can't understand what your analysis is doing--it will help!
|
||||||
|
|
||||||
|
(c) The `Logging.d_printf`/`Logging.d_printfln` functions print to the debug HTML. The logging is contextual: it will print to the log for the CFG node that's being analyzed at the time the logging statement execute. Try adding `Logging.d_printfln "Hi Infer";` inside of the case for `Call`, recompiling/re-running. You should see the text you printed inside the appropriate CFG node log in the debug HTML.
|
||||||
|
|
||||||
|
(d) The `Logging.debug_dev` function prints to the console. This can be useful for printing information that doesn't happen in the context of a particular CFG node (e.g., performing post-processing on a summary). Try adding `Logging.debug_dev "Hi Infer;"` to the `compute_post` function, recompile/re-run and see your text printed on the command line.
|
||||||
|
|
||||||
|
## (2) Integer domain for straight-line programs
|
||||||
|
|
||||||
|
(a) Let's start with a simple-minded analysis: count the number of open resources. Change `ResourceLeakDomain.t` to be of type `int`:
|
||||||
|
|
||||||
|
```OCaml
|
||||||
|
type t = int
|
||||||
|
```
|
||||||
|
|
||||||
|
You don't need to change `join` or `widen` yet, this will be done later. You also don't need to change ResourceLeakDomain.mli, only ResourceLeakDomain.ml.
|
||||||
|
|
||||||
|
(b) Now, in `ResourceLeaks.ml`, change the first `Call` case of `exec_instr` to bump the integer when a resource is acquired, and decrease it when a resource is released. Use `acquires_resource` (remove the leading `_`, telling OCaml not to warn that it was unused) and `releases_resource` (same). For the rest of the lab, it will be useful **not** to expose the type of `ResourceLeakDomain.t` to `ResourceLeaks.ml`, so add functions `ResourceLeakDomain.acquire_resource` and `ResourceLeakDomain.release_resource` to do the actual integer manipulations. Expose these functions in ResourceLeakDomain.mli`.
|
||||||
|
|
||||||
|
Finally, look again at the HTML debug output of infer on [Leaks.java](https://github.com/facebook/infer/blob/master/infer/tests/codetoanalyze/java/lab/Leaks.java). You should see the resource count be incremented and decremented appropriately.
|
||||||
|
|
||||||
|
(c) Now let's report leaks! Write and expose a function `ResourceLeakDomain.has_leak`, true when an abstract state shows a leak. Then change `ResourceLeaks.report_if_leak` to report when `ResourceLeakDomain.has_leak post` is true. You can use this code to report:
|
||||||
|
|
||||||
|
```OCaml
|
||||||
|
let last_loc = Procdesc.Node.get_loc (Procdesc.get_exit_node proc_data.pdesc) in
|
||||||
|
let message = F.asprintf "Leaked %d resource(s)" ResourceLeakDomain.pp leak_count in
|
||||||
|
Reporting.log_error summary ~loc:last_loc IssueType.resource_leak message
|
||||||
|
```
|
||||||
|
|
||||||
|
(d) Think about the concretization of the resource count. What does a resource count of zero mean? Is there a concrete state in the concretization of "Resource count zero" that leaks a resource? Write a simple test method `FN_leakBad` in [Leaks.java](https://github.com/facebook/infer/blob/master/infer/tests/codetoanalyze/java/lab/Leaks.java) that will produce this concrete state (that is, a false negative test where the program leaks a resource, but the analyzer doesn't catch it).
|
||||||
|
|
||||||
|
(e) In addition, there are programs that do not leak resources that the analyzer will flag. Write a simple test method `FP_noLeakOk` that exhibits this problem (that is, a false positive test that demonstrates the imprecision of the analyzer).
|
||||||
|
|
||||||
|
(f) Do your false negative and false positive examples have a common underlying cause? Can the domain be improved to address them?
|
||||||
|
|
||||||
|
Let's stick with just an integer domain to keep things simple until (5).
|
||||||
|
|
||||||
|
## (3) Integer domain for branching and looping programs
|
||||||
|
|
||||||
|
(a) Run your checker on [LeaksBranch.java](https://github.com/facebook/infer/blob/master/infer/tests/codetoanalyze/java/lab/LeaksBranch.java) it again. Uh oh, it crashed! Fix it by implementing `join` for your domain.
|
||||||
|
|
||||||
|
(b) Now run on [LeaksLoop.java](https://github.com/facebook/infer/blob/master/infer/tests/codetoanalyze/java/lab/LeaksLoop.java), and implement `widen`. Hmm... There's a termination bug in the abstract domain--do you see it?
|
||||||
|
|
||||||
|
(c) Time for a richer domain! A classic solution to this problem in abstract interpretation is to add a `Top` value that is larger than any integer.
|
||||||
|
|
||||||
|
- Fix the domain to make your test pass and ensure that the analyzer will always terminate. Make sure your fix is sufficiently general (remember: the allocation count can be negative :)).
|
||||||
|
|
||||||
|
- Hint: `AbstractDomain.TopLifted` may be useful for this. Just put all the code in ResourceLeakDomain.ml that corresponds to the signature `AbstractDomain.S` into a submodule `FiniteBounds`, then let `TopLifted` do all the lifting with
|
||||||
|
|
||||||
|
```
|
||||||
|
include AbstractDomain.TopLifter (FiniteBounds)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Hint#2: use `open AbstractDomain.Types` to be able to write, e.g., `Top` instead of `AbstractDomain.Top`.
|
||||||
|
|
||||||
|
|
||||||
|
## (4) Interprocedural analysis
|
||||||
|
|
||||||
|
Augment the summary type with state to indicate when the current procedure returns a resource. Allowing a resource to escape to the caller should not be considered a leak. This improvement should allow your analysis to pass the tests in [LeaksInterprocedural.java](https://github.com/facebook/infer/blob/master/infer/tests/codetoanalyze/java/lab/LeaksInterprocedural.java).
|
||||||
|
|
||||||
|
Hint: What do return values look like in infer? They are assignments to a special variable, detected by `Var.is_return`. You may also want to match only return variables of some specific object type. Use this code to look at the classname of the type of the return value:
|
||||||
|
|
||||||
|
```OCaml
|
||||||
|
| Assign
|
||||||
|
(Base (ret, {Typ.desc= Typ.Tptr ({Typ.desc= Typ.Tstruct ret_typename}, _)}), _rhs_exp, _loc)
|
||||||
|
when Var.is_return ret ->
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `ret_var` is the return variable (if `Var.is_return ret_var` returns true), of type `ret_typename` (really `ret_typename*` in infer's intermediate representation).
|
||||||
|
|
||||||
|
|
||||||
|
## (5) Access paths
|
||||||
|
|
||||||
|
(a) Change the simple counting domain to a domain that overapproximates the set of storage locations that hold a resource. As a concrete goal, the new domain should allow you to print the name of the resource(s) that leak in the error message (rather than just the number of resources). The new domain should also allow your analysis to get the correct answer on your false negative and false positive tests from 2(d) and 2(e). Think about the following questions when designing your new domain:
|
||||||
|
|
||||||
|
- How should we abstract storage locations? Is abstracting the stack program variables (`Var.t`)'s enough, or do we need an abstraction of the heap as well?
|
||||||
|
|
||||||
|
- How will we handle aliasing (storage locations that store the same address)? More precisely, how to handle assignement?
|
||||||
|
|
||||||
|
- Will it be easy to extend the domain to incorporate information from the callee summaries/represent state that will be instantiated by callers?
|
||||||
|
|
||||||
|
- Some modules that might be useful in creating your new domain (depending on what approach you choose): `AbstractDomain.FiniteSet`, `AbstractDomain.Map`, `AccessPath`, `Var`.
|
||||||
|
|
||||||
|
- It's okay for the domain to diverge in certain cases for the purpose of this lab. Can you write a method that would make your checker diverge?
|
||||||
|
|
||||||
|
(b) Write some tests that demonstrate the limitations of your new domain: both false positives (names prefixed with `FP_` and false negatives (prefixed with `FN_`). Add them to [LeaksAccessPaths.java](https://github.com/facebook/infer/blob/master/infer/tests/codetoanalyze/java/lab/LeaksAccessPath.java).
|
||||||
|
|
||||||
|
(c) Augment the summary type with state to indicate formals that are closed by the current function. This should allow your analysis to pass the tests in [LeaksAccessPathsInterprocedural.java](https://github.com/facebook/infer/blob/master/infer/tests/codetoanalyze/java/lab/LeaksAccessPathsInterprocedural.java).
|
||||||
|
|
||||||
|
Hint: You will find the `FormalMap` module useful for this. This module lets you go back and forth between the index of a formal and its name. This utility module is also used in the `RacerD` and `TaintAnalysis` modules.
|
||||||
|
|
||||||
|
|
||||||
|
## (6) Making it practical
|
||||||
|
|
||||||
|
(a) Real resource leaks frequently involve failing to close resources along exceptional control-flow paths. For simplicity, the initial version of the current analysis uses a filtered view of the CFG that skips exceptional edges (`ProcCfg.Normal`). To find more bugs, you might want to switch to using `ProcCfg.Exceptional` and make sure that your analysis gets the right answer on some realistic exception examples like [LeaksExceptions.java](https://github.com/facebook/infer/blob/master/infer/tests/codetoanalyze/java/lab/LeaksExceptions.java).
|
||||||
|
|
||||||
|
(b) Try running on real code! The instructions [here](http://fm.csl.sri.com/SSFT17/infer-instr.html) have several suggestions for open-source Android apps to point your analysis at. Try `./gradlew assembleDebug -x test` first to make sure everything builds correctly without Infer (if not, you are probably missing some dependencies--the error messages should guide you). Once that's working, try
|
||||||
|
`./gradlew clean; infer run --resource-leak-only -- ./gradlew assembleDebug -x test`.
|
||||||
|
- Found a real bug? Bonus points! Send a pull request to fix it! Very frequently, the best fix is to use try-with-resources.
|
||||||
|
- Found a false positive in your analysis? Try re-running Infer with `--debug` and see if you can narrow down the root cause/fix it.
|
||||||
|
- How does your analysis compare to Infer's production resource leak analysis? Run with `infer -- <gradle command>` to see if your analysis finds bugs that Infer misses, or vice versa.
|
@ -0,0 +1,14 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
include AbstractDomain.S
|
||||||
|
|
||||||
|
val initial : t
|
||||||
|
|
||||||
|
type summary = t
|
@ -0,0 +1,10 @@
|
|||||||
|
(*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
val checker : Callbacks.proc_callback_t
|
@ -1,138 +0,0 @@
|
|||||||
This is a lab exercise designed to take the participant through the basics of using the Infer.AI framework for building compositional abstract interpreters. We provide the skeleton for a simple intraprocedural resource leak analysis. During this exercise, you will identify limitations in the existing approach and work on extending it to a create a more powerful interprocedural analysis.
|
|
||||||
|
|
||||||
This document assumes that you have already [installed](https://github.com/facebook/infer/blob/master/INSTALL.md) Infer from source (you only need Infer for Java for this lab: `./build-infer.sh java`), successfully compiled it, and [set up](https://github.com/facebook/infer/blob/master/CONTRIBUTING.md#hacking-on-the-code) your OCaml dev environment.
|
|
||||||
|
|
||||||
The `infer/src/labs/ResourceLeaks.ml` and `infer/src/labls/ResourceLeaksDomain.ml` files have comments containing hints for which parts of the code you need to change in order to accomplish a particular task (e.g., 1(e) in `ResourceLeaks.ml`).
|
|
||||||
|
|
||||||
## (1) Warm up: running, testing, and debugging Infer
|
|
||||||
|
|
||||||
(a) Change to the test directory (`cd infer/tests/codetoanalyze/java/lab`) and run the checker on a single file: `infer --resource-leak-only -- javac Leaks.java`. Infer should report 3 bugs.
|
|
||||||
|
|
||||||
(b) Run the tests with `make test`. The tests work by producing an output file `issues.exp.test`, which is compared against the expected output file `issues.exp`. Tests pass when the two files are identical. You should see no output, which indicates that the tests have passed.
|
|
||||||
|
|
||||||
(c) Add a new test method containing a resource leak to `Leaks.java`, then run the tests again. The tests should now fail, and the error message should indicate that the failure is due to the new lreeak that you added. As the message suggests, run `make replace` to update the expected output (this simply copies the actual output `issues.exp.test` to the expected output `issues.exp`). Re-run the tests; they should now pass again.
|
|
||||||
|
|
||||||
(d) Run the analyzer on a single test file to produce the debug HTML: `infer --debug --resource-leak-only -- javac Leaks.java`. Then, open the debug HTML: `open infer-out/captured/*.html`. This helpful artifact shows the Infer warnings alongside the code they are complaining about. It also displays the CFG node(s) associated with each line of code. Clicking a CFG node shows the Infer IR associated with the node, and the pre/post state computed while analyzing the instruction. Come back to the debug HTML early and often when you can't understand what your analysis is doing--it will help!
|
|
||||||
|
|
||||||
(e) The `Logging.d_str`/`Logging.d_strln` functions print to the debug HTML. The logging is contextual: it will print to the log for the CFG node that's being analyzed at the time the logging statement execute. Try adding `Logging.d_strln "Hi Infer";` inside of the case for `Call`, recompiling/re-running. You should see the text you printed inside the appropriate CFG node log in the debug HTML.
|
|
||||||
|
|
||||||
(f) The `Logging.progress` function prints to stderr. This can be useful for printing information that doesn't happen in the context of a particular CFG node (e.g., performing post-processing on a summary). Try adding `Logging.progress "Hi Infer;"` to the `compute_post` function, recompile/re-run and see your text printed on the command line.
|
|
||||||
|
|
||||||
## (2) Reasoning about abstract domains
|
|
||||||
|
|
||||||
(a) Look at the `ResourceLeakDomain.ml`. There's a termination bug in the abstract domain--do you see it?.
|
|
||||||
- Write a test method that makes the analyzer diverge (Note: this may manifest as a stack overflow that crashes Infer rather than geniune divergence).
|
|
||||||
- Fix the domain to make your test pass and ensure that the analyzer will always terminate. Make sure your fix is sufficiently general (remember: the allocation count can be negative :)).
|
|
||||||
- Hint: `AbstractDomain.TopLifted` may be useful for this.
|
|
||||||
|
|
||||||
(b) Think about the concretization of the resource count. What does a resource count of zero mean? Is there a concrete state in the concretization of "Resource count zero" that leaks a resource? Write a simple test method `FN_leakBad` that will produce this concrete state (that is, a false negative test where the program leaks a resource, but the analyzer doesn't catch it)?
|
|
||||||
|
|
||||||
(c) In addition, there are programs that do not leak resources that the analyzer will flag. Write a simple test method `FP_noLeakOk` that exhibits this problem (that is, a false positive test that demonstrates the imprecision of the analyzer)?
|
|
||||||
|
|
||||||
(d) Do your false negative and false positive examples have a common underlying cause? Can the domain be improved to address them?
|
|
||||||
|
|
||||||
## (3) Improving the domain
|
|
||||||
|
|
||||||
(a) Change the simple counting domain to a domain that overapproximates the set of storage locations that hold a resource. As a concrete goal, the new domain should allow you to print the name of the resource(s) that leak in the error message (rather than just the number of resources). The new domain should also allow your analysis to get the correct answer on your false negative and false positive tests from 2(b) and 2(c). Think about the following questions when designing your new domain:
|
|
||||||
- How should we abstract storage locations? Is abstracting the stack program variables (`Var.t`)'s enough, or do we need an abstraction of the heap as well?
|
|
||||||
- How will we handle aliasing (storage locations that store the same address?)
|
|
||||||
- Will it be easy to extend the domain to incorporate information from the callee summaries/represent state that will be instantiated by callers (see 4)?
|
|
||||||
- Some modules that might be useful in creating your new domain (depending on what approach you choose): `AbstractDomain.FiniteSet`, `AbstractDomain.Map`, `AccessPath`, `Var`.
|
|
||||||
|
|
||||||
(b) Write some tests that demonstrate the limitations of your new domain: both false positives (names prefixed with `FP_` and false negatives (prefixed with `FN_`).
|
|
||||||
|
|
||||||
## (4) Interprocedural analysis
|
|
||||||
At this point, you likely have a fairly advanced intraprocedural analysis that is capable of finding resource leaks in real programs. Feel free to skip to 5 if you are eager to get started with bugfinding; many real resource leak bugs are intraprocedural. Alternatively, continue on with 4 to make your analysis interprocedural.
|
|
||||||
|
|
||||||
(a) Augment the summary type with state to indicate when the current procedure returns a resource. Allowing a resource to escape to the caller should not be considered a leak. This improvement should allow your analysis to pass the following tests:
|
|
||||||
|
|
||||||
```
|
|
||||||
FileInputStream returnResourceOk() {
|
|
||||||
return new FileInputStream("file.txt");
|
|
||||||
}
|
|
||||||
|
|
||||||
FileInputStream returnResourceWrapperOk() {
|
|
||||||
return returnResourceOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void returnResourceThenCloseOk() {
|
|
||||||
returnResourceWrapperOk().close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void returnResourceThenLeakBad() {
|
|
||||||
returnResourceWrapperOk(); // warning
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
(b) Augment the summary type with state to indicate formals that are closed by the current function. This should allow your analysis to pass the following tests:
|
|
||||||
|
|
||||||
```
|
|
||||||
void closeResourceOk(Closeable c) {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeResourceWrapperOk(c) {
|
|
||||||
closeResourceOk(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeResourceDirectOK() {
|
|
||||||
closeResourceOk(new FileInputStream("file.txt"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeResourceTransitiveOk()
|
|
||||||
closeResourceOk(new FileInputStream("file.txt"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeOne(Closeable c1, Closeable c2) {
|
|
||||||
c2.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeOnlyOneBad() {
|
|
||||||
closeOne(new FileInputStream("1.txt"), new FileInputStream("2.txt")); // warning
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Hint: you might find the `FormalMap.t` stored in `proc_data.extras` useful for this. This module lets you go back and forth between the index of a formal and its name. This utility module is also used in the `RacerD` and `TaintAnalysis` modules.
|
|
||||||
|
|
||||||
## (5) Making it practical
|
|
||||||
|
|
||||||
(a) Real resource leaks frequently involve failing to close resources along exceptional control-flow paths. For simplicity, the initial version of the current analysis uses a filtered view of the CFG that skips exceptional edges (`ProcCfg.Normal`). To find more bugs, you might want to switch to using `ProcCfg.Exceptional` and make sure that your analysis gets the right answer on some realistic exception examples like:
|
|
||||||
|
|
||||||
```
|
|
||||||
void tryWithResourcesOk() {
|
|
||||||
// this is syntactic sugar that makes sure stream gets closed
|
|
||||||
try (FileInputStream stream = new FileInputStream("file.txt")) {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeInFinallyOk() {
|
|
||||||
FileInputStream stream = null;
|
|
||||||
try {
|
|
||||||
stream = new FileInputStream("file.txt");
|
|
||||||
} finally {
|
|
||||||
if (stream != null) {
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeInCatchBad() {
|
|
||||||
FileInputStream stream = null;
|
|
||||||
try {
|
|
||||||
stream = new FileInputStream("file.txt");
|
|
||||||
} catch {
|
|
||||||
// ok not to close here, since we never acquired the resource
|
|
||||||
}
|
|
||||||
if (stream != null) {
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
(b) Try running on real code! The instructions [here](http://fm.csl.sri.com/SSFT17/infer-instr.html) have several suggestions for open-source Android apps to point your analysis at. Try `./gradlew assembleDebug -x test` first to make sure everything builds correctly without Infer (if not, you are probably missing some dependencies--the error messages should guide you). Once that's working, try
|
|
||||||
`./gradlew clean; infer run --resource-leak-only -- ./gradlew assembleDebug -x test`.
|
|
||||||
- Found a real bug? Bonus points! Send a pull request to fix it! Very frequently, the best fix is to use try-with-resources.
|
|
||||||
- Found a false positive in your analysis? Try re-running Infer with `--debug` and see if you can narrow down the root cause/fix it.
|
|
||||||
- How does your analysis compare to Infer's production resource leak analysis? Run with `infer -- <gradle command>` to see if your analysis finds bugs that Infer misses, or vice versa.
|
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package codetoanalyze.java.checkers;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class LeaksAccessPaths {
|
||||||
|
|
||||||
|
void acquireTwoThenReleaseOneTwiceBad() throws IOException, FileNotFoundException {
|
||||||
|
FileInputStream stream1 = new FileInputStream("file1.txt");
|
||||||
|
FileInputStream stream2 = new FileInputStream("file2.txt");
|
||||||
|
stream1.close();
|
||||||
|
stream1.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package codetoanalyze.java.checkers;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class LeaksAccessPathsInterprocedural {
|
||||||
|
|
||||||
|
void closeResourceOk(Closeable c) throws IOException {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeResourceWrapperOk(Closeable c) throws IOException {
|
||||||
|
closeResourceOk(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeResourceDirectOK() throws IOException, FileNotFoundException {
|
||||||
|
closeResourceOk(new FileInputStream("file.txt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeResourceTransitiveOk() throws IOException, FileNotFoundException {
|
||||||
|
closeResourceOk(new FileInputStream("file.txt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeOne(Closeable c1, Closeable c2) throws IOException {
|
||||||
|
c2.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeOnlyOneBad() throws IOException, FileNotFoundException {
|
||||||
|
closeOne(new FileInputStream("1.txt"), new FileInputStream("2.txt")); // warning
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package codetoanalyze.java.checkers;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class LeaksAliasing {
|
||||||
|
|
||||||
|
void releaseBothOk(FileInputStream stream1, FileInputStream stream2) throws IOException {
|
||||||
|
if (stream1 == stream2) {
|
||||||
|
stream1.close();
|
||||||
|
} else {
|
||||||
|
stream1.close();
|
||||||
|
stream2.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package codetoanalyze.java.checkers;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class LeaksBranch {
|
||||||
|
|
||||||
|
void mayLeakBad(Boolean b) throws IOException, FileNotFoundException {
|
||||||
|
FileInputStream stream;
|
||||||
|
if (b) {
|
||||||
|
stream = new FileInputStream("file.txt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void choiceCloseOk(Boolean b) throws IOException, FileNotFoundException {
|
||||||
|
FileInputStream stream = new FileInputStream("file.txt");
|
||||||
|
if (b) {
|
||||||
|
stream.close();
|
||||||
|
} else {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package codetoanalyze.java.checkers;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class LeaksExceptions {
|
||||||
|
|
||||||
|
void tryWithResourcesOk() throws IOException, FileNotFoundException {
|
||||||
|
// this is syntactic sugar that makes sure stream gets closed
|
||||||
|
try (FileInputStream stream = new FileInputStream("file.txt")) {
|
||||||
|
// do something with stream here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeInFinallyOk() throws IOException, FileNotFoundException {
|
||||||
|
FileInputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = new FileInputStream("file.txt");
|
||||||
|
} finally {
|
||||||
|
if (stream != null) {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void twoResourcesBad() throws IOException, FileNotFoundException {
|
||||||
|
FileInputStream stream1 = null;
|
||||||
|
FileInputStream stream2 = null;
|
||||||
|
try {
|
||||||
|
stream1 = new FileInputStream("file1.txt");
|
||||||
|
stream2 = new FileInputStream("file2.txt");
|
||||||
|
} finally {
|
||||||
|
if (stream1 != null) {
|
||||||
|
stream1.close(); // close() can throw!
|
||||||
|
}
|
||||||
|
if (stream2 != null) {
|
||||||
|
stream2.close(); // then this is never reached and stream2 leaks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void leakInCatchBad() throws IOException, FileNotFoundException {
|
||||||
|
FileInputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = new FileInputStream("file_in.txt");
|
||||||
|
} catch (Exception e) {
|
||||||
|
FileOutputStream fis = new FileOutputStream("file_out.txt");
|
||||||
|
// forgot to close fis
|
||||||
|
} finally {
|
||||||
|
if (stream != null) {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package codetoanalyze.java.checkers;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class LeaksInterprocedural {
|
||||||
|
|
||||||
|
FileInputStream returnResourceOk() throws IOException, FileNotFoundException {
|
||||||
|
return new FileInputStream("file.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream returnResourceWrapperOk() throws IOException, FileNotFoundException {
|
||||||
|
return returnResourceOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void returnResourceThenCloseOk() throws IOException, FileNotFoundException {
|
||||||
|
returnResourceWrapperOk().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
int returnResourceThenLeakBad() throws IOException, FileNotFoundException {
|
||||||
|
returnResourceWrapperOk(); // warning
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package codetoanalyze.java.checkers;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class LeaksLoop {
|
||||||
|
|
||||||
|
void openCloseLoopOk(String[] files) throws IOException, FileNotFoundException {
|
||||||
|
FileInputStream stream;
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
String file = files[i];
|
||||||
|
stream = new FileInputStream(file);
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void openAllCloseAllLoopOk(String[] files) throws IOException, FileNotFoundException {
|
||||||
|
FileInputStream[] streams = new FileInputStream[files.length];
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
streams[i] = new FileInputStream(files[i]);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
streams[i].close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,10 @@
|
|||||||
codetoanalyze/java/lab/Leaks.java, codetoanalyze.java.checkers.Leaks.acquireTwoForgetOneBad():void, 4, RESOURCE_LEAK, no_bucket, ERROR, []
|
codetoanalyze/java/lab/Leaks.java, codetoanalyze.java.checkers.Leaks.acquireTwoForgetOneBad():void, 4, RESOURCE_LEAK, no_bucket, ERROR, []
|
||||||
codetoanalyze/java/lab/Leaks.java, codetoanalyze.java.checkers.Leaks.basicLeakBad():void, 2, RESOURCE_LEAK, no_bucket, ERROR, []
|
codetoanalyze/java/lab/Leaks.java, codetoanalyze.java.checkers.Leaks.basicLeakBad():void, 2, RESOURCE_LEAK, no_bucket, ERROR, []
|
||||||
codetoanalyze/java/lab/Leaks.java, codetoanalyze.java.checkers.Leaks.doubleLeakBad():void, 3, RESOURCE_LEAK, no_bucket, ERROR, []
|
codetoanalyze/java/lab/Leaks.java, codetoanalyze.java.checkers.Leaks.doubleLeakBad():void, 3, RESOURCE_LEAK, no_bucket, ERROR, []
|
||||||
|
codetoanalyze/java/lab/LeaksAccessPathsInterprocedural.java, codetoanalyze.java.checkers.LeaksAccessPathsInterprocedural.closeOnlyOneBad():void, 2, RESOURCE_LEAK, no_bucket, ERROR, []
|
||||||
|
codetoanalyze/java/lab/LeaksBranch.java, codetoanalyze.java.checkers.LeaksBranch.mayLeakBad(java.lang.Boolean):void, 5, RESOURCE_LEAK, no_bucket, ERROR, []
|
||||||
|
codetoanalyze/java/lab/LeaksExceptions.java, codetoanalyze.java.checkers.LeaksExceptions.closeInFinallyOk():void, 9, RESOURCE_LEAK, no_bucket, ERROR, []
|
||||||
|
codetoanalyze/java/lab/LeaksExceptions.java, codetoanalyze.java.checkers.LeaksExceptions.leakInCatchBad():void, 12, RESOURCE_LEAK, no_bucket, ERROR, []
|
||||||
|
codetoanalyze/java/lab/LeaksExceptions.java, codetoanalyze.java.checkers.LeaksExceptions.tryWithResourcesOk():void, 5, RESOURCE_LEAK, no_bucket, ERROR, []
|
||||||
|
codetoanalyze/java/lab/LeaksExceptions.java, codetoanalyze.java.checkers.LeaksExceptions.twoResourcesBad():void, 14, RESOURCE_LEAK, no_bucket, ERROR, []
|
||||||
|
codetoanalyze/java/lab/LeaksInterprocedural.java, codetoanalyze.java.checkers.LeaksInterprocedural.returnResourceThenLeakBad():int, 2, RESOURCE_LEAK, no_bucket, ERROR, []
|
||||||
|
Loading…
Reference in new issue