Summary: - freshen up /docs/next/absint-framework to give sensible advice, and delete outdated bits that are now in the API docs so they remain fresh - delete SimpleChecker.ml as it's just a source of bitrot - delete the "adding checkers" page as it's completely outdated and subsumed by the "AI framework" page + the labs. Reviewed By: jberdine Differential Revision: D23597271 fbshipit-source-id: 78b541746master
parent
8fde3f2479
commit
7eba62c9ed
@ -1,96 +0,0 @@
|
||||
(*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
module F = Format
|
||||
module L = Logging
|
||||
|
||||
(** functor that makes it easy to write a basic abstract interpretation checker by lifting a type,
|
||||
comparison function, reporting function, and exec function into an analyzer *)
|
||||
|
||||
module type Spec = sig
|
||||
(** what state do you want to propagate? *)
|
||||
type t
|
||||
|
||||
val initial : t
|
||||
(** implement the state the analysis should start from here *)
|
||||
|
||||
val exec_instr : t -> Sil.instr -> Procdesc.Node.nodekind -> Procname.t -> Tenv.t -> t
|
||||
(** implement how an instruction changes your state here. input is the previous state, current
|
||||
instruction, current node kind, current procedure and type environment. *)
|
||||
|
||||
val report : t -> Location.t -> Procname.t -> unit
|
||||
(** log errors here. input is a state, location where the state occurs in the source, and the
|
||||
current procedure. *)
|
||||
|
||||
val compare : t -> t -> int
|
||||
end
|
||||
|
||||
module type S = sig
|
||||
val checker : IntraproceduralAnalysis.t -> unit
|
||||
(** add YourChecker.checker to registerCallbacks.ml to run your checker *)
|
||||
end
|
||||
|
||||
module Make (Spec : Spec) : S = struct
|
||||
(* powerset domain over Spec.t *)
|
||||
module Domain = struct
|
||||
include AbstractDomain.FiniteSet (struct
|
||||
type t = Spec.t
|
||||
|
||||
let compare = Spec.compare
|
||||
|
||||
let pp _ _ = ()
|
||||
end)
|
||||
|
||||
let widen ~prev ~next ~num_iters =
|
||||
let iters_befor_timeout = 1000 in
|
||||
(* failsafe for accidental non-finite height domains *)
|
||||
if num_iters >= iters_befor_timeout then
|
||||
L.(die InternalError)
|
||||
"Stopping analysis after 1000 iterations without convergence. Make sure your domain is \
|
||||
finite height."
|
||||
else widen ~prev ~next ~num_iters
|
||||
end
|
||||
|
||||
module TransferFunctions (CFG : ProcCfg.S) = struct
|
||||
module CFG = CFG
|
||||
module Domain = Domain
|
||||
|
||||
type analysis_data = IntraproceduralAnalysis.t
|
||||
|
||||
let exec_instr astate_set {IntraproceduralAnalysis.proc_desc; tenv} node instr =
|
||||
let node_kind = CFG.Node.kind node in
|
||||
let pname = Procdesc.get_proc_name proc_desc in
|
||||
Domain.fold
|
||||
(fun astate acc -> Domain.add (Spec.exec_instr astate instr node_kind pname tenv) acc)
|
||||
astate_set Domain.empty
|
||||
|
||||
|
||||
let pp_session_name _node fmt = F.pp_print_string fmt "simple checker"
|
||||
end
|
||||
|
||||
module Analyzer = AbstractInterpreter.MakeRPO (TransferFunctions (ProcCfg.Exceptional))
|
||||
|
||||
let checker ({IntraproceduralAnalysis.proc_desc} as analysis_data) =
|
||||
let proc_name = Procdesc.get_proc_name proc_desc in
|
||||
let nodes = Procdesc.get_nodes proc_desc in
|
||||
let do_reporting node_id state =
|
||||
let astate_set = state.AbstractInterpreter.State.post in
|
||||
if not (Domain.is_empty astate_set) then
|
||||
(* should never fail since keys in the invariant map should always be real node id's *)
|
||||
let node =
|
||||
List.find_exn
|
||||
~f:(fun node -> Procdesc.Node.equal_id node_id (Procdesc.Node.get_id node))
|
||||
nodes
|
||||
in
|
||||
Domain.iter
|
||||
(fun astate -> Spec.report astate (ProcCfg.Exceptional.Node.loc node) proc_name)
|
||||
astate_set
|
||||
in
|
||||
let inv_map = Analyzer.exec_pdesc analysis_data ~initial:Domain.empty proc_desc in
|
||||
Analyzer.InvariantMap.iter do_reporting inv_map
|
||||
end
|
@ -1,26 +0,0 @@
|
||||
(*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
module type Spec = sig
|
||||
type t
|
||||
|
||||
val initial : t
|
||||
|
||||
val exec_instr : t -> Sil.instr -> Procdesc.Node.nodekind -> Procname.t -> Tenv.t -> t
|
||||
|
||||
val report : t -> Location.t -> Procname.t -> unit
|
||||
|
||||
val compare : t -> t -> int
|
||||
end
|
||||
|
||||
module type S = sig
|
||||
val checker : IntraproceduralAnalysis.t -> unit
|
||||
end
|
||||
|
||||
module Make (Spec : Spec) : S
|
@ -1,210 +0,0 @@
|
||||
---
|
||||
id: adding-checkers
|
||||
title: Simple intraprocedural checkers
|
||||
---
|
||||
|
||||
## How can I create my own checkers?
|
||||
|
||||
Infer Checkers provide a framework to perform intra-procedural static analyses.
|
||||
Since this is an open source project, everyone is welcome to contribute with new
|
||||
great checkers. In this page, we will create a very basic checker - a detector
|
||||
for every time the output method `java.io.PrintStream.println` is called. This
|
||||
should be enough to get you started.
|
||||
|
||||
## Before you start
|
||||
|
||||
Make sure you are able to successfully build Infer and your developer
|
||||
environment is set up:
|
||||
|
||||
```
|
||||
./build-infer.sh
|
||||
make devsetup
|
||||
```
|
||||
|
||||
Get familiar with Infer checkers and run Infer with some examples:
|
||||
|
||||
```
|
||||
infer run -- javac Hello.java
|
||||
```
|
||||
|
||||
In addition, get familiar with the Control Flow Graph (CFG) that Infer generates
|
||||
for you:
|
||||
|
||||
```
|
||||
infer run -g -- javac Hello.java
|
||||
dot -Tpdf infer-out/captured/Hello.java*/icfg.dot -o icfg.pdf
|
||||
open icfg.pdf
|
||||
```
|
||||
|
||||
This will give you further information about the analysis that is being done,
|
||||
including the CFG in dot format. It is important that you understand the
|
||||
generated CFG since this is the abstraction of code that Checkers will analyze.
|
||||
|
||||
Infer is built with [OCaml](https://ocaml.org). This is a programming language
|
||||
that combines both functional and imperative programming. If you are not
|
||||
familiar with OCaml, it might be hard at the beginning to understand the code.
|
||||
Take your time to review the
|
||||
[basics](https://ocaml.org/learn/tutorials/basics.html) and do some
|
||||
[exercises](https://ocaml.org/learn/tutorials/99problems.html).
|
||||
|
||||
## Let's go
|
||||
|
||||
The directory `infer/src/absint` contains utilities for the abstract
|
||||
interpretation framework that checkers are based on.
|
||||
|
||||
Looking into `infer/src/checkers` we can find some simple checkers. Most of them
|
||||
are implemented as a module created from a `TransferFunctions` module that is
|
||||
turned into an analyzer by applying one of the `AbstractInterpreter.Make*`
|
||||
functors, together with a `checker` function that calls into it. You can start
|
||||
by copying the code for one of these and modify it (eg
|
||||
checkers/SimpleChecker.ml). For example:
|
||||
|
||||
```ocaml
|
||||
module TransferFunctions = struct
|
||||
...
|
||||
let exec_instr astate proc_data cfg_node (instr : Sil.instr) =
|
||||
match instr with
|
||||
| pattern ->
|
||||
ST.report_error
|
||||
proc_name
|
||||
proc_desc
|
||||
"CHECKERS_MY_SIMPLE_CHECKER"
|
||||
location
|
||||
"A description of my simple checker"
|
||||
| _ -> astate
|
||||
end
|
||||
|
||||
module Analyzer = AbstractInterpreter.Make (TransferFunctions)
|
||||
|
||||
let checker {Callbacks.exe_env; summary; get_procs_in_file} : Summary.t =
|
||||
let proc_name = Summary.get_proc_name summary in
|
||||
let tenv = Exe_env.get_tenv exe_env proc_name in
|
||||
let proc_data = ProcData.make_default summary tenv in
|
||||
ignore (Analyzer.compute_post proc_data ~initial) ;
|
||||
summary
|
||||
```
|
||||
|
||||
Checkers implement a function that detects a given pattern for our specific
|
||||
checker and then calls `AbstractInterpreter.Make` to iterate over all the nodes
|
||||
of the CFG.
|
||||
|
||||
So now we need to know how to create our pattern. As an example, consider the
|
||||
following:
|
||||
|
||||
```ocaml
|
||||
Sil.Call (_, Sil.Const (Sil.Cfun pn), _, loc, _)
|
||||
```
|
||||
|
||||
This pattern matches every function call. In our code, it would look like:
|
||||
|
||||
```ocaml
|
||||
let exec_instr astate proc_data cfg_node (instr : Sil.instr) =
|
||||
match instr with
|
||||
| Call (_, Const (Cfun pn), _, loc, _) ->
|
||||
ST.report_error
|
||||
proc_name
|
||||
proc_desc
|
||||
"CHECKERS_MY_SIMPLE_CHECKER"
|
||||
location
|
||||
"A description of my simple checker"
|
||||
| _ -> astate
|
||||
```
|
||||
|
||||
The `absint/PatternMatch.ml` module contains the
|
||||
`java_proc_name_with_class_method` function which we can use for matching the
|
||||
required pattern.
|
||||
|
||||
Each node is represented using the type `instr` from the Smallfoot Intermediate
|
||||
Language (SIL). Take a look at `IR/Sil.mli` to get familiar with all the types.
|
||||
All source code languages supported by Infer are converted to this
|
||||
representation.
|
||||
|
||||
In this particular example, `Sil.Call` has the following information:
|
||||
|
||||
```ocaml
|
||||
Sil.Call (
|
||||
list_of_return_values,
|
||||
Sil.Const (Const.Cfun name_of_function),
|
||||
list_of_arguments,
|
||||
location,
|
||||
call_flags
|
||||
)
|
||||
```
|
||||
|
||||
I hope this looks straight forward. Argument `call_flags` holds information
|
||||
about the function, such as whether it is virtual or not. Again, this is
|
||||
specified in the file `Sil.mli`.
|
||||
|
||||
The Checker we have written so far is able to detect every single function call.
|
||||
Now, we have to detect whether a specific function call is actually calling
|
||||
`java.io.PrintStream.println`.
|
||||
|
||||
Let's try this:
|
||||
|
||||
```ocaml
|
||||
let is_println pln = match pln with
|
||||
| Procname.Java pn_java ->
|
||||
PatternMatch.java_proc_name_with_class_method
|
||||
pn_java "java.io.PrintStream" "println"
|
||||
| _ ->
|
||||
false in
|
||||
|
||||
let exec_instr astate proc_data cfg_node (instr : Sil.instr) =
|
||||
match instr with
|
||||
| Call (_, Const (Cfun pn), _, loc, _) when is_println pn ->
|
||||
ST.report_error
|
||||
proc_name
|
||||
proc_desc
|
||||
"CHECKERS_MY_SIMPLE_CHECKER"
|
||||
location
|
||||
"A description of my simple checker"
|
||||
| _ -> astate
|
||||
|
||||
```
|
||||
|
||||
Can you spot the difference? A new restriction was added to our pattern --
|
||||
`is_println` expression helps us to check whether the current method is a
|
||||
`java.io.PrintStream.println` method or not.
|
||||
|
||||
So our implementation is done. Now we have to register it as an enabled Checker
|
||||
in `checkers/registerCheckers.ml`.
|
||||
|
||||
Assuming the code is in SimpleCheckers.ml, you would register your checker as a
|
||||
_java_checker_ in `checkers/registerCheckers.ml` by adding it to the
|
||||
`all_checkers` list:
|
||||
|
||||
```ocaml
|
||||
let all_checkers =
|
||||
[ { name= "my simple checker"
|
||||
; active= true
|
||||
; callbacks= [(Procedure SimpleChecker.checker, Language.Java)] }
|
||||
; (* the rest of the list as it was there *)
|
||||
... ]
|
||||
```
|
||||
|
||||
Build Infer with `./build-infer.sh` and your first Checker is ready!
|
||||
|
||||
If you want you can try with this java example:
|
||||
|
||||
```java
|
||||
/*Hello.java*/
|
||||
class Hello {
|
||||
int println(){
|
||||
return 0;
|
||||
}
|
||||
int test() {
|
||||
String s = "Hello World";
|
||||
System.out.println(s);
|
||||
s = null;
|
||||
println();
|
||||
return s.length();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notice that only `System.out.println` is being detected.
|
||||
|
||||
All set! You are ready to create your own Checkers! Infer is an open source
|
||||
project and you are more than welcome to contribute. Take a look at the
|
||||
[Github](https://github.com/facebook/infer/) page and feel free to fork or even
|
||||
open an issue if you're facing any trouble.
|
Loading…
Reference in new issue