feat(io): handle-aware I/O for the lean non-Python walk plus Python cursor derive - #770
Conversation
…st, C++ and Python cursor derive
…lus Python cursor derive (#714)
There was a problem hiding this comment.
Code Review
This pull request implements handle-aware I/O tracking for non-Python languages (the "lean" walk, addressing issue #714). It introduces mechanisms to track resource handles (such as files, databases, and sockets) across JavaScript/TypeScript, Go, Java, Rust, and C++. This includes resolving constructors, unwrapping result/wrapper types, deriving sub-handles (e.g., database cursors), and attributing subsequent method calls or stream operations (like << and >>) to the correct underlying resource. The changes also include refactoring shared binding extraction logic and adding comprehensive integration and unit tests. There are no review comments, so I have no feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Greptile SummaryThis PR adds handle-aware I/O detection across the lean non-Python walk and extends Python DB cursor tracking. The main changes are:
Confidence Score: 5/5Safe to merge with low risk. No confirmed runtime, logic, or security issues were found in the changed files. The updated I/O handle paths are covered by focused unit and integration tests. No files require special attention.
What T-Rex did
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Caller as Caller AST scope
participant Walk as IOAccessProcessor lean walk
participant Bind as Handle binding map
participant Reg as I/O registry tables
participant Graph as Graph ingestor
Caller->>Walk: Visit statements in source order
Walk->>Reg: Resolve constructor, wrapper, and derive calls
Reg-->>Walk: Handle kind, method direction, and identity rules
Walk->>Bind: Bind or rebind handle variables
Caller->>Walk: Later handle method or stream operator call
Walk->>Bind: Lookup receiver binding
Bind-->>Walk: Resource kind and identity
Walk->>Graph: Emit READS_FROM or WRITES_TO edge
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Caller as Caller AST scope
participant Walk as IOAccessProcessor lean walk
participant Bind as Handle binding map
participant Reg as I/O registry tables
participant Graph as Graph ingestor
Caller->>Walk: Visit statements in source order
Walk->>Reg: Resolve constructor, wrapper, and derive calls
Reg-->>Walk: Handle kind, method direction, and identity rules
Walk->>Bind: Bind or rebind handle variables
Caller->>Walk: Later handle method or stream operator call
Walk->>Bind: Lookup receiver binding
Bind-->>Walk: Resource kind and identity
Walk->>Graph: Emit READS_FROM or WRITES_TO edge
Reviews (4): Last reviewed commit: "refactor(io): extract sonar-flagged dupl..." | Re-trigger Greptile |
|
@greptile review |
|
@greptile review |
# Conflicts: # uv.lock
…h-complexity walk helpers
|
@greptile review |
|



Problem
The lean non-Python I/O walk (issue #714) only matched direct sink calls. Handle-based I/O, which is the dominant idiom in most of these languages, produced nothing:
f, _ := os.OpenFile("data.txt", os.O_WRONLY, 0644); f.WriteString(s)— no edge (OpenFile's direction depends on flags, so it cannot be a direct sink at all).const ws = fs.createWriteStream('out.txt'); ws.write(data)— no edge.new FileWriter("out.txt"),new BufferedReader(new FileReader("in.txt")),Files.newBufferedReader(Path.of("cfg.txt"))— Java had zero handle coverage since every file/DB touch goes through a handle.let mut f = File::open("in.txt")?; f.read_to_string(&mut s)?— no edge.std::ifstream in("in.txt"); in >> word;— no edge.cur = conn.cursor(); cur.execute("SELECT ...")— the cursor was invisible even in the full Python handle walk.IO_HANDLE_CONSTRUCTORShad only Python entries; the registry comment marked handles as a follow-up.Fix
The lean walk now tracks handle bindings in source order (flat, mirroring Python's handle walk) and attributes handle-method calls to the constructor's resource:
os.Open/Create/OpenFile,database/sql.Open,net.Dial,fs.createReadStream/createWriteStream,Files.newBufferedReader/...,DriverManager.getConnection,std::fs::File::open/create,std::net::TcpStream::connect), resolved with the same shadow-aware, import-expanding matching as sinks.new-shaped constructors (Javanew FileWriteretc.) plus wrapper types (new BufferedReader(new FileReader(p)),new Scanner(new File(p))) that delegate identity to arg0; PrintWriter keeps both its writer-wrapping and filename overloads.BufReader::new(f)/BufWriter::new(f), Gobufio.NewReader/NewWriter/NewScanner.std::ifstream in("x.txt")(init_declarator) and the most-vexing-parse formstd::ifstream dyn(path)(function_declarator,<dynamic>identity).<<on a bound handle writes and>>reads its resource; the existing cout/cerr stream sink behaviour is unchanged.?(try_expression) and.unwrap()/.expect(..).Files.newBufferedReader(Path.of("cfg.txt"))andnew Scanner(new File("data.csv"))carry the literal.IO_HANDLE_DERIVES):conn.createStatement()/conn.prepareStatement()(java.sql) andconn.cursor()(Python DB-API) yield same-resource sub-handles — this also closes the long-standing Python cursor gap.g := f), rebinds kill, unbound receivers and arithmeticx << 2stay silent.IO_LEAN_HANDLE_METHODS) map methods to direction; java.sqlexecute(sql)refines READ_WRITE by the SQL first keyword, like Python.The (targets, values) extraction the flow walk already used for taint binding is factored into a shared
binding_targets_valueshelper inio_access/extract.py, used by both walks.Validation
RED → GREEN in commit history: the
test(io)commit adds 26 failing specs first (30 after review-driven additions), covering every language plus negatives; the feature commit turns them green. Five e2e integration tests added to the per-languagetest_*_io_e2e.pyfiles.Full suite: 4986 passed, 5 skipped (
pytest -n auto). No default-capture behaviour changes: everything stays behind the opt-iniocapture group, and all existing direct-sink semantics (including Goos.Open/os.Createas direct sinks) are preserved.Part of #714; the remaining lean-flow path-sensitivity depth (loops/try for non-hoisted languages, Rust
if/match) follows in the next PR.