Summary: Looks for fields modified outside of synchronization in classes labelled ThreadSafe Reviewed By: sblackshear Differential Revision: D3956467 fbshipit-source-id: 5c86e7fmaster
parent
5e2e7b88aa
commit
3d1eba890a
@ -0,0 +1,186 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2016 - present Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(** I want to use various powersets instead of just variables like Var.Set
|
||||||
|
For example to track the analogues of attributes
|
||||||
|
I will do this running forwards later, but backwards for now.
|
||||||
|
*)
|
||||||
|
|
||||||
|
open! Utils
|
||||||
|
|
||||||
|
module F = Format
|
||||||
|
module L = Logging
|
||||||
|
|
||||||
|
module PPString = PrettyPrintable.MakePPSet(struct
|
||||||
|
type t = string
|
||||||
|
let compare = string_compare
|
||||||
|
let pp_element fmt s = F.fprintf fmt "%s" s
|
||||||
|
end)
|
||||||
|
|
||||||
|
module PPrawpath = PrettyPrintable.MakePPSet(struct
|
||||||
|
type t = AccessPath.raw
|
||||||
|
let compare = AccessPath.raw_compare
|
||||||
|
let pp_element = AccessPath.pp_raw
|
||||||
|
end)
|
||||||
|
|
||||||
|
module LocksDomain = AbstractDomain.FiniteSet(PPString)
|
||||||
|
|
||||||
|
module PathDomain = AbstractDomain.FiniteSet(PPrawpath)
|
||||||
|
|
||||||
|
module ReadWriteDomain = AbstractDomain.Pair (PathDomain) (PathDomain)
|
||||||
|
|
||||||
|
module CombinedDomain = AbstractDomain.Pair (LocksDomain) (ReadWriteDomain)
|
||||||
|
(* a typical element is (){locked}, {vars and fields}) *)
|
||||||
|
|
||||||
|
|
||||||
|
module TransferFunctions (CFG : ProcCfg.S) = struct
|
||||||
|
module CFG = CFG
|
||||||
|
module Domain = CombinedDomain
|
||||||
|
type extras = ProcData.no_extras
|
||||||
|
|
||||||
|
let is_lock_procedure pn = Procname.equal pn ModelBuiltins.__set_locked_attribute
|
||||||
|
|
||||||
|
let is_unlock_procedure pn = Procname.equal pn ModelBuiltins.__delete_locked_attribute
|
||||||
|
|
||||||
|
let add_path_to_state exp typ pathdomainstate =
|
||||||
|
match AccessPath.of_exp exp typ ~f_resolve_id:(fun _ -> None) with
|
||||||
|
| Some rawpath -> PathDomain.add rawpath pathdomainstate
|
||||||
|
| None -> pathdomainstate
|
||||||
|
|
||||||
|
|
||||||
|
let exec_instr ((lockstate,(readstate,writestate)) as astate) { ProcData.pdesc; } _ =
|
||||||
|
let is_unprotected lockstate =
|
||||||
|
(not (Cfg.Procdesc.is_java_synchronized pdesc)) && (LocksDomain.is_empty lockstate)
|
||||||
|
in
|
||||||
|
function
|
||||||
|
| Sil.Call (_, Const (Cfun pn), _, _, _) ->
|
||||||
|
if is_lock_procedure pn
|
||||||
|
then
|
||||||
|
((LocksDomain.add "locked" lockstate), (readstate,writestate))
|
||||||
|
else if is_unlock_procedure pn
|
||||||
|
then
|
||||||
|
((LocksDomain.remove "locked" lockstate) , (readstate,writestate))
|
||||||
|
else
|
||||||
|
astate
|
||||||
|
|
||||||
|
| Sil.Store ((Lfield ( _, _, typ) as lhsfield) , _, _, _) ->
|
||||||
|
if is_unprotected lockstate then (* abstracts no lock being held*)
|
||||||
|
(lockstate, (readstate, add_path_to_state lhsfield typ writestate))
|
||||||
|
else astate
|
||||||
|
|
||||||
|
(* Note: it appears that the third arg of Store is never an Lfield in the targets of,
|
||||||
|
our translations, which is why we have not covered that case. *)
|
||||||
|
| Sil.Store (_, _, Lfield _, _) ->
|
||||||
|
failwith "Unexpected store instruction with rhs field"
|
||||||
|
|
||||||
|
| Sil.Load (_, (Lfield ( _, _, typ) as rhsfield) , _, _) ->
|
||||||
|
if is_unprotected lockstate then (* abstracts no lock being held*)
|
||||||
|
(lockstate, (add_path_to_state rhsfield typ readstate, writestate))
|
||||||
|
else astate
|
||||||
|
|
||||||
|
| _ ->
|
||||||
|
astate
|
||||||
|
end
|
||||||
|
|
||||||
|
module Analyzer =
|
||||||
|
AbstractInterpreter.Make
|
||||||
|
(ProcCfg.Normal)
|
||||||
|
(Scheduler.ReversePostorder)
|
||||||
|
(TransferFunctions)
|
||||||
|
|
||||||
|
let method_analysis { Callbacks.proc_desc; tenv; } =
|
||||||
|
match Analyzer.compute_post (ProcData.make_default proc_desc tenv) with
|
||||||
|
| Some post -> (* I am printing to commandline and out to cater to javac and buck*)
|
||||||
|
(L.stdout "\n Procedure: %s@ "
|
||||||
|
(Procname.to_string (Cfg.Procdesc.get_proc_name proc_desc) )
|
||||||
|
);
|
||||||
|
L.stdout "\n POST: %a\n" CombinedDomain.pp post;
|
||||||
|
(L.out "\n Procedure: %s@ "
|
||||||
|
(Procname.to_string (Cfg.Procdesc.get_proc_name proc_desc) )
|
||||||
|
);
|
||||||
|
L.out "\n POST: %a\n" CombinedDomain.pp post
|
||||||
|
| None -> ()
|
||||||
|
|
||||||
|
(* a results table is a Map where a key is an a procedure environment,
|
||||||
|
i.e., something of type Idenv.t * Tenv.t * Procname.t * Cfg.Procdesc.t
|
||||||
|
*)
|
||||||
|
module ResultsTableType = Map.Make (struct
|
||||||
|
type t = Idenv.t * Tenv.t * Procname.t * Cfg.Procdesc.t
|
||||||
|
let compare (_, _, pn1, _) (_,_,pn2,_) = Procname.compare pn1 pn2
|
||||||
|
end)
|
||||||
|
|
||||||
|
let should_analyze_proc (_,_,proc_name,proc_desc) =
|
||||||
|
(Procname.is_constructor proc_name) || (Cfg.Procdesc.get_access proc_desc <> PredSymb.Private)
|
||||||
|
|
||||||
|
(* creates a map from proc_envs to postconditions *)
|
||||||
|
let make_results_table file_env =
|
||||||
|
let procs_to_analyze = IList.filter should_analyze_proc file_env
|
||||||
|
in
|
||||||
|
(* make a Map sending each element e of list l to (f e) *)
|
||||||
|
let map_post_computation_over_procs f l =
|
||||||
|
IList.fold_left (fun m p -> ResultsTableType.add p (f p) m
|
||||||
|
) ResultsTableType.empty l
|
||||||
|
in
|
||||||
|
let compute_post_for_procedure = (* takes proc_env as arg *)
|
||||||
|
fun (_, tenv, _, proc_desc) ->
|
||||||
|
match Analyzer.compute_post (ProcData.make_default proc_desc tenv) with
|
||||||
|
| Some post -> post
|
||||||
|
| None -> CombinedDomain.initial
|
||||||
|
in
|
||||||
|
map_post_computation_over_procs compute_post_for_procedure procs_to_analyze
|
||||||
|
|
||||||
|
(* For now, just checks if there is one active element amongst the posts of the analyzed methods.
|
||||||
|
This indicates that the method races with itself. To be refined later. *)
|
||||||
|
let process_results_table tab = (
|
||||||
|
let finalresult =
|
||||||
|
ResultsTableType.for_all (* check if writestate is empty for all postconditions*)
|
||||||
|
(fun _ ( _,( _, writestate)) -> (PathDomain.is_empty writestate)
|
||||||
|
)
|
||||||
|
tab
|
||||||
|
in
|
||||||
|
if finalresult then
|
||||||
|
begin
|
||||||
|
L.stdout "\n ***** \n THREADSAFE \n *****\n" ;
|
||||||
|
L.out "\n ***** \n THREADSAFE \n *****\n"
|
||||||
|
end
|
||||||
|
else begin
|
||||||
|
L.stdout "\n ***** \n RACY \n *****\n" ;
|
||||||
|
L.out "\n ***** \n RACY \n *****\n"
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
(* Currently we analyze if there is an @ThreadSafe annotation on at least one of
|
||||||
|
the classes in a file. This might be tightened in future or even broadened in future
|
||||||
|
based on other criteria *)
|
||||||
|
let should_analyze_file file_env =
|
||||||
|
IList.exists
|
||||||
|
(fun (_, tenv, pname, _) ->
|
||||||
|
AnnotationReachability.check_attributes Annotations.ia_is_thread_safe tenv pname
|
||||||
|
)
|
||||||
|
file_env
|
||||||
|
|
||||||
|
(*Gathers results by analyzing all the methods in a file, then post-processes
|
||||||
|
the results to check (approximation of) thread safety *)
|
||||||
|
(* file_env: (Idenv.t * Tenv.t * Procname.t * Cfg.Procdesc.t) list *)
|
||||||
|
let file_analysis _ _ _ file_env =
|
||||||
|
begin L.stdout "\n THREAD SAFETY CHECKER \n\n";
|
||||||
|
if should_analyze_file file_env then
|
||||||
|
process_results_table
|
||||||
|
(make_results_table file_env)
|
||||||
|
end
|
||||||
|
|
||||||
|
(*
|
||||||
|
Todo:
|
||||||
|
0. Refactor abstract domain to use records rather than tuples
|
||||||
|
1. Track line numbers of accesses
|
||||||
|
2. Track protected writes and reads, too; if we have a write and a read where
|
||||||
|
one is non-protected then we have potential race (including protected write, unprotected read
|
||||||
|
3. Output error message when potential race found
|
||||||
|
4. Lotsa other stuff
|
||||||
|
*)
|
@ -1,104 +0,0 @@
|
|||||||
(*
|
|
||||||
* Copyright (c) 2016 - present Facebook, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the BSD style license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree. An additional grant
|
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
|
||||||
*)
|
|
||||||
|
|
||||||
(** I want to use various powersets instead of just variables like Var.Set
|
|
||||||
For example to track the analogues of attributes
|
|
||||||
I will do this running forwards later, but backwards for now.
|
|
||||||
*)
|
|
||||||
|
|
||||||
open! Utils
|
|
||||||
|
|
||||||
module F = Format
|
|
||||||
module L = Logging
|
|
||||||
|
|
||||||
module PPstring = PrettyPrintable.MakePPSet(struct
|
|
||||||
type nonrec t = string
|
|
||||||
let compare = string_compare
|
|
||||||
let pp_element fmt s = F.fprintf fmt "%s" s
|
|
||||||
end)
|
|
||||||
|
|
||||||
module PPrawpath = PrettyPrintable.MakePPSet(struct
|
|
||||||
type nonrec t = AccessPath.raw
|
|
||||||
let compare = AccessPath.raw_compare
|
|
||||||
let pp_element = AccessPath.pp_raw
|
|
||||||
end)
|
|
||||||
|
|
||||||
module LocksDomain = AbstractDomain.FiniteSet(PPstring)
|
|
||||||
|
|
||||||
module PathDomain = AbstractDomain.FiniteSet(PPrawpath)
|
|
||||||
|
|
||||||
module ReadWriteDomain = AbstractDomain.Pair (PathDomain) (PathDomain)
|
|
||||||
|
|
||||||
module CombinedDomain = AbstractDomain.Pair (LocksDomain) (ReadWriteDomain)
|
|
||||||
(* a typical element is (){locked}, {vars and fields}) *)
|
|
||||||
|
|
||||||
module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
||||||
module CFG = CFG
|
|
||||||
module Domain = CombinedDomain
|
|
||||||
type extras = ProcData.no_extras
|
|
||||||
|
|
||||||
let is_lock_procedure pn = Procname.equal pn ModelBuiltins.__set_locked_attribute
|
|
||||||
|
|
||||||
let is_unlock_procedure pn = Procname.equal pn ModelBuiltins.__delete_locked_attribute
|
|
||||||
|
|
||||||
let add_path_to_state exp typ pathdomainstate =
|
|
||||||
match AccessPath.of_exp exp typ ~f_resolve_id:(fun _ -> None) with
|
|
||||||
| Some rawpath -> PathDomain.add rawpath pathdomainstate
|
|
||||||
| None -> pathdomainstate
|
|
||||||
|
|
||||||
|
|
||||||
let exec_instr ((lockstate,(readstate,writestate)) as astate) _ _ = function
|
|
||||||
| Sil.Call (_, Exp.Const (Const.Cfun pn), _, _, _) ->
|
|
||||||
if is_lock_procedure pn
|
|
||||||
then
|
|
||||||
((LocksDomain.add "locked" lockstate), (readstate,writestate))
|
|
||||||
else if is_unlock_procedure pn
|
|
||||||
then
|
|
||||||
((LocksDomain.remove "locked" lockstate) , (readstate,writestate))
|
|
||||||
else
|
|
||||||
astate
|
|
||||||
|
|
||||||
| Sil.Store ((Exp.Lfield ( _, _, typ) as lhsfield) , _, _, _) ->
|
|
||||||
if LocksDomain.is_empty lockstate then (* abstracts no lock being held*)
|
|
||||||
(lockstate, (readstate, add_path_to_state lhsfield typ writestate))
|
|
||||||
else astate
|
|
||||||
|
|
||||||
(* Note: it appears that the third arg of Store is never an Lfield in the targets of,
|
|
||||||
our translations, which is why we have not covered that case. *)
|
|
||||||
| Sil.Store (_, _, Exp.Lfield _, _) ->
|
|
||||||
failwith "Unexpected store instruction with rhs field"
|
|
||||||
|
|
||||||
| Sil.Load (_, (Exp.Lfield ( _, _, typ) as rhsfield) , _, _) ->
|
|
||||||
if LocksDomain.is_empty lockstate then (* abstracts no lock being held*)
|
|
||||||
(lockstate, (add_path_to_state rhsfield typ readstate, writestate))
|
|
||||||
else astate
|
|
||||||
|
|
||||||
| _ ->
|
|
||||||
astate
|
|
||||||
end
|
|
||||||
|
|
||||||
module Analyzer =
|
|
||||||
AbstractInterpreter.Make
|
|
||||||
(ProcCfg.Normal)
|
|
||||||
(Scheduler.ReversePostorder)
|
|
||||||
(TransferFunctions)
|
|
||||||
|
|
||||||
let checker { Callbacks.proc_desc; tenv; } =
|
|
||||||
match Analyzer.compute_post (ProcData.make_default proc_desc tenv) with
|
|
||||||
| Some post -> (* I am printing to commandline and out to cater to javac and buck*)
|
|
||||||
((L.stdout "\n Procedure: %s@ "
|
|
||||||
(Procname.to_string (Cfg.Procdesc.get_proc_name proc_desc) )
|
|
||||||
);
|
|
||||||
L.stdout "\n POST: %a\n" CombinedDomain.pp post;
|
|
||||||
(L.out "\n Procedure: %s@ "
|
|
||||||
(Procname.to_string (Cfg.Procdesc.get_proc_name proc_desc) )
|
|
||||||
);
|
|
||||||
L.out "\n POST: %a\n" CombinedDomain.pp post
|
|
||||||
)
|
|
||||||
| None -> ()
|
|
Loading…
Reference in new issue