Skip to main content

tldr_cli/commands/
reaching_defs.rs

1//! Reaching Definitions command - Display reaching definitions analysis
2//!
3//! Provides data flow analysis showing which variable definitions reach
4//! each use point in a function.
5//!
6//! Reference: session10-spec.md Section 4.2
7
8use std::path::PathBuf;
9
10use anyhow::Result;
11use clap::Args;
12
13use tldr_core::dfg::{
14    build_reaching_defs_report, filter_reaching_defs_by_variable, format_reaching_defs_json,
15    format_reaching_defs_text_with_options, ReachingDefsFormatOptions, ReachingDefsReport,
16};
17use tldr_core::{get_cfg_context, get_dfg_context, Language};
18
19use crate::output::OutputFormat;
20
21/// Analyze reaching definitions for a function
22#[derive(Debug, Args)]
23pub struct ReachingDefsArgs {
24    /// Source file to analyze
25    pub file: PathBuf,
26
27    /// Function name to analyze
28    pub function: String,
29
30    /// Programming language (auto-detected from file extension if not specified)
31    #[arg(long, short = 'l')]
32    pub lang: Option<Language>,
33
34    /// Filter output to specific variable
35    #[arg(long)]
36    pub var: Option<String>,
37
38    /// Show definitions reaching specific line
39    #[arg(long)]
40    pub line: Option<usize>,
41
42    /// Show def-use chains (enabled by default)
43    #[arg(long, default_value = "true")]
44    pub show_chains: bool,
45
46    /// Flag potentially uninitialized uses (enabled by default)
47    #[arg(long, default_value = "true")]
48    pub show_uninitialized: bool,
49
50    /// Show IN/OUT sets per block
51    #[arg(long)]
52    pub show_in_out: bool,
53
54    /// Show only def-use/use-def chains, hide header, blocks, and statistics
55    #[arg(long)]
56    pub chains_only: bool,
57
58    /// Function parameters (comma-separated, for uninit detection)
59    #[arg(long)]
60    pub params: Option<String>,
61}
62
63impl ReachingDefsArgs {
64    /// Run the reaching-defs command
65    pub fn run(&self, format: OutputFormat, quiet: bool) -> Result<()> {
66        use crate::output::OutputWriter;
67
68        let writer = OutputWriter::new(format, quiet);
69
70        // Determine language from file extension or argument
71        let language = self
72            .lang
73            .unwrap_or_else(|| Language::from_path(&self.file).unwrap_or(Language::Python));
74
75        writer.progress(&format!(
76            "Analyzing reaching definitions for {} in {}...",
77            self.function,
78            self.file.display()
79        ));
80
81        // Read source file - ensure it exists
82        if !self.file.exists() {
83            return Err(anyhow::anyhow!("File not found: {}", self.file.display()));
84        }
85
86        // Get CFG and DFG
87        let cfg = get_cfg_context(
88            self.file.to_str().unwrap_or_default(),
89            &self.function,
90            language,
91        )?;
92
93        let dfg = get_dfg_context(
94            self.file.to_str().unwrap_or_default(),
95            &self.function,
96            language,
97        )?;
98
99        // Build the report
100        let mut report = build_reaching_defs_report(&cfg, &dfg.refs, self.file.clone());
101
102        // Filter by variable if requested
103        if let Some(ref var) = self.var {
104            report = filter_reaching_defs_by_variable(&report, var);
105        }
106
107        // Filter by line if requested
108        if let Some(line) = self.line {
109            report = filter_report_by_line(&report, line as u32);
110        }
111
112        // Build format options from CLI flags
113        // --chains-only: show ONLY chains (no header, no blocks, no stats)
114        // --show-in-out: show per-block GEN/KILL/IN/OUT details
115        // Default (neither flag): header + chains + stats (no blocks)
116        // Both flags: chains_only takes precedence
117        let format_options = if self.chains_only {
118            ReachingDefsFormatOptions::chains_only()
119        } else {
120            ReachingDefsFormatOptions {
121                show_blocks: self.show_in_out,
122                show_chains: self.show_chains,
123                show_uninitialized: self.show_uninitialized,
124                show_header: true,
125                show_stats: true,
126            }
127        };
128
129        // Output based on format
130        match format {
131            OutputFormat::Text => {
132                let text = format_reaching_defs_text_with_options(&report, &format_options);
133                writer.write_text(&text)?;
134            }
135            OutputFormat::Json | OutputFormat::Compact => {
136                let json = format_reaching_defs_json(&report)
137                    .map_err(|e| anyhow::anyhow!("JSON serialization failed: {}", e))?;
138                writer.write_text(&json)?;
139            }
140            OutputFormat::Dot => {
141                // DOT not supported for reaching defs, fall back to JSON
142                let json = format_reaching_defs_json(&report)
143                    .map_err(|e| anyhow::anyhow!("JSON serialization failed: {}", e))?;
144                writer.write_text(&json)?;
145            }
146            OutputFormat::Sarif => {
147                // SARIF not supported, fall back to JSON
148                let json = format_reaching_defs_json(&report)
149                    .map_err(|e| anyhow::anyhow!("JSON serialization failed: {}", e))?;
150                writer.write_text(&json)?;
151            }
152        }
153
154        Ok(())
155    }
156}
157
158/// Filter the report to show only definitions reaching a specific line.
159///
160/// This shows:
161/// - Use-def chains where the use is at the specified line
162/// - Def-use chains where the definition reaches the specified line
163fn filter_report_by_line(report: &ReachingDefsReport, line: u32) -> ReachingDefsReport {
164    use std::collections::HashSet;
165
166    // Find use-def chains where the use is at this line
167    let relevant_use_def_chains: Vec<_> = report
168        .use_def_chains
169        .iter()
170        .filter(|c| c.use_site.line == line)
171        .cloned()
172        .collect();
173
174    // Collect all definition lines from relevant use-def chains
175    let relevant_def_lines: HashSet<u32> = relevant_use_def_chains
176        .iter()
177        .flat_map(|c| c.reaching_defs.iter().map(|d| d.line))
178        .collect();
179
180    // Filter def-use chains to those relevant definitions
181    let relevant_def_use_chains: Vec<_> = report
182        .def_use_chains
183        .iter()
184        .filter(|c| relevant_def_lines.contains(&c.definition.line))
185        .cloned()
186        .collect();
187
188    // Filter uninitialized uses to this line
189    let relevant_uninitialized: Vec<_> = report
190        .uninitialized
191        .iter()
192        .filter(|u| u.line == line)
193        .cloned()
194        .collect();
195
196    ReachingDefsReport {
197        function: report.function.clone(),
198        file: report.file.clone(),
199        blocks: report.blocks.clone(), // Keep all blocks for context
200        def_use_chains: relevant_def_use_chains,
201        use_def_chains: relevant_use_def_chains,
202        uninitialized: relevant_uninitialized,
203        stats: report.stats.clone(),
204        uncertain_defs: report.uncertain_defs.clone(),
205        confidence: report.confidence,
206    }
207}