Reviewed By: jeremydubreil Differential Revision: D6359547 fbshipit-source-id: 3cb467amaster
parent
d26e227267
commit
181959216b
@ -0,0 +1,55 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2013 - 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.
|
||||||
|
*)
|
||||||
|
|
||||||
|
open! IStd
|
||||||
|
module L = Logging
|
||||||
|
module F = Format
|
||||||
|
|
||||||
|
(** Android lifecycle types and their lifecycle methods that are called by the framework *)
|
||||||
|
|
||||||
|
let on_destroy = "onDestroy"
|
||||||
|
|
||||||
|
let on_destroy_view = "onDestroyView"
|
||||||
|
|
||||||
|
let drawable_prefix = "R$drawable"
|
||||||
|
|
||||||
|
(** return true if [pname] is a special lifecycle cleanup method *)
|
||||||
|
let is_destroy_method pname =
|
||||||
|
match pname with
|
||||||
|
| Typ.Procname.Java pname_java ->
|
||||||
|
let method_name = Typ.Procname.java_get_method pname_java in
|
||||||
|
String.equal method_name on_destroy || String.equal method_name on_destroy_view
|
||||||
|
| _ ->
|
||||||
|
false
|
||||||
|
|
||||||
|
|
||||||
|
let is_subtype_package_class tenv tname package classname =
|
||||||
|
PatternMatch.is_subtype tenv tname (Typ.Name.Java.from_package_class package classname)
|
||||||
|
|
||||||
|
|
||||||
|
let is_autocloseable tenv tname = is_subtype_package_class tenv tname "java.lang" "AutoCloseable"
|
||||||
|
|
||||||
|
let is_context tenv tname = is_subtype_package_class tenv tname "android.content" "Context"
|
||||||
|
|
||||||
|
let is_application tenv tname = is_subtype_package_class tenv tname "android.app" "Application"
|
||||||
|
|
||||||
|
let is_activity tenv tname = is_subtype_package_class tenv tname "android.app" "Activity"
|
||||||
|
|
||||||
|
let is_view tenv tname = is_subtype_package_class tenv tname "android.view" "View"
|
||||||
|
|
||||||
|
let is_fragment tenv tname =
|
||||||
|
is_subtype_package_class tenv tname "android.app" "Fragment"
|
||||||
|
|| is_subtype_package_class tenv tname "android.support.v4.app" "Fragment"
|
||||||
|
|
||||||
|
|
||||||
|
(** return true if [class_name] is the name of a class that belong to the Android framework *)
|
||||||
|
let is_android_lib_class class_name =
|
||||||
|
let class_str = Typ.Name.name class_name in
|
||||||
|
String.is_prefix ~prefix:"android" class_str || String.is_prefix ~prefix:"com.android" class_str
|
||||||
|
|
@ -1,129 +0,0 @@
|
|||||||
(*
|
|
||||||
* Copyright (c) 2013 - 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.
|
|
||||||
*)
|
|
||||||
|
|
||||||
open! IStd
|
|
||||||
module L = Logging
|
|
||||||
module F = Format
|
|
||||||
|
|
||||||
(** Android lifecycle types and their lifecycle methods that are called by the framework *)
|
|
||||||
|
|
||||||
let on_destroy = "onDestroy"
|
|
||||||
|
|
||||||
let on_destroy_view = "onDestroyView"
|
|
||||||
|
|
||||||
let drawable_prefix = "R$drawable"
|
|
||||||
|
|
||||||
(** return true if [pname] is a special lifecycle cleanup method *)
|
|
||||||
let is_destroy_method pname =
|
|
||||||
match pname with
|
|
||||||
| Typ.Procname.Java pname_java ->
|
|
||||||
let method_name = Typ.Procname.java_get_method pname_java in
|
|
||||||
String.equal method_name on_destroy || String.equal method_name on_destroy_view
|
|
||||||
| _ ->
|
|
||||||
false
|
|
||||||
|
|
||||||
|
|
||||||
let android_lifecycles =
|
|
||||||
let android_content = "android.content" in
|
|
||||||
let android_app = "android.app" in
|
|
||||||
let fragment_lifecycle =
|
|
||||||
[ "onInflate"
|
|
||||||
; "onAttach"
|
|
||||||
; "onCreate"
|
|
||||||
; "onCreateView"
|
|
||||||
; "onViewCreated"
|
|
||||||
; "onActivityCreated"
|
|
||||||
; "onViewStateRestored"
|
|
||||||
; "onStart"
|
|
||||||
; "onResume"
|
|
||||||
; "onPause"
|
|
||||||
; "onSaveInstanceState"
|
|
||||||
; "onStop"
|
|
||||||
; on_destroy_view
|
|
||||||
; on_destroy
|
|
||||||
; "onDetach" ]
|
|
||||||
in
|
|
||||||
[ (android_content, "ContentProvider", ["onCreate"])
|
|
||||||
; ( android_app
|
|
||||||
, "Activity"
|
|
||||||
, [ "onCreate"
|
|
||||||
; "onStart"
|
|
||||||
; "onRestoreInstanceState"
|
|
||||||
; "onPostCreate"
|
|
||||||
; "onResume"
|
|
||||||
; "onPostResume"
|
|
||||||
; "onCreateDescription"
|
|
||||||
; "onSaveInstanceState"
|
|
||||||
; "onPause"
|
|
||||||
; "onStop"
|
|
||||||
; on_destroy ] )
|
|
||||||
; ( android_app
|
|
||||||
, "Service"
|
|
||||||
, ["onCreate"; "onStart"; "onStartCommand"; "onBind"; "onUnbind"; on_destroy] )
|
|
||||||
; (android_content, "BroadcastReceiever", ["onReceive"])
|
|
||||||
; (android_app, "Fragment", fragment_lifecycle)
|
|
||||||
; (* this is the pre-Android 3.0 Fragment type (can also be used post-3.0) *)
|
|
||||||
("android.support.v4.app", "Fragment", fragment_lifecycle) ]
|
|
||||||
|
|
||||||
|
|
||||||
let is_subtype_package_class tenv tname package classname =
|
|
||||||
PatternMatch.is_subtype tenv tname (Typ.Name.Java.from_package_class package classname)
|
|
||||||
|
|
||||||
|
|
||||||
let is_autocloseable tenv tname = is_subtype_package_class tenv tname "java.lang" "AutoCloseable"
|
|
||||||
|
|
||||||
let is_context tenv tname = is_subtype_package_class tenv tname "android.content" "Context"
|
|
||||||
|
|
||||||
let is_application tenv tname = is_subtype_package_class tenv tname "android.app" "Application"
|
|
||||||
|
|
||||||
let is_activity tenv tname = is_subtype_package_class tenv tname "android.app" "Activity"
|
|
||||||
|
|
||||||
let is_view tenv tname = is_subtype_package_class tenv tname "android.view" "View"
|
|
||||||
|
|
||||||
let is_fragment tenv tname =
|
|
||||||
is_subtype_package_class tenv tname "android.app" "Fragment"
|
|
||||||
|| is_subtype_package_class tenv tname "android.support.v4.app" "Fragment"
|
|
||||||
|
|
||||||
|
|
||||||
(** return true if [class_name] is the name of a class that belong to the Android framework *)
|
|
||||||
let is_android_lib_class class_name =
|
|
||||||
let class_str = Typ.Name.name class_name in
|
|
||||||
String.is_prefix ~prefix:"android" class_str || String.is_prefix ~prefix:"com.android" class_str
|
|
||||||
|
|
||||||
|
|
||||||
(** given an Android framework type mangled string [lifecycle_typ] (e.g., android.app.Activity) and
|
|
||||||
a list of method names [lifecycle_procs_strs], get the appropriate typ and procnames *)
|
|
||||||
let get_lifecycle_for_framework_typ_opt tenv lifecycle_typ lifecycle_proc_strs =
|
|
||||||
match Tenv.lookup tenv lifecycle_typ with
|
|
||||||
| Some {methods} ->
|
|
||||||
(* TODO (t4645631): collect the procedures for which is_java is returning false *)
|
|
||||||
let lookup_proc lifecycle_proc =
|
|
||||||
List.find_exn
|
|
||||||
~f:(fun decl_proc ->
|
|
||||||
match decl_proc with
|
|
||||||
| Typ.Procname.Java decl_proc_java ->
|
|
||||||
String.equal lifecycle_proc (Typ.Procname.java_get_method decl_proc_java)
|
|
||||||
| _ ->
|
|
||||||
false)
|
|
||||||
methods
|
|
||||||
in
|
|
||||||
(* convert each of the framework lifecycle proc strings to a lifecycle method procname *)
|
|
||||||
let lifecycle_procs =
|
|
||||||
List.fold
|
|
||||||
~f:(fun lifecycle_procs lifecycle_proc_str ->
|
|
||||||
try lookup_proc lifecycle_proc_str :: lifecycle_procs with Not_found -> lifecycle_procs)
|
|
||||||
~init:[] lifecycle_proc_strs
|
|
||||||
in
|
|
||||||
lifecycle_procs
|
|
||||||
| _ ->
|
|
||||||
[]
|
|
||||||
|
|
||||||
|
|
||||||
(** return the complete list of (package, lifecycle_classname, lifecycle_methods) trios *)
|
|
||||||
let get_lifecycles = android_lifecycles
|
|
@ -1,72 +0,0 @@
|
|||||||
(*
|
|
||||||
* Copyright (c) 2013 - 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.
|
|
||||||
*)
|
|
||||||
|
|
||||||
open! IStd
|
|
||||||
module L = Logging
|
|
||||||
module F = Format
|
|
||||||
|
|
||||||
(** Automatically create a harness method to exercise code under test *)
|
|
||||||
|
|
||||||
(** if [struct_typ] is a lifecycle type, generate a list of (method call, receiver) pairs
|
|
||||||
constituting a lifecycle trace *)
|
|
||||||
let try_create_lifecycle_trace name lifecycle_name lifecycle_procs tenv =
|
|
||||||
match name with
|
|
||||||
| Typ.JavaClass _ ->
|
|
||||||
if PatternMatch.is_subtype tenv name lifecycle_name
|
|
||||||
&& not (AndroidFramework.is_android_lib_class name)
|
|
||||||
then
|
|
||||||
let ptr_to_struct_typ = Some (Typ.mk (Tptr (Typ.mk (Tstruct name), Pk_pointer))) in
|
|
||||||
List.fold
|
|
||||||
~f:(fun trace lifecycle_proc ->
|
|
||||||
(* given a lifecycle subclass T, resolve the call T.lifecycle_proc() to the procname
|
|
||||||
* that will actually be called at runtime *)
|
|
||||||
let resolved_proc = SymExec.resolve_method tenv name lifecycle_proc in
|
|
||||||
(resolved_proc, ptr_to_struct_typ) :: trace)
|
|
||||||
~init:[] lifecycle_procs
|
|
||||||
else []
|
|
||||||
| _ ->
|
|
||||||
[]
|
|
||||||
|
|
||||||
|
|
||||||
(** generate a harness for a lifecycle type in an Android application *)
|
|
||||||
let create_harness cfg cg tenv =
|
|
||||||
List.iter
|
|
||||||
~f:(fun (pkg, clazz, lifecycle_methods) ->
|
|
||||||
let typname = Typ.Name.Java.from_package_class pkg clazz in
|
|
||||||
let framework_procs =
|
|
||||||
AndroidFramework.get_lifecycle_for_framework_typ_opt tenv typname lifecycle_methods
|
|
||||||
in
|
|
||||||
(* iterate through the type environment and generate a lifecycle harness for each
|
|
||||||
subclass of [lifecycle_typ] *)
|
|
||||||
(* TODO: instead of iterating through the type environment, interate through the types
|
|
||||||
declared in [cfg] *)
|
|
||||||
Tenv.iter
|
|
||||||
(fun name _ ->
|
|
||||||
match try_create_lifecycle_trace name typname framework_procs tenv with
|
|
||||||
| [] ->
|
|
||||||
()
|
|
||||||
| lifecycle_trace ->
|
|
||||||
let harness_procname =
|
|
||||||
let harness_cls_name = Typ.Name.name name in
|
|
||||||
let pname =
|
|
||||||
Typ.Procname.Java
|
|
||||||
(Typ.Procname.java
|
|
||||||
(Typ.Name.Java.from_string harness_cls_name)
|
|
||||||
None "InferGeneratedHarness" [] Typ.Procname.Static)
|
|
||||||
in
|
|
||||||
match pname with
|
|
||||||
| Typ.Procname.Java harness_procname ->
|
|
||||||
harness_procname
|
|
||||||
| _ ->
|
|
||||||
assert false
|
|
||||||
in
|
|
||||||
Inhabit.inhabit_trace tenv lifecycle_trace harness_procname cg cfg)
|
|
||||||
tenv)
|
|
||||||
AndroidFramework.get_lifecycles
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
|||||||
(*
|
|
||||||
* Copyright (c) 2013 - 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.
|
|
||||||
*)
|
|
||||||
|
|
||||||
open! IStd
|
|
||||||
|
|
||||||
(** Automatically create a harness method to exercise code under test *)
|
|
||||||
|
|
||||||
val create_harness : Cfg.t -> Cg.t -> Tenv.t -> unit
|
|
||||||
(** Generate a harness method for exe_env and add it to the execution environment *)
|
|
@ -1,315 +0,0 @@
|
|||||||
(*
|
|
||||||
* Copyright (c) 2013 - 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.
|
|
||||||
*)
|
|
||||||
|
|
||||||
open! IStd
|
|
||||||
|
|
||||||
(** Generate a procedure that calls a given sequence of methods. Useful for harness/test
|
|
||||||
* generation. *)
|
|
||||||
|
|
||||||
module L = Logging
|
|
||||||
module F = Format
|
|
||||||
module P = Printf
|
|
||||||
module IdSet = Ident.IdentSet
|
|
||||||
module TypSet = Typ.Set
|
|
||||||
module TypMap = Typ.Map
|
|
||||||
|
|
||||||
type lifecycle_trace = (Typ.Procname.t * Typ.t option) list
|
|
||||||
|
|
||||||
(** list of instrs and temporary variables created during inhabitation and a cache of types that
|
|
||||||
* have already been inhabited *)
|
|
||||||
type env =
|
|
||||||
{ instrs: Sil.instr list
|
|
||||||
; cache: Exp.t TypMap.t
|
|
||||||
; (* set of types currently being inhabited. consult to prevent infinite recursion *)
|
|
||||||
cur_inhabiting: TypSet.t
|
|
||||||
; pc: Location.t
|
|
||||||
; harness_name: Typ.Procname.java }
|
|
||||||
|
|
||||||
let procdesc_from_name cfg pname =
|
|
||||||
let pdesc_ref = ref None in
|
|
||||||
Cfg.iter_proc_desc cfg (fun cfg_pname pdesc ->
|
|
||||||
if Typ.Procname.equal cfg_pname pname then pdesc_ref := Some pdesc ) ;
|
|
||||||
!pdesc_ref
|
|
||||||
|
|
||||||
|
|
||||||
let formals_from_name cfg pname =
|
|
||||||
match procdesc_from_name cfg pname with Some pdesc -> Procdesc.get_formals pdesc | None -> []
|
|
||||||
|
|
||||||
|
|
||||||
(** add an instruction to the env, update tmp_vars, and bump the pc *)
|
|
||||||
let env_add_instr instr env =
|
|
||||||
let incr_pc pc = {pc with Location.line= pc.Location.line + 1} in
|
|
||||||
{env with instrs= instr :: env.instrs; pc= incr_pc env.pc}
|
|
||||||
|
|
||||||
|
|
||||||
(** call flags for an allocation or call to a constructor *)
|
|
||||||
let cf_alloc = CallFlags.default
|
|
||||||
|
|
||||||
let fun_exp_from_name proc_name = Exp.Const (Const.Cfun proc_name)
|
|
||||||
|
|
||||||
let local_name_cntr = ref 0
|
|
||||||
|
|
||||||
let create_fresh_local_name () =
|
|
||||||
incr local_name_cntr ;
|
|
||||||
"dummy_local" ^ string_of_int !local_name_cntr
|
|
||||||
|
|
||||||
|
|
||||||
(** more forgiving variation of List.tl that won't raise an exception on the empty list *)
|
|
||||||
let tl_or_empty l = if List.is_empty l then l else List.tl_exn l
|
|
||||||
|
|
||||||
let get_non_receiver_formals formals = tl_or_empty formals
|
|
||||||
|
|
||||||
(** create Sil corresponding to x = new typ() or x = new typ[]. For ordinary allocation, sizeof_typ
|
|
||||||
* and ret_typ should be the same, but arrays are slightly odd in that sizeof_typ will have a size
|
|
||||||
* component but the size component of ret_typ is always -1. *)
|
|
||||||
let inhabit_alloc sizeof_typ sizeof_len ret_typ alloc_kind env =
|
|
||||||
let retval = Ident.create_fresh Ident.knormal in
|
|
||||||
let inhabited_exp = Exp.Var retval in
|
|
||||||
let call_instr =
|
|
||||||
let fun_new = fun_exp_from_name alloc_kind in
|
|
||||||
let sizeof_exp =
|
|
||||||
Exp.Sizeof {typ= sizeof_typ; nbytes= None; dynamic_length= sizeof_len; subtype= Subtype.exact}
|
|
||||||
in
|
|
||||||
let args = [(sizeof_exp, Typ.mk (Tptr (ret_typ, Typ.Pk_pointer)))] in
|
|
||||||
Sil.Call (Some (retval, ret_typ), fun_new, args, env.pc, cf_alloc)
|
|
||||||
in
|
|
||||||
(inhabited_exp, env_add_instr call_instr env)
|
|
||||||
|
|
||||||
|
|
||||||
(* TODO: this should be done in a differnt way: just make typ a param of the harness procedure *)
|
|
||||||
|
|
||||||
(** find or create a Sil expression with type typ *)
|
|
||||||
let rec inhabit_typ tenv typ cfg env =
|
|
||||||
try (TypMap.find typ env.cache, env) with Not_found ->
|
|
||||||
let inhabit_internal typ env =
|
|
||||||
match typ.Typ.desc with
|
|
||||||
| Typ.Tptr ({desc= Tarray (inner_typ, Some _, _)}, Typ.Pk_pointer) ->
|
|
||||||
let len = Exp.Const (Const.Cint IntLit.one) in
|
|
||||||
let arr_typ = Typ.mk (Tarray (inner_typ, Some IntLit.one, None)) in
|
|
||||||
inhabit_alloc arr_typ (Some len) typ BuiltinDecl.__new_array env
|
|
||||||
| Typ.Tptr (typ, Typ.Pk_pointer) ->
|
|
||||||
(* TODO (t4575417): this case does not work correctly for enums, but they are currently
|
|
||||||
* broken in Infer anyway (see t4592290) *)
|
|
||||||
let allocated_obj_exp, env = inhabit_alloc typ None typ BuiltinDecl.__new env in
|
|
||||||
(* select methods that are constructors and won't force us into infinite recursion because
|
|
||||||
* we are already inhabiting one of their argument types *)
|
|
||||||
let get_all_suitable_constructors (typ: Typ.t) =
|
|
||||||
match typ.desc with
|
|
||||||
| Tstruct name when Typ.Name.is_class name -> (
|
|
||||||
match Tenv.lookup tenv name with
|
|
||||||
| Some {methods} ->
|
|
||||||
let is_suitable_constructor p =
|
|
||||||
let try_get_non_receiver_formals p =
|
|
||||||
get_non_receiver_formals (formals_from_name cfg p)
|
|
||||||
in
|
|
||||||
Typ.Procname.is_constructor p
|
|
||||||
&& List.for_all
|
|
||||||
~f:(fun (_, typ) -> not (TypSet.mem typ env.cur_inhabiting))
|
|
||||||
(try_get_non_receiver_formals p)
|
|
||||||
in
|
|
||||||
List.filter ~f:(fun p -> is_suitable_constructor p) methods
|
|
||||||
| _ ->
|
|
||||||
[] )
|
|
||||||
| _ ->
|
|
||||||
[]
|
|
||||||
in
|
|
||||||
let env, typ_class_name =
|
|
||||||
match get_all_suitable_constructors typ with
|
|
||||||
| constructor :: _ ->
|
|
||||||
(* arbitrarily choose a constructor for typ and invoke it. eventually, we may want to
|
|
||||||
* nondeterministically call all possible constructors instead *)
|
|
||||||
let env = inhabit_constructor tenv constructor (allocated_obj_exp, typ) cfg env in
|
|
||||||
(* try to get the unqualified name as a class (e.g., Object for java.lang.Object so we
|
|
||||||
* we can use it as a descriptive local variable name in the harness *)
|
|
||||||
let typ_class_name =
|
|
||||||
match constructor with
|
|
||||||
| Typ.Procname.Java pname_java ->
|
|
||||||
Typ.Procname.java_get_simple_class_name pname_java
|
|
||||||
| _ ->
|
|
||||||
create_fresh_local_name ()
|
|
||||||
in
|
|
||||||
(env, Mangled.from_string typ_class_name)
|
|
||||||
| [] ->
|
|
||||||
(env, Mangled.from_string (create_fresh_local_name ()))
|
|
||||||
in
|
|
||||||
(* add the instructions *& local = [allocated_obj_exp]; id = *& local, where local and id are
|
|
||||||
* both fresh. the only point of this is to add a descriptive local name that makes error
|
|
||||||
* reports from the harness look nicer -- it's not necessary to make symbolic execution work *)
|
|
||||||
let fresh_local_exp =
|
|
||||||
Exp.Lvar (Pvar.mk typ_class_name (Typ.Procname.Java env.harness_name))
|
|
||||||
in
|
|
||||||
let write_to_local_instr = Sil.Store (fresh_local_exp, typ, allocated_obj_exp, env.pc) in
|
|
||||||
let env' = env_add_instr write_to_local_instr env in
|
|
||||||
let fresh_id = Ident.create_fresh Ident.knormal in
|
|
||||||
let read_from_local_instr = Sil.Load (fresh_id, fresh_local_exp, typ, env'.pc) in
|
|
||||||
(Exp.Var fresh_id, env_add_instr read_from_local_instr env')
|
|
||||||
| Typ.Tint _ ->
|
|
||||||
(Exp.Const (Const.Cint IntLit.zero), env)
|
|
||||||
| Typ.Tfloat _ ->
|
|
||||||
(Exp.Const (Const.Cfloat 0.0), env)
|
|
||||||
| _ ->
|
|
||||||
L.die InternalError "Couldn't inhabit typ: %a" (Typ.pp Pp.text) typ
|
|
||||||
in
|
|
||||||
let inhabited_exp, env' =
|
|
||||||
inhabit_internal typ {env with cur_inhabiting= TypSet.add typ env.cur_inhabiting}
|
|
||||||
in
|
|
||||||
( inhabited_exp
|
|
||||||
, {env' with cache= TypMap.add typ inhabited_exp env.cache; cur_inhabiting= env.cur_inhabiting}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
(** inhabit each of the types in the formals list *)
|
|
||||||
and inhabit_args tenv formals cfg env =
|
|
||||||
let inhabit_arg (_, formal_typ) (args, env) =
|
|
||||||
let exp, env = inhabit_typ tenv formal_typ cfg env in
|
|
||||||
((exp, formal_typ) :: args, env)
|
|
||||||
in
|
|
||||||
List.fold_right ~f:inhabit_arg formals ~init:([], env)
|
|
||||||
|
|
||||||
|
|
||||||
(** create Sil that calls the constructor in constr_name on allocated_obj and inhabits the
|
|
||||||
* remaining arguments *)
|
|
||||||
and inhabit_constructor tenv constr_name (allocated_obj, obj_type) cfg env =
|
|
||||||
try
|
|
||||||
(* this lookup can fail when we try to get the procdesc of a procedure from a different
|
|
||||||
* module. this could be solved with a whole - program class hierarchy analysis *)
|
|
||||||
let args, env =
|
|
||||||
let non_receiver_formals = tl_or_empty (formals_from_name cfg constr_name) in
|
|
||||||
inhabit_args tenv non_receiver_formals cfg env
|
|
||||||
in
|
|
||||||
let constr_instr =
|
|
||||||
let fun_exp = fun_exp_from_name constr_name in
|
|
||||||
Sil.Call (None, fun_exp, (allocated_obj, obj_type) :: args, env.pc, CallFlags.default)
|
|
||||||
in
|
|
||||||
env_add_instr constr_instr env
|
|
||||||
with Not_found -> env
|
|
||||||
|
|
||||||
|
|
||||||
let inhabit_call_with_args procname procdesc args env =
|
|
||||||
let retval =
|
|
||||||
let ret_typ = Procdesc.get_ret_type procdesc in
|
|
||||||
let is_void = Typ.equal_desc ret_typ.Typ.desc Typ.Tvoid in
|
|
||||||
if is_void then None else Some (Ident.create_fresh Ident.knormal, ret_typ)
|
|
||||||
in
|
|
||||||
let call_instr =
|
|
||||||
let fun_exp = fun_exp_from_name procname in
|
|
||||||
let flags =
|
|
||||||
{CallFlags.default with CallFlags.cf_virtual= not (Typ.Procname.java_is_static procname)}
|
|
||||||
in
|
|
||||||
Sil.Call (retval, fun_exp, args, env.pc, flags)
|
|
||||||
in
|
|
||||||
env_add_instr call_instr env
|
|
||||||
|
|
||||||
|
|
||||||
(** create Sil that inhabits args to and calls proc_name *)
|
|
||||||
let inhabit_call tenv (procname, receiver) cfg env =
|
|
||||||
try
|
|
||||||
match procdesc_from_name cfg procname with
|
|
||||||
| Some procdesc ->
|
|
||||||
(* swap the type of the 'this' formal with the receiver type, if there is one *)
|
|
||||||
let formals =
|
|
||||||
match (Procdesc.get_formals procdesc, receiver) with
|
|
||||||
| (name, _) :: formals, Some receiver ->
|
|
||||||
(name, receiver) :: formals
|
|
||||||
| formals, None ->
|
|
||||||
formals
|
|
||||||
| [], Some _ ->
|
|
||||||
L.(die InternalError)
|
|
||||||
"Expected at least one formal to bind receiver to in method %a" Typ.Procname.pp
|
|
||||||
procname
|
|
||||||
in
|
|
||||||
let args, env = inhabit_args tenv formals cfg env in
|
|
||||||
inhabit_call_with_args procname procdesc args env
|
|
||||||
| None ->
|
|
||||||
env
|
|
||||||
with Not_found -> env
|
|
||||||
|
|
||||||
|
|
||||||
(** create a dummy file for the harness and associate them in the exe_env *)
|
|
||||||
let create_dummy_harness_filename harness_name =
|
|
||||||
let dummy_file_dir = Filename.temp_dir_name in
|
|
||||||
let file_str =
|
|
||||||
Typ.Procname.java_get_class_name harness_name ^ "_" ^ Typ.Procname.java_get_method harness_name
|
|
||||||
^ ".java"
|
|
||||||
in
|
|
||||||
Filename.concat dummy_file_dir file_str
|
|
||||||
|
|
||||||
|
|
||||||
(* TODO (t3040429): fill this file up with Java-like code that matches the SIL *)
|
|
||||||
|
|
||||||
(** write the SIL for the harness to a file *)
|
|
||||||
let write_harness_to_file harness_instrs harness_file_name =
|
|
||||||
let harness_file = Utils.create_outfile harness_file_name in
|
|
||||||
let pp_harness fmt =
|
|
||||||
List.iter
|
|
||||||
~f:(fun instr -> Format.fprintf fmt "%a@\n" (Sil.pp_instr Pp.text) instr)
|
|
||||||
harness_instrs
|
|
||||||
in
|
|
||||||
Utils.do_outf harness_file (fun outf -> pp_harness outf.fmt ; Utils.close_outf outf)
|
|
||||||
|
|
||||||
|
|
||||||
(** add the harness proc to the cg and make sure its callees can be looked up by sym execution *)
|
|
||||||
let add_harness_to_cg harness_name harness_node cg =
|
|
||||||
Cg.add_defined_node cg (Typ.Procname.Java harness_name) ;
|
|
||||||
List.iter
|
|
||||||
~f:(fun p -> Cg.add_edge cg (Typ.Procname.Java harness_name) p)
|
|
||||||
(Procdesc.Node.get_callees harness_node)
|
|
||||||
|
|
||||||
|
|
||||||
(** create and fill the appropriate nodes and add them to the harness cfg. also add the harness
|
|
||||||
* proc to the cg *)
|
|
||||||
let setup_harness_cfg harness_name env cg cfg =
|
|
||||||
(* each procedure has different scope: start names from id 0 *)
|
|
||||||
Ident.NameGenerator.reset () ;
|
|
||||||
let procname = Typ.Procname.Java harness_name in
|
|
||||||
let proc_attributes =
|
|
||||||
{ (ProcAttributes.default procname Config.Java) with
|
|
||||||
ProcAttributes.is_defined= true; loc= env.pc }
|
|
||||||
in
|
|
||||||
let procdesc = Cfg.create_proc_desc cfg proc_attributes in
|
|
||||||
let harness_node =
|
|
||||||
(* important to reverse the list or there will be scoping issues! *)
|
|
||||||
let instrs = List.rev env.instrs in
|
|
||||||
let nodekind = Procdesc.Node.Stmt_node "method_body" in
|
|
||||||
Procdesc.create_node procdesc env.pc nodekind instrs
|
|
||||||
in
|
|
||||||
let start_node, exit_node =
|
|
||||||
let create_node kind = Procdesc.create_node procdesc env.pc kind [] in
|
|
||||||
let start_kind = Procdesc.Node.Start_node procname in
|
|
||||||
let exit_kind = Procdesc.Node.Exit_node procname in
|
|
||||||
(create_node start_kind, create_node exit_kind)
|
|
||||||
in
|
|
||||||
Procdesc.set_start_node procdesc start_node ;
|
|
||||||
Procdesc.set_exit_node procdesc exit_node ;
|
|
||||||
Procdesc.Node.add_locals_ret_declaration start_node proc_attributes [] ;
|
|
||||||
Procdesc.node_set_succs_exn procdesc start_node [harness_node] [exit_node] ;
|
|
||||||
Procdesc.node_set_succs_exn procdesc harness_node [exit_node] [exit_node] ;
|
|
||||||
add_harness_to_cg harness_name harness_node cg
|
|
||||||
|
|
||||||
|
|
||||||
(** create a procedure named harness_name that calls each of the methods in trace in the specified
|
|
||||||
* order with the specified receiver and add it to the execution environment *)
|
|
||||||
let inhabit_trace tenv trace harness_name cg cfg =
|
|
||||||
if List.length trace > 0 then
|
|
||||||
let source_file = Cg.get_source cg in
|
|
||||||
let harness_filename = create_dummy_harness_filename harness_name in
|
|
||||||
let start_line = 1 in
|
|
||||||
let empty_env =
|
|
||||||
let pc = {Location.line= start_line; col= 1; file= source_file} in
|
|
||||||
{instrs= []; cache= TypMap.empty; pc; cur_inhabiting= TypSet.empty; harness_name}
|
|
||||||
in
|
|
||||||
(* invoke lifecycle methods *)
|
|
||||||
let env'' =
|
|
||||||
List.fold ~f:(fun env to_call -> inhabit_call tenv to_call cfg env) ~init:empty_env trace
|
|
||||||
in
|
|
||||||
try
|
|
||||||
setup_harness_cfg harness_name env'' cg cfg ;
|
|
||||||
write_harness_to_file (List.rev env''.instrs) harness_filename
|
|
||||||
with Not_found -> ()
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
|||||||
(*
|
|
||||||
* Copyright (c) 2013 - 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.
|
|
||||||
*)
|
|
||||||
|
|
||||||
open! IStd
|
|
||||||
|
|
||||||
(** Generate a procedure that calls a given sequence of methods. Useful for harness/test generation. *)
|
|
||||||
|
|
||||||
type lifecycle_trace = (Typ.Procname.t * Typ.t option) list
|
|
||||||
|
|
||||||
val inhabit_trace : Tenv.t -> lifecycle_trace -> Typ.Procname.java -> Cg.t -> Cfg.t -> unit
|
|
||||||
(** create a procedure named harness_name that calls each of the methods in trace add it to the
|
|
||||||
cg/cfg *)
|
|
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015 - 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package codetoanalyze.java.harness;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Test if harness generation understands basics of Activity lifecycle.
|
|
||||||
*/
|
|
||||||
public class BasicHarnessActivity extends Activity {
|
|
||||||
|
|
||||||
public BasicHarnessActivity(BasicHarnessActivity a) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object mObj;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
mObj = new Object();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
mObj = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
String s = mObj.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,16 +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.
|
|
||||||
|
|
||||||
TESTS_DIR = ../../..
|
|
||||||
|
|
||||||
ANALYZER = checkers
|
|
||||||
INFER_OPTIONS = --biabduction-only --android-harness --no-filtering --debug-exceptions
|
|
||||||
INFERPRINT_OPTIONS = --issues-fields "file,procedure,line_offset,bug_type" --issues-tests
|
|
||||||
SOURCES = \
|
|
||||||
BasicHarnessActivity.java \
|
|
||||||
|
|
||||||
include $(TESTS_DIR)/javac.make
|
|
@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015 - 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package codetoanalyze.java.harness;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
public class MyView extends View {
|
|
||||||
public MyView(Context ctx) {
|
|
||||||
super(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015 - 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package codetoanalyze.java.harness;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Test if type inhabitation can inhabit tricky types.
|
|
||||||
*/
|
|
||||||
public class TrickyParamsActivity extends Activity {
|
|
||||||
|
|
||||||
private Object mObj;
|
|
||||||
|
|
||||||
// we have to be able to inhabit all params or no methods from this
|
|
||||||
// class will be called by the harness
|
|
||||||
public TrickyParamsActivity(
|
|
||||||
Object o, int i, boolean b, char c, long l,
|
|
||||||
float f, double d, short sh, byte byt,
|
|
||||||
Object[] arr1, int[] arr2) {
|
|
||||||
this.mObj = new Object();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
mObj = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
String s = mObj.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
codetoanalyze/java/harness/BasicHarnessActivity.java, BasicHarnessActivity.InferGeneratedHarness(), -1, NULL_DEREFERENCE
|
|
Loading…
Reference in new issue