Skip to content

Commit

Permalink
Add nonlocals to Call Graph
Browse files Browse the repository at this point in the history
Summary: Now nonlocal variable accesses mirror global variable accesses and are present in the call graph.

Reviewed By: arthaud

Differential Revision: D50207568

fbshipit-source-id: 1a4a1f105d03b08012edf4a0af8c469d3cfb9e9d
  • Loading branch information
alexkassil authored and facebook-github-bot committed Oct 14, 2023
1 parent 15c6d15 commit 9589ae0
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 67 deletions.
124 changes: 93 additions & 31 deletions source/interprocedural/callGraph.ml
Original file line number Diff line number Diff line change
Expand Up @@ -620,18 +620,43 @@ end

(** An aggregate of all possible callees for a given identifier expression, i.e `foo`. *)
module IdentifierCallees = struct
type t = { global_targets: CallTarget.t list } [@@deriving eq, show { with_path = false }]
type t = {
global_targets: CallTarget.t list;
nonlocal_targets: CallTarget.t list;
}
[@@deriving eq, show { with_path = false }]

let deduplicate { global_targets } = { global_targets = CallTarget.dedup_and_sort global_targets }
type identifier_reference =
| Global of Reference.t
| Nonlocal of Reference.t

let join { global_targets = left_global_targets } { global_targets = right_global_targets } =
{ global_targets = List.rev_append left_global_targets right_global_targets }
let deduplicate { global_targets; nonlocal_targets } =
{
global_targets = CallTarget.dedup_and_sort global_targets;
nonlocal_targets = CallTarget.dedup_and_sort nonlocal_targets;
}


let all_targets { global_targets } = List.map ~f:CallTarget.target global_targets
let join
{ global_targets = left_global_targets; nonlocal_targets = left_nonlocal_targets }
{ global_targets = right_global_targets; nonlocal_targets = right_nonlocal_targets }
=
{
global_targets = List.rev_append left_global_targets right_global_targets;
nonlocal_targets = List.rev_append left_nonlocal_targets right_nonlocal_targets;
}


let all_targets { global_targets; nonlocal_targets } =
List.map ~f:CallTarget.target (global_targets @ nonlocal_targets)

let to_json { global_targets } =
`Assoc ["globals", `List (List.map ~f:CallTarget.to_json global_targets)]

let to_json { global_targets; nonlocal_targets } =
`Assoc
[
"globals", `List (List.map ~f:CallTarget.to_json global_targets);
"nonlocals", `List (List.map ~f:CallTarget.to_json nonlocal_targets);
]
end

(** An aggregate of callees for formatting strings. *)
Expand Down Expand Up @@ -1723,12 +1748,14 @@ let resolve_attribute_access_properties
{ property_targets; is_attribute }


let as_global_reference ~resolution expression =
let as_identifier_reference ~define ~resolution expression =
match Node.value expression with
| Expression.Name (Name.Identifier identifier) ->
let reference = Reference.create identifier in
if Resolution.is_global resolution ~reference then
Some (Reference.delocalize reference)
Some (IdentifierCallees.Global (Reference.delocalize reference))
else if CallResolution.is_nonlocal ~resolution ~define reference then
Some (IdentifierCallees.Nonlocal (Reference.delocalize reference))
else
None
| Name name -> (
Expand All @@ -1738,20 +1765,31 @@ let as_global_reference ~resolution expression =
>>= function
| UnannotatedGlobalEnvironment.ResolvedReference.ModuleAttribute
{ from; name; remaining = []; _ } ->
Some (Reference.combine from (Reference.create name))
Some (IdentifierCallees.Global (Reference.combine from (Reference.create name)))
| _ -> None)
| _ -> None


let is_builtin_reference reference = reference |> Reference.single |> Option.is_some
let is_builtin_reference = function
| IdentifierCallees.Global reference -> reference |> Reference.single |> Option.is_some
| Nonlocal _ -> false

let resolve_attribute_access_global_targets ~resolution ~base_annotation ~base ~attribute ~special =

let resolve_attribute_access_global_targets
~define
~resolution
~base_annotation
~base
~attribute
~special
=
let expression =
Expression.Name (Name.Attribute { Name.Attribute.base; attribute; special })
|> Node.create_with_default_location
in
match as_global_reference ~resolution expression with
| Some global -> [global]
match as_identifier_reference ~define ~resolution expression with
| Some (IdentifierCallees.Global global) -> [global]
| Some (Nonlocal _) -> []
| None ->
let global_resolution = Resolution.global_resolution resolution in
let rec find_targets targets = function
Expand Down Expand Up @@ -1805,24 +1843,38 @@ let resolve_attribute_access_global_targets ~resolution ~base_annotation ~base ~
find_targets [] base_annotation


let resolve_identifier ~resolution ~call_indexer ~identifier =
let resolve_identifier ~define ~resolution ~call_indexer ~identifier =
Expression.Name (Name.Identifier identifier)
|> Node.create_with_default_location
|> as_global_reference ~resolution
|> as_identifier_reference ~define ~resolution
|> Option.filter ~f:(Fn.non is_builtin_reference)
>>| Target.create_object
>>| fun global ->
{
IdentifierCallees.global_targets =
[
CallTargetIndexer.create_target
call_indexer
~implicit_self:false
~implicit_dunder_call:false
~return_type:None
global;
];
}
>>| function
| IdentifierCallees.Global global ->
{
IdentifierCallees.global_targets =
[
CallTargetIndexer.create_target
call_indexer
~implicit_self:false
~implicit_dunder_call:false
~return_type:None
(Target.create_object global);
];
nonlocal_targets = [];
}
| Nonlocal nonlocal ->
{
IdentifierCallees.nonlocal_targets =
[
CallTargetIndexer.create_target
call_indexer
~implicit_self:false
~implicit_dunder_call:false
~return_type:None
(Target.create_object nonlocal);
];
global_targets = [];
}


(* This is a bit of a trick. The only place that knows where the local annotation map keys is the
Expand All @@ -1836,6 +1888,8 @@ module DefineCallGraphFixpoint (Context : sig

val qualifier : Reference.t

val name : Reference.t

val parent : Reference.t option

val debug : bool
Expand Down Expand Up @@ -2004,7 +2058,13 @@ struct
in

let global_targets =
resolve_attribute_access_global_targets ~resolution ~base_annotation ~base ~attribute ~special
resolve_attribute_access_global_targets
~define:Context.name
~resolution
~base_annotation
~base
~attribute
~special
|> List.map ~f:Target.create_object
(* Use a hashset here for faster lookups. *)
|> List.filter ~f:(Hash_set.mem attribute_targets)
Expand Down Expand Up @@ -2105,7 +2165,7 @@ struct
|> ExpressionCallees.from_attribute_access
|> register_targets ~expression_identifier:attribute
| Expression.Name (Name.Identifier identifier) ->
resolve_identifier ~resolution ~call_indexer ~identifier
resolve_identifier ~define:Context.name ~resolution ~call_indexer ~identifier
>>| ExpressionCallees.from_identifier
>>| register_targets ~expression_identifier:identifier
|> ignore
Expand Down Expand Up @@ -2419,6 +2479,8 @@ let call_graph_of_define

let qualifier = qualifier

let name = name

let parent = parent

let debug = Ast.Statement.Define.dump define || Ast.Statement.Define.dump_call_graph define
Expand Down
6 changes: 5 additions & 1 deletion source/interprocedural/callGraph.mli
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ end

(** An aggregate of all possible callees for a given identifier expression, i.e `foo`. *)
module IdentifierCallees : sig
type t = { global_targets: CallTarget.t list } [@@deriving eq, show]
type t = {
global_targets: CallTarget.t list;
nonlocal_targets: CallTarget.t list;
}
[@@deriving eq, show]

val to_json : t -> Yojson.Safe.t
end
Expand Down
41 changes: 39 additions & 2 deletions source/interprocedural/test/callGraphTest.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1745,6 +1745,7 @@ let test_call_graph_of_define context =
{
IdentifierCallees.global_targets =
[CallTarget.create ~return_type:None (Target.Object "test.d")];
nonlocal_targets = [];
} );
( "__getitem__",
ExpressionCallees.from_attribute_access
Expand Down Expand Up @@ -2874,6 +2875,7 @@ let test_call_graph_of_define context =
{
IdentifierCallees.global_targets =
[CallTarget.create ~return_type:None (Target.Object "test.x")];
nonlocal_targets = [];
}) );
]
();
Expand Down Expand Up @@ -4878,13 +4880,15 @@ let test_call_graph_of_define context =
{
IdentifierCallees.global_targets =
[CallTarget.create ~index:0 ~return_type:None (Target.Object "test.x")];
nonlocal_targets = [];
}) );
( "10:2-10:3",
LocationCallees.Singleton
(ExpressionCallees.from_identifier
{
IdentifierCallees.global_targets =
[CallTarget.create ~index:0 ~return_type:None (Target.Object "test.y")];
nonlocal_targets = [];
}) );
( "12:2-12:8",
LocationCallees.Singleton
Expand All @@ -4903,6 +4907,7 @@ let test_call_graph_of_define context =
{
IdentifierCallees.global_targets =
[CallTarget.create ~index:1 ~return_type:None (Target.Object "test.x")];
nonlocal_targets = [];
}) );
( "13:2-13:8",
LocationCallees.Singleton
Expand All @@ -4921,6 +4926,7 @@ let test_call_graph_of_define context =
{
IdentifierCallees.global_targets =
[CallTarget.create ~index:1 ~return_type:None (Target.Object "test.y")];
nonlocal_targets = [];
}) );
]
();
Expand All @@ -4944,6 +4950,7 @@ let test_call_graph_of_define context =
{
IdentifierCallees.global_targets =
[CallTarget.create ~return_type:None (Target.Object "test.x")];
nonlocal_targets = [];
}) );
]
();
Expand Down Expand Up @@ -5049,10 +5056,10 @@ let test_call_graph_of_define context =
{
IdentifierCallees.global_targets =
[CallTarget.create ~return_type:None (Target.Object "test.x")];
nonlocal_targets = [];
}) );
]
();
(* TODO(T165690928): Add nonlocal to call graph *)
assert_call_graph_of_define
~source:
{|
Expand All @@ -5063,7 +5070,37 @@ let test_call_graph_of_define context =
x = "str"
|}
~define_name:"$local_test?outer$inner"
~expected:[]
~expected:
[
( "6:4-6:5",
LocationCallees.Singleton
(ExpressionCallees.from_identifier
{
IdentifierCallees.nonlocal_targets =
[CallTarget.create ~return_type:None (Target.Object "test.outer.x")];
global_targets = [];
}) );
]
();
assert_call_graph_of_define
~source:{|
def outer():
x = ""
def inner():
y = x
|}
~define_name:"$local_test?outer$inner"
~expected:
[
( "5:8-5:9",
LocationCallees.Singleton
(ExpressionCallees.from_identifier
{
IdentifierCallees.nonlocal_targets =
[CallTarget.create ~return_type:None (Target.Object "test.outer.x")];
global_targets = [];
}) );
]
();
()

Expand Down
2 changes: 1 addition & 1 deletion source/interprocedural_analyses/taint/globalModel.ml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ let get_global_targets ~call_graph ~expression =
call_graph
~location:(Node.location expression)
~identifier
>>| (fun { global_targets } -> global_targets)
>>| (fun { global_targets; nonlocal_targets = _ } -> global_targets)
|> Option.value ~default:[]
| Expression.Name (Name.Attribute { attribute; _ }) ->
CallGraph.DefineCallGraph.resolve_attribute_access
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
@generated
Call dependencies
$local_closure?parameter_order_swap_different_variable_names$inner (fun) -> [_test_sink (fun)]
$local_closure?parameter_order_swap_different_variable_names$inner (fun) -> [_test_sink (fun) closure.parameter_order_swap_different_variable_names.a (object) closure.parameter_order_swap_different_variable_names.b (object) closure.parameter_order_swap_different_variable_names.c (object)]
$local_closure?parameter_order_swap$inner (fun) -> [_test_sink (fun)]
$local_closure?side_effect_reduction_closure$inner (fun) -> [_test_sink (fun)]
$local_closure?side_effect_reduction_closure$inner (fun) -> [_test_sink (fun) closure.side_effect_reduction_closure.x (object)]
$local_closure?wrapper_for_taint_propagation$inner (fun) -> [_test_sink (fun)]
$local_closure?wrapper_for_taint_propagation_hof$inner (fun) -> [_test_sink (fun)]
closure.parameter_order_swap_different_variable_names (fun) -> [$local_closure?parameter_order_swap_different_variable_names$inner (fun)]
closure.wrapper_for_taint_propagation_hof (fun) -> [$local_closure?wrapper_for_taint_propagation_hof$inner (fun) closure.higher_order_function (fun)]
$local_closure?closure$sink (fun) -> [_test_sink (fun)]
$local_closure?closure$source (fun) -> [_test_source (fun)]
$local_closure?nonlocal_closure$sink (fun) -> [_test_sink (fun)]
$local_closure?nonlocal_closure$source (fun) -> [_test_source (fun)]
$local_closure?closure$sink (fun) -> [_test_sink (fun) closure.closure.obj (object)]
$local_closure?closure$source (fun) -> [_test_source (fun) closure.closure.obj (object)]
$local_closure?nonlocal_closure$sink (fun) -> [_test_sink (fun) closure.nonlocal_closure.obj (object)]
$local_closure?nonlocal_closure$source (fun) -> [_test_source (fun) closure.nonlocal_closure.obj (object)]
closure.$toplevel (fun) -> []
closure.closure (fun) -> [object.__init__ (method) object.__new__ (method)]
closure.closure_flow (fun) -> [closure.closure (fun)]
Expand Down
Loading

0 comments on commit 9589ae0

Please sign in to comment.