Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 16 additions & 26 deletions tests/benchmarks/resolution/tracer/jvm-tracer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -142,17 +142,13 @@ case "$LANG" in
# Match lines like: public void method(...) {
# The first sed pass matches all method/constructor opening braces,
# so a second pass is unnecessary (it would double-inject traceCall).
sedi -E '/\)\s*\{$/{
/class |interface /!{
a\ CallTracer.traceCall();
}
}' "$javafile"
sedi_append_unless '/\)[[:space:]]*\{$/' '/class |interface /' \
' CallTracer.traceCall();' "$javafile"
done

# Add dump call at end of main
sedi '/public static void main/,/\}/ {
/^\s*\}/ i\ CallTracer.dump();
}' "$TMP_DIR/Main.java" 2>/dev/null || true
sedi_insert_before_end '/public static void main/' '/\}/' '/^[[:space:]]*\}/' \
' CallTracer.dump();' "$TMP_DIR/Main.java" 2>/dev/null || true

# Compile and run
cd "$TMP_DIR"
Expand All @@ -171,15 +167,13 @@ case "$LANG" in

# Inject CallTracer.traceCall() into every function body
for ktfile in "$TMP_DIR"/*.kt; do
sedi -E '/fun [a-zA-Z].*\{[[:space:]]*$/{
/class |interface |object /!a\ CallTracer.traceCall();
}' "$ktfile"
sedi_append_unless '/fun [a-zA-Z].*\{[[:space:]]*$/' '/class |interface |object /' \
' CallTracer.traceCall();' "$ktfile"
done

# Inject dump call before main's closing brace
sedi '/^fun main/,/^\}/ {
/^\}/ i\ CallTracer.dump()
}' "$TMP_DIR/Main.kt"
sedi_insert_before_end '/^fun main/' '/^\}/' '/^\}/' \
' CallTracer.dump()' "$TMP_DIR/Main.kt"

# Suppress println to keep stdout clean for JSON
for ktfile in "$TMP_DIR"/*.kt; do
Expand All @@ -198,15 +192,13 @@ case "$LANG" in
# Inject CallTracer.traceCall() into every def body
for scfile in "$TMP_DIR"/*.scala; do
base="$(basename "$scfile")"
sedi -E '/def [a-zA-Z].*\{[[:space:]]*$/{
/class |trait |object .*extends/!a\ CallTracer.traceCall();
}' "$scfile"
sedi_append_unless '/def [a-zA-Z].*\{[[:space:]]*$/' '/class |trait |object .*extends/' \
' CallTracer.traceCall();' "$scfile"
done

# Inject dump call before main's closing brace
sedi '/def main/,/^\s*\}/ {
/^\s*\}/ i\ CallTracer.dump()
}' "$TMP_DIR/Main.scala"
sedi_insert_before_end '/def main/' '/^[[:space:]]*\}/' '/^[[:space:]]*\}/' \
' CallTracer.dump()' "$TMP_DIR/Main.scala"

# Suppress println to keep stdout clean for JSON
for scfile in "$TMP_DIR"/*.scala; do
Expand All @@ -231,15 +223,13 @@ case "$LANG" in

# Inject CallTracer.traceCall() into every method body
for grfile in "$TMP_DIR"/*.groovy; do
sedi -E '/\)\s*\{[[:space:]]*$/{
/class |interface /!a\ CallTracer.traceCall();
}' "$grfile"
sedi_append_unless '/\)[[:space:]]*\{[[:space:]]*$/' '/class |interface /' \
' CallTracer.traceCall();' "$grfile"
done

# Inject dump call before main's closing brace
sedi '/static void main/,/^\s*\}/ {
/^\s*\}/ i\ CallTracer.dump()
}' "$TMP_DIR/Main.groovy"
sedi_insert_before_end '/static void main/' '/^[[:space:]]*\}/' '/^[[:space:]]*\}/' \
' CallTracer.dump()' "$TMP_DIR/Main.groovy"

# Suppress println to keep stdout clean for JSON
for grfile in "$TMP_DIR"/*.groovy; do
Expand Down
23 changes: 10 additions & 13 deletions tests/benchmarks/resolution/tracer/native-tracer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -598,12 +598,12 @@ CSTRACE
done

# Inject Dump at end of Main
sedi '/public static void Main/,/^\s*\}/ {
/^\s*\}/ i\ CallTracer.Dump();
}' "$TMP_DIR/Program.cs" 2>/dev/null || true
sedi_insert_before_end '/public static void Main/' '/^[[:space:]]*\}/' '/^[[:space:]]*\}/' \
' CallTracer.Dump();' "$TMP_DIR/Program.cs" 2>/dev/null || true

# Also call RunWithValidation if it exists
sedi '/CallTracer.Dump/i\ RunWithValidation();' "$TMP_DIR/Program.cs" 2>/dev/null || true
sedi_insert_before '/CallTracer.Dump/' ' RunWithValidation();' \
"$TMP_DIR/Program.cs" 2>/dev/null || true

# Redirect Console.WriteLine in fixture code to stderr
for csfile in "$TMP_DIR"/*.cs; do
Expand Down Expand Up @@ -702,9 +702,8 @@ SWTRACE

# Inject dump at end of main (top-level code or main function)
if grep -q 'func main' "$TMP_DIR/main.swift" 2>/dev/null; then
sedi '/^func main/,/^\}/ {
/^\}/ i\ CallTracer.shared.dump()
}' "$TMP_DIR/main.swift"
sedi_insert_before_end '/^func main/' '/^\}/' '/^\}/' \
' CallTracer.shared.dump()' "$TMP_DIR/main.swift"
else
echo 'CallTracer.shared.dump()' >> "$TMP_DIR/main.swift"
fi
Expand Down Expand Up @@ -800,9 +799,8 @@ DARTTRACE
' } finally { CallTracer.instance.traceReturn(); }'

# Inject dump at end of main
sedi '/^void main/,/^\}/ {
/^\}/ i\ CallTracer.instance.dump();
}' "$TMP_DIR/main.dart" 2>/dev/null || true
sedi_insert_before_end '/^void main/' '/^\}/' '/^\}/' \
' CallTracer.instance.dump();' "$TMP_DIR/main.dart" 2>/dev/null || true

# Redirect print to stderr in fixture files (trace_support.dart excluded
# because its dump() must write JSON to stdout; main.dart is NOT excluded
Expand Down Expand Up @@ -912,9 +910,8 @@ ZIGTRACE
' trace_support.traceCall("%s", "%s"); defer trace_support.traceReturn();'

# Inject dump at end of main
sedi '/^pub fn main/,/^\}/ {
/^\}/ i\ trace_support.dumpTrace();
}' "$TMP_DIR/main.zig" 2>/dev/null || true
sedi_insert_before_end '/^pub fn main/' '/^\}/' '/^\}/' \
' trace_support.dumpTrace();' "$TMP_DIR/main.zig" 2>/dev/null || true

cd "$TMP_DIR"
if zig build-exe main.zig 2>/dev/null; then
Expand Down
39 changes: 39 additions & 0 deletions tests/benchmarks/resolution/tracer/tracer-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,42 @@ sedi() {
sed -i '' "$@"
fi
}

# GNU sed accepts the single-line "i\text" / "a\text" shortcut for its insert
# and append commands; BSD sed (macOS) rejects it and requires the text on
# the line *after* the backslash (see #1759). The three helpers below build
# that portable multi-line form internally so call sites never hand-roll the
# GNU-only shortcut (see #1913).

# Insert TEXT on the line immediately before the first line matching PATTERN.
sedi_insert_before() {
Comment on lines +26 to +27

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The docstring says "first line matching PATTERN" but the generated sed command ${pattern} i\ applies to every line that matches the pattern, not only the first. The current sole call-site happens to target a unique string (/CallTracer.Dump/), so the mismatch is harmless today — but a future caller that expects first-only semantics would silently get multiple insertions. The comment should say "every" (or restrict the implementation to a 0,/pat/-scoped address for truly first-only behaviour).

Suggested change
# Insert TEXT on the line immediately before the first line matching PATTERN.
sedi_insert_before() {
# Insert TEXT on the line immediately before every line matching PATTERN.
sedi_insert_before() {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

local pattern="$1" text="$2" file="$3"
sedi "${pattern} i\\
${text}" "$file"
}

# Insert TEXT immediately before the first line matching INSERT_PATTERN,
# scoped to the START_PATTERN,END_PATTERN address range. Used to splice a
# dump()-style call just before a function's closing brace without touching
# unrelated braces elsewhere in the file. INSERT_PATTERN is taken separately
# from END_PATTERN because some callers close the range on a looser match
# (e.g. any line containing `}`) than the line they actually inject before.
sedi_insert_before_end() {
local start_pattern="$1" end_pattern="$2" insert_pattern="$3" text="$4" file="$5"
sedi "${start_pattern},${end_pattern} {
${insert_pattern} i\\
${text}
}" "$file"
}

# Append TEXT after every line matching OUTER_PATTERN, except lines that also
# match NEGATE_PATTERN (used to skip class/interface/object declaration lines
# that share the same brace-opening shape as method signatures). Runs with
# extended regex (-E) since every current caller relies on ERE alternation.
sedi_append_unless() {
local outer_pattern="$1" negate_pattern="$2" text="$3" file="$4"
sedi -E "${outer_pattern}{
${negate_pattern}!a\\
${text}
}" "$file"
}
159 changes: 159 additions & 0 deletions tests/benchmarks/resolution/tracer/tracer-common.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/**
* Regression coverage for #1913: the sed-injection helpers in
* tracer-common.sh must produce output that is byte-identical on GNU sed
* (Linux CI) and BSD sed (macOS dev boxes), which — unlike GNU — rejects the
* single-line `a\text` / `i\text` shortcut and requires the text on the line
* after the backslash.
*
* These tests exercise the shared helpers directly with bash + sed (whatever
* sed the host provides), independent of any per-language compiler toolchain
* (javac/kotlinc/scalac/groovyc/dotnet/dart/zig), so they run identically in
* every environment regardless of which toolchains happen to be installed.
*/
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const TRACER_DIR = __dirname;
const TRACER_COMMON = path.join(TRACER_DIR, 'tracer-common.sh');

function runHelper(fnCall: string, file: string): string {
execFileSync('bash', ['-c', `source "${TRACER_COMMON}"; ${fnCall}`]);

Check failure on line 25 in tests/benchmarks/resolution/tracer/tracer-common.test.ts

View workflow job for this annotation

GitHub Actions / Test Node 22 (windows-2022)

tests/benchmarks/resolution/tracer/tracer-common.test.ts > tracer-common.sh sed-injection helpers (#1913) > sedi_append_unless appends after matching lines except negated ones, including a space before the brace (jvm-tracer.sh java/groovy per-method sites)

Error: Command failed: bash -c source "D:\a\ops-codegraph-tool\ops-codegraph-tool\tests\benchmarks\resolution\tracer\tracer-common.sh"; sedi_append_unless '/\)[[:space:]]*\{$/' '/class |interface /' ' CallTracer.traceCall();' "C:\Users\RUNNER~1\AppData\Local\Temp\tracer-common-test-PabUHV\BaseService.java" sed: cannot rename ./sed2AXEF6: Invalid cross-device link ❯ runHelper tests/benchmarks/resolution/tracer/tracer-common.test.ts:25:3 ❯ tests/benchmarks/resolution/tracer/tracer-common.test.ts:120:17 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { status: 4, signal: null, output: [ null, '<Buffer(0) ...>', '<Buffer(58) ...>' ], pid: 6992, stdout: '<Buffer(0) ...>', stderr: '<Buffer(58) ...>' }

Check failure on line 25 in tests/benchmarks/resolution/tracer/tracer-common.test.ts

View workflow job for this annotation

GitHub Actions / Test Node 22 (windows-2022)

tests/benchmarks/resolution/tracer/tracer-common.test.ts > tracer-common.sh sed-injection helpers (#1913) > sedi_insert_before_end honors distinct range-end and insert-line patterns (jvm-tracer.sh java dump site)

Error: Command failed: bash -c source "D:\a\ops-codegraph-tool\ops-codegraph-tool\tests\benchmarks\resolution\tracer\tracer-common.sh"; sedi_insert_before_end '/public static void main/' '/\}/' '/^[[:space:]]*\}/' ' CallTracer.dump();' "C:\Users\RUNNER~1\AppData\Local\Temp\tracer-common-test-pGaGOu\Main.java" sed: cannot rename ./sedzUxiu6: Invalid cross-device link ❯ runHelper tests/benchmarks/resolution/tracer/tracer-common.test.ts:25:3 ❯ tests/benchmarks/resolution/tracer/tracer-common.test.ts:97:17 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { status: 4, signal: null, output: [ null, '<Buffer(0) ...>', '<Buffer(58) ...>' ], pid: 6184, stdout: '<Buffer(0) ...>', stderr: '<Buffer(58) ...>' }

Check failure on line 25 in tests/benchmarks/resolution/tracer/tracer-common.test.ts

View workflow job for this annotation

GitHub Actions / Test Node 22 (windows-2022)

tests/benchmarks/resolution/tracer/tracer-common.test.ts > tracer-common.sh sed-injection helpers (#1913) > sedi_insert_before_end inserts before the matching brace within a range (jvm/native dump-injection sites)

Error: Command failed: bash -c source "D:\a\ops-codegraph-tool\ops-codegraph-tool\tests\benchmarks\resolution\tracer\tracer-common.sh"; sedi_insert_before_end '/^void main/' '/^\}/' '/^\}/' ' CallTracer.instance.dump();' "C:\Users\RUNNER~1\AppData\Local\Temp\tracer-common-test-IIkjNV\main.dart" sed: cannot rename ./sedW36lm4: Invalid cross-device link ❯ runHelper tests/benchmarks/resolution/tracer/tracer-common.test.ts:25:3 ❯ tests/benchmarks/resolution/tracer/tracer-common.test.ts:66:17 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { status: 4, signal: null, output: [ null, '<Buffer(0) ...>', '<Buffer(58) ...>' ], pid: 8092, stdout: '<Buffer(0) ...>', stderr: '<Buffer(58) ...>' }

Check failure on line 25 in tests/benchmarks/resolution/tracer/tracer-common.test.ts

View workflow job for this annotation

GitHub Actions / Test Node 22 (windows-2022)

tests/benchmarks/resolution/tracer/tracer-common.test.ts > tracer-common.sh sed-injection helpers (#1913) > sedi_insert_before inserts text directly before the matching line (native-tracer.sh csharp RunWithValidation site)

Error: Command failed: bash -c source "D:\a\ops-codegraph-tool\ops-codegraph-tool\tests\benchmarks\resolution\tracer\tracer-common.sh"; sedi_insert_before '/CallTracer.Dump/' ' RunWithValidation();' "C:\Users\RUNNER~1\AppData\Local\Temp\tracer-common-test-37AoCk\Program.cs" sed: cannot rename ./sedfDGHqB: Invalid cross-device link ❯ runHelper tests/benchmarks/resolution/tracer/tracer-common.test.ts:25:3 ❯ tests/benchmarks/resolution/tracer/tracer-common.test.ts:54:17 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { status: 4, signal: null, output: [ null, '<Buffer(0) ...>', '<Buffer(58) ...>' ], pid: 6356, stdout: '<Buffer(0) ...>', stderr: '<Buffer(58) ...>' }
return fs.readFileSync(file, 'utf8');
}

describe('tracer-common.sh sed-injection helpers (#1913)', () => {
let tmpDir: string;

beforeEach(() => {
tmpDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'tracer-common-test-')));
});

afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});

it('sedi_insert_before inserts text directly before the matching line (native-tracer.sh csharp RunWithValidation site)', () => {
const file = path.join(tmpDir, 'Program.cs');
fs.writeFileSync(
file,
[
'class Program {',
' public static void Main() {',
' CallTracer.Dump();',
' }',
'}',
'',
].join('\n'),
);

const out = runHelper(
`sedi_insert_before '/CallTracer.Dump/' ' RunWithValidation();' "${file}"`,
file,
);

expect(out).toContain(' RunWithValidation();\n CallTracer.Dump();');

Check failure on line 59 in tests/benchmarks/resolution/tracer/tracer-common.test.ts

View workflow job for this annotation

GitHub Actions / Test Node 22 (macos-latest)

tests/benchmarks/resolution/tracer/tracer-common.test.ts > tracer-common.sh sed-injection helpers (#1913) > sedi_insert_before inserts text directly before the matching line (native-tracer.sh csharp RunWithValidation site)

AssertionError: expected 'class Program {\n public static vo…' to contain ' RunWithValidation();\n …' - Expected + Received - RunWithValidation(); - CallTracer.Dump(); + class Program { + public static void Main() { + RunWithValidation(); CallTracer.Dump(); + } + } + ❯ tests/benchmarks/resolution/tracer/tracer-common.test.ts:59:17
});

it('sedi_insert_before_end inserts before the matching brace within a range (jvm/native dump-injection sites)', () => {
const file = path.join(tmpDir, 'main.dart');
fs.writeFileSync(file, ['void main() {', ' print(1);', '}', ''].join('\n'));

const out = runHelper(
`sedi_insert_before_end '/^void main/' '/^\\}/' '/^\\}/' ' CallTracer.instance.dump();' "${file}"`,
file,
);

expect(out.split('\n')).toEqual([
'void main() {',
' print(1);',
' CallTracer.instance.dump();',
'}',
'',
]);
});

it('sedi_insert_before_end honors distinct range-end and insert-line patterns (jvm-tracer.sh java dump site)', () => {
// java's dump site scopes the range with a loose /\}/ end-address but
// only inserts before a line that is *purely* a closing brace, so the
// range and insertion patterns must be tracked independently.
const file = path.join(tmpDir, 'Main.java');
fs.writeFileSync(
file,
[
'public class Main {',
' public static void main(String[] args) {',
' foo();',
' }',
'}',
'',
].join('\n'),
);

const out = runHelper(
`sedi_insert_before_end '/public static void main/' '/\\}/' '/^[[:space:]]*\\}/' ' CallTracer.dump();' "${file}"`,
file,
);

expect(out).toContain(' foo();\n CallTracer.dump();\n }\n}');
});

it('sedi_append_unless appends after matching lines except negated ones, including a space before the brace (jvm-tracer.sh java/groovy per-method sites)', () => {
const file = path.join(tmpDir, 'BaseService.java');
fs.writeFileSync(
file,
[
'public abstract class BaseService {',
'',
' protected void log(String message) {',
' System.out.println(message);',
' }',
'}',
'',
].join('\n'),
);

const out = runHelper(
`sedi_append_unless '/\\)[[:space:]]*\\{$/' '/class |interface /' ' CallTracer.traceCall();' "${file}"`,
file,
);

// The method signature line (ending in ") {") gets the call appended...
expect(out).toContain(
' protected void log(String message) {\n CallTracer.traceCall();',
);
// ...but the class declaration line (also ending in "{") is excluded.
expect(out.split('\n')[0]).toBe('public abstract class BaseService {');
});

it('rejects a regression back to the GNU-only single-line a\\/i\\ shortcut in the tracer scripts', () => {
// Every dump()/traceCall() injection now goes through the shared helpers
// above, so the raw sed scripts embedded directly in the language
// tracers should contain zero remaining single-line "i\text"/"a\text"
// occurrences (the exact defect #1913 fixed) — this guards against
// future injection sites reintroducing the GNU-only shortcut.
const guardedFiles = ['jvm-tracer.sh', 'native-tracer.sh', 'go-tracer.sh'];

for (const name of guardedFiles) {
const code = fs
.readFileSync(path.join(TRACER_DIR, name), 'utf8')
.split('\n')
// Drop comment lines so prose that *describes* the banned form (as
// this very fix's changelog comments do) isn't mistaken for it.
.filter((line) => !line.trim().startsWith('#'))
.join('\n');
// \S excludes the newline that must immediately follow a portable
// "i\" / "a\" — so this only matches the banned single-line form
// where real text follows the backslash on the same line.
const offender = code.match(/[ai]\\\S/);
expect(
offender,
`${name} contains a GNU-only single-line a\\/i\\ form: ${offender}`,
).toBeNull();
}
});
});
Comment on lines +139 to +159

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Regression guard omits tracer-common.sh itself

The guard intentionally skips tracer-common.sh to avoid false-positives from the i\\ in the helpers' own portable implementation (the JS regex /[ai]\\\S/ would match the second backslash in i\\<newline>text). That's the right trade-off, but it means a new injection helper mistakenly written with the GNU-only form directly inside tracer-common.sh would go undetected. A comment in the test recording why the file is excluded would make this intentional gap visible to the next contributor.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Loading