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