6.3 KiB
id | title |
---|---|
adding-checkers | 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. 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 and do some exercises.
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:
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:
Sil.Call (_, Sil.Const (Sil.Cfun pn), _, loc, _)
This pattern matches every function call. In our code, it would look like:
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:
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:
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:
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:
/*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 page and feel free to fork or even open an issue if you're facing any trouble.