Skip to content

Commit 156b7f3

Browse files
arthaudmeta-codesync[bot]
authored andcommitted
Add unified pysa_dump_* debugging triggers for Pysa and Pyrefly
Summary: This adds a unified `pysa_dump_*` family of debugging/logging triggers for Pysa and Pyrefly. Each phase can be triggered two ways (OR'd together): an in-source `pysa_dump_<phase>()` function call, or a `PYSA_DUMP_<PHASE>=<exact qualified name>` environment variable. `pysa_dump()` / `PYSA_DUMP` is the master switch that enables all phases for a function. This avoids having to edit analyzed source code to insert dump calls, and avoids threading CLI flags through multiple programs. It also cleans up and unifies the previously inconsistent logging logic (e.g. `pysa_dump` worked in pyrefly but not pysa). Reviewed By: tianhan0 Differential Revision: D108298619 fbshipit-source-id: 584002fb9c6be4bf79ab6f79888e1c8e83b804ac
1 parent e5a6b52 commit 156b7f3

15 files changed

Lines changed: 215 additions & 50 deletions

File tree

documentation/website/docs/pysa_tips.md

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,31 @@ of square brackets to access a dictionary.
121121

122122
## Debugging Tools
123123

124-
### `pyre_dump()`
125-
126-
You can insert a call to the (non-existent) `pyre_dump()` function in your code
127-
to enable verbose logging of the call graph, forward and backward analysis of
128-
the current function or method. This can be useful as a starting point to figure
129-
out why something is/isn't happening. This will produce _very_ verbose output.
130-
131-
### `pyre_dump_call_graph`
132-
133-
You can insert a call to `pyre_dump_call_graph` (no import needed) in a function
134-
or method to enable logging of the call graph building. This will produce
135-
verbose output.
124+
### `pysa_dump()`
125+
126+
You can insert a call to the `pysa_dump()` function (no imported needed) in your code
127+
to enable verbose logging of every Pysa analysis phase (call graph, higher order
128+
call graph, forward and backward taint analysis) of the current function or
129+
method. This is the master switch and can be useful as a starting point to
130+
figure out why something is/isn't happening. This will produce _very_ verbose
131+
output.
132+
133+
Every trigger below has two equivalent forms that are OR'd together: an
134+
in-source magic call (e.g. `pysa_dump_taint()`) that targets the function or
135+
method it appears in, and an environment variable (e.g. `PYSA_DUMP_TAINT`) whose
136+
value is matched _exactly_ against the fully-qualified name of the callable being
137+
analyzed (e.g. `my.module.MyClass.my_method`). The master `pysa_dump()` /
138+
`PYSA_DUMP` enables all of the phases below.
139+
140+
| In-source call | Environment variable | Effect |
141+
| ----------------------------------------- | ----------------------------------------- | ---------------------------------------------------------- |
142+
| `pysa_dump()` | `PYSA_DUMP` | Enables every phase below |
143+
| `pysa_dump_call_graph()` | `PYSA_DUMP_CALL_GRAPH` | First-order call graph build logs |
144+
| `pysa_dump_higher_order_call_graph()` | `PYSA_DUMP_HIGHER_ORDER_CALL_GRAPH` | Higher order call graph build logs |
145+
| `pysa_dump_taint()` | `PYSA_DUMP_TAINT` | Taint forward/backward analysis and call graph dump |
146+
| `pysa_dump_cfg()` | `PYSA_DUMP_CFG` | Dump the control flow graph at the start of taint analysis |
147+
| `pysa_dump_perf()` | `PYSA_DUMP_PERF` | Taint perf profiling (spawns `perf`, see below) |
148+
| `pysa_dump_perf_higher_order_call_graph()` | `PYSA_DUMP_PERF_HIGHER_ORDER_CALL_GRAPH` | Higher order call graph perf profiling (in-memory only) |
136149

137150
### `reveal_type(YOUR_VARIABLE)`
138151

@@ -151,11 +164,17 @@ analyzes the function (which could be many times) it will update it's
151164
understanding of the taint flowing into the function and output the current
152165
state. The final output will be the most complete.
153166

154-
### `pyre_dump_perf()`
167+
### `pysa_dump_perf()`
168+
169+
You can insert a call to `pysa_dump_perf` (no import needed) in a function or
170+
method, or set the `PYSA_DUMP_PERF` environment variable to its fully-qualified
171+
name, to profile the taint analysis on that function or method and dump the
172+
results on stdout. This spawns a real `perf` process.
155173

156-
You can insert a call to `pyre_dump_perf` (no import needed) in a function or
157-
method to profile the current analysis on that function or method, and dump the
158-
results on stdout.
174+
The related `pysa_dump_perf_higher_order_call_graph()` /
175+
`PYSA_DUMP_PERF_HIGHER_ORDER_CALL_GRAPH` profiles the higher order call graph
176+
build. Unlike `pysa_dump_perf`, this profiling is in-memory only and does not
177+
spawn a `perf` process.
159178

160179
### `results.json`
161180

source/analysis/scope.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,13 @@ module Builtins = struct
685685
(* Builtins recognized by Pyre itself *)
686686
| "BoundMethod"
687687
| "pyre_dump"
688+
| "pysa_dump"
689+
| "pysa_dump_call_graph"
690+
| "pysa_dump_higher_order_call_graph"
691+
| "pysa_dump_perf_higher_order_call_graph"
692+
| "pysa_dump_perf"
693+
| "pysa_dump_taint"
694+
| "pysa_dump_cfg"
688695
| "reveal_type"
689696
| "reveal_locals"
690697
(* Builtins recognized by Python *)

source/ast/statement.ml

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -652,13 +652,19 @@ and Define : sig
652652

653653
val dump_locations : t -> bool
654654

655-
val dump_call_graph : t -> bool
655+
val pysa_dump : t -> bool
656656

657-
val dump_higher_order_call_graph : t -> bool
657+
val pysa_dump_call_graph : t -> bool
658658

659-
val dump_perf_higher_order_call_graph : t -> bool
659+
val pysa_dump_higher_order_call_graph : t -> bool
660660

661-
val dump_perf : t -> bool
661+
val pysa_dump_perf_higher_order_call_graph : t -> bool
662+
663+
val pysa_dump_perf : t -> bool
664+
665+
val pysa_dump_taint : t -> bool
666+
667+
val pysa_dump_cfg : t -> bool
662668

663669
val show_json : t -> string
664670

@@ -1085,15 +1091,23 @@ end = struct
10851091

10861092
let dump_locations define = contains_call define "pyre_dump_locations"
10871093

1088-
let dump_call_graph define = contains_call define "pyre_dump_call_graph"
1094+
let pysa_dump define = contains_call define "pysa_dump"
1095+
1096+
let pysa_dump_call_graph define = contains_call define "pysa_dump_call_graph"
1097+
1098+
let pysa_dump_higher_order_call_graph define =
1099+
contains_call define "pysa_dump_higher_order_call_graph"
1100+
1101+
1102+
let pysa_dump_perf_higher_order_call_graph define =
1103+
contains_call define "pysa_dump_perf_higher_order_call_graph"
10891104

1090-
let dump_higher_order_call_graph define = contains_call define "pyre_dump_higher_order_call_graph"
10911105

1092-
let dump_perf_higher_order_call_graph define =
1093-
contains_call define "pyre_dump_perf_higher_order_call_graph"
1106+
let pysa_dump_perf define = contains_call define "pysa_dump_perf"
10941107

1108+
let pysa_dump_taint define = contains_call define "pysa_dump_taint"
10951109

1096-
let dump_perf define = contains_call define "pyre_dump_perf"
1110+
let pysa_dump_cfg define = contains_call define "pysa_dump_cfg"
10971111

10981112
let show_json define = define |> to_yojson |> Yojson.Safe.pretty_to_string
10991113

source/ast/statement.mli

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,19 @@ and Define : sig
318318

319319
val dump_locations : t -> bool
320320

321-
val dump_call_graph : t -> bool
321+
val pysa_dump : t -> bool
322322

323-
val dump_higher_order_call_graph : t -> bool
323+
val pysa_dump_call_graph : t -> bool
324324

325-
val dump_perf_higher_order_call_graph : t -> bool
325+
val pysa_dump_higher_order_call_graph : t -> bool
326326

327-
val dump_perf : t -> bool
327+
val pysa_dump_perf_higher_order_call_graph : t -> bool
328+
329+
val pysa_dump_perf : t -> bool
330+
331+
val pysa_dump_taint : t -> bool
332+
333+
val pysa_dump_cfg : t -> bool
328334

329335
val show_json : t -> string
330336

source/interprocedural/callGraphBuilder.ml

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4536,12 +4536,6 @@ module HigherOrderCallGraph = struct
45364536
end
45374537
end
45384538

4539-
let debug_higher_order_call_graph define =
4540-
Ast.Statement.Define.dump define
4541-
|| Ast.Statement.Define.dump_call_graph define
4542-
|| Ast.Statement.Define.dump_higher_order_call_graph define
4543-
4544-
45454539
let higher_order_call_graph_of_define
45464540
~define_call_graph
45474541
~pyre_api
@@ -4568,7 +4562,7 @@ let higher_order_call_graph_of_define
45684562

45694563
let get_callee_model = get_callee_model
45704564

4571-
let debug = debug_higher_order_call_graph (Node.value define)
4565+
let debug = PysaDump.should_dump_higher_order_call_graph ~define:(Node.value define) ~callable
45724566

45734567
let module_qualifier = qualifier
45744568

@@ -4693,9 +4687,7 @@ let call_graph_of_define
46934687
Some { MissingFlowTypeAnalysis.caller = callable }
46944688
else
46954689
None);
4696-
debug =
4697-
Ast.Statement.Define.dump (Node.value define)
4698-
|| Ast.Statement.Define.dump_call_graph (Node.value define);
4690+
debug = PysaDump.should_dump_call_graph ~define:(Node.value define) ~callable;
46994691
override_graph;
47004692
attribute_targets;
47014693
skip_call_higher_order_functions;
@@ -5686,9 +5678,7 @@ let build_whole_program_call_graph_for_pyrefly
56865678
| AstResult.Some
56875679
({ CallablesSharedMemory.DefineAndQualifier.define = { Node.value = define; _ }; _ } as
56885680
define_and_qualifier) ->
5689-
let debug =
5690-
Ast.Statement.Define.dump define || Ast.Statement.Define.dump_call_graph define
5691-
in
5681+
let debug = PysaDump.should_dump_call_graph ~define ~callable in
56925682
debug, Some define_and_qualifier
56935683
| _ -> false, None
56945684
in

source/interprocedural/callGraphBuilder.mli

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,6 @@ end
132132

133133
val default_scheduler_policy : Scheduler.Policy.t
134134

135-
val debug_higher_order_call_graph : Ast.Statement.Define.t -> bool
136-
137135
val higher_order_call_graph_of_define
138136
: define_call_graph:CallGraph.DefineCallGraph.t ->
139137
pyre_api:PyrePysaApi.ReadOnly.t ->

source/interprocedural/callGraphFixpoint.ml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@ module CallGraphAnalysis = struct
136136
~message:(Format.asprintf "Missing call graph for `%a`" Target.pp callable)
137137
in
138138
let profiler =
139-
if Ast.Statement.Define.dump_perf_higher_order_call_graph (Ast.Node.value define) then
139+
if
140+
PysaDump.should_dump_perf_higher_order_call_graph
141+
~define:(Ast.Node.value define)
142+
~callable
143+
then (* Higher order call graph perf profiling is kept in-memory only. *)
140144
CallGraphProfiler.start ~enable_perf:false ~callable ()
141145
else
142146
CallGraphProfiler.disabled
@@ -190,7 +194,7 @@ module CallGraphAnalysis = struct
190194
|> Option.value ~default:true
191195
|> not)
192196
in
193-
if CallGraphBuilder.debug_higher_order_call_graph (Ast.Node.value define) then (
197+
if PysaDump.should_dump_higher_order_call_graph ~define:(Ast.Node.value define) ~callable then (
194198
Log.dump
195199
"Returned callables for `%a`: `%a`"
196200
Target.pp_pretty_with_kind

source/interprocedural/interprocedural.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ module IntraproceduralProfiler = IntraproceduralProfiler
3434
module CallGraphProfiler = CallGraphProfiler
3535
module TypeOfExpressionSharedMemory = TypeOfExpressionSharedMemory
3636
module ExpressionIdentifier = ExpressionIdentifier
37+
module PysaDump = PysaDump

source/interprocedural/pysaDump.ml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
(*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*)
7+
8+
open Core
9+
10+
let pysa_dump_env = lazy (Sys.getenv "PYSA_DUMP")
11+
12+
let pysa_dump_call_graph_env = lazy (Sys.getenv "PYSA_DUMP_CALL_GRAPH")
13+
14+
let pysa_dump_higher_order_call_graph_env = lazy (Sys.getenv "PYSA_DUMP_HIGHER_ORDER_CALL_GRAPH")
15+
16+
let pysa_dump_taint_env = lazy (Sys.getenv "PYSA_DUMP_TAINT")
17+
18+
let pysa_dump_cfg_env = lazy (Sys.getenv "PYSA_DUMP_CFG")
19+
20+
let pysa_dump_perf_env = lazy (Sys.getenv "PYSA_DUMP_PERF")
21+
22+
let perf_higher_order_call_graph_env = lazy (Sys.getenv "PYSA_DUMP_PERF_HIGHER_ORDER_CALL_GRAPH")
23+
24+
(* A phase fires when the master switch is on (in-source `pysa_dump()` or `PYSA_DUMP` matching the
25+
fully-qualified name), or when the phase-specific in-source call or environment variable
26+
matches. *)
27+
let should_dump ~define ~callable ~in_source_phase ~phase_env =
28+
let qualified_name = Ast.Reference.show (Target.define_name_exn callable) in
29+
let matches_env env =
30+
match Lazy.force env with
31+
| Some value -> String.equal value qualified_name
32+
| None -> false
33+
in
34+
Ast.Statement.Define.pysa_dump define
35+
|| in_source_phase define
36+
|| matches_env pysa_dump_env
37+
|| matches_env phase_env
38+
39+
40+
let should_dump_call_graph ~define ~callable =
41+
should_dump
42+
~define
43+
~callable
44+
~in_source_phase:Ast.Statement.Define.pysa_dump_call_graph
45+
~phase_env:pysa_dump_call_graph_env
46+
47+
48+
let should_dump_higher_order_call_graph ~define ~callable =
49+
should_dump
50+
~define
51+
~callable
52+
~in_source_phase:Ast.Statement.Define.pysa_dump_higher_order_call_graph
53+
~phase_env:pysa_dump_higher_order_call_graph_env
54+
55+
56+
let should_dump_taint ~define ~callable =
57+
should_dump
58+
~define
59+
~callable
60+
~in_source_phase:Ast.Statement.Define.pysa_dump_taint
61+
~phase_env:pysa_dump_taint_env
62+
63+
64+
let should_dump_cfg ~define ~callable =
65+
should_dump
66+
~define
67+
~callable
68+
~in_source_phase:Ast.Statement.Define.pysa_dump_cfg
69+
~phase_env:pysa_dump_cfg_env
70+
71+
72+
let should_dump_perf ~define ~callable =
73+
should_dump
74+
~define
75+
~callable
76+
~in_source_phase:Ast.Statement.Define.pysa_dump_perf
77+
~phase_env:pysa_dump_perf_env
78+
79+
80+
let should_dump_perf_higher_order_call_graph ~define ~callable =
81+
should_dump
82+
~define
83+
~callable
84+
~in_source_phase:Ast.Statement.Define.pysa_dump_perf_higher_order_call_graph
85+
~phase_env:perf_higher_order_call_graph_env
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
(*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*)
7+
8+
(* Decides whether verbose "magic dump" logging is enabled for a given callable during a Pysa
9+
analysis phase. *)
10+
11+
val should_dump_call_graph : define:Ast.Statement.Define.t -> callable:Target.t -> bool
12+
13+
val should_dump_higher_order_call_graph : define:Ast.Statement.Define.t -> callable:Target.t -> bool
14+
15+
val should_dump_taint : define:Ast.Statement.Define.t -> callable:Target.t -> bool
16+
17+
val should_dump_cfg : define:Ast.Statement.Define.t -> callable:Target.t -> bool
18+
19+
val should_dump_perf : define:Ast.Statement.Define.t -> callable:Target.t -> bool
20+
21+
val should_dump_perf_higher_order_call_graph
22+
: define:Ast.Statement.Define.t ->
23+
callable:Target.t ->
24+
bool

0 commit comments

Comments
 (0)