Merge branch 'main' into fix/2605-rename-edit-count

This commit is contained in:
Gergő Magyar 2026-07-21 15:43:53 +01:00 • committed by GitHub
commit 0e28486289
7 changed files with 141 additions and 18 deletions

View file

@ -24,6 +24,7 @@ from workflow_bench.proposer_sandbox import (
SANDBOX_USER_SKILLS,
ReadOnlyMount,
SandboxError,
_runtime_mount_args,
build_claude_settings,
build_sandbox_environment,
prepare_sandbox,
@ -191,6 +192,30 @@ def test_sandbox_command_has_minimal_mounts_and_no_host_root_bind(tmp_path: Path
assert not private_root.exists()
def test_runtime_mounts_bind_the_resolved_node_wherever_path_puts_it(monkeypatch) -> None:
# sanitized_graph.py and runner_sessions.py invoke the sandboxed graph CLI
# at the fixed path /usr/local/bin/node. That's only covered by the /usr
# bind when node happens to live under /usr/local/bin on the host -- true
# on GitHub-hosted runner images, but not on a self-hosted runner where
# actions/setup-node installs into its own tool-cache directory instead
# (observed empirically: "bwrap: execvp /usr/local/bin/node: No such file
# or directory" on a fresh self-hosted runner).
monkeypatch.setattr(
"workflow_bench.proposer_sandbox.shutil.which",
lambda name: "/opt/hostedtoolcache/node/22.18.0/x64/bin/node" if name == "node" else None,
)
args = _runtime_mount_args()
node_index = args.index("/opt/hostedtoolcache/node/22.18.0/x64/bin/node")
assert args[node_index - 1] == "--ro-bind"
assert args[node_index + 1] == "/usr/local/bin/node"
def test_runtime_mounts_skip_the_node_bind_when_node_is_unresolvable(monkeypatch) -> None:
monkeypatch.setattr("workflow_bench.proposer_sandbox.shutil.which", lambda name: None)
args = _runtime_mount_args()
assert "/usr/local/bin/node" not in args
def test_stricter_prefix_freezes_evaluated_skills_and_can_unshare_network(tmp_path: Path) -> None:
clone = tmp_path / "clone"
skill = clone / ".claude" / "skills" / "gitnexus-work"
@ -772,4 +797,3 @@ for line in sys.stdin:
assert bash_result.get("is_error") is not True, bash_result
assert (clone / "bash-called").read_text() == "canary"
assert (clone / "mcp-called").read_text() == "ok"

View file

@ -354,6 +354,17 @@ def _runtime_mount_args() -> list[str]:
path = Path(raw)
if path.exists():
args += ["--ro-bind", raw, raw]
# sanitized_graph.py and runner_sessions.py invoke the sandboxed graph
# CLI at the fixed path /usr/local/bin/node. That's only covered by the
# /usr bind above when node happens to live under /usr/local/bin on the
# host -- true on GitHub-hosted runner images, but not on a self-hosted
# runner where actions/setup-node installs into its own tool-cache
# directory instead. Bind whatever `node` actually resolves to on PATH
# to that same fixed sandbox path so both call sites keep working
# regardless of where the host actually put it.
node_bin = shutil.which("node")
if node_bin:
args += ["--ro-bind", node_bin, "/usr/local/bin/node"]
for raw in (
"/etc/ssl",
"/etc/hosts",
@ -398,7 +409,7 @@ def _create_python3_wrapper(private_root: Path) -> Path:
"""
wrapper = private_root / "python3"
wrapper.write_text("#!/bin/bash\nset -eu\nexec /usr/bin/python3 \"$@\"\n")
wrapper.write_text('#!/bin/bash\nset -eu\nexec /usr/bin/python3 "$@"\n')
wrapper.chmod(0o500)
return wrapper

View file

@ -89,7 +89,7 @@
"_rebaselined": "#1919 review CF3 fix: extended kotlin-local-property-owner (init/accessor destructuring) + new dart-accessor-owner fixture (getter/setter ownership). Fingerprint-only corpus drift; scaling ~1.0."
},
"java": {
"fingerprint": "85fc7af9c3c1bceac76cb4f27214410b04967682a2eaa7e468e26efd1f4e2537",
"fingerprint": "d04298a91beec76d0fa7099b3d71265723be60c1df688969aa954f135dd49686",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata; same-name lexical regions use an O(ancestor-depth) ID-set lookup. Prior d5c59d7dc9e206637515d5aea1163f7c1cdd76410c38c5fe6143d13d19677d6a -> 004a3592998dca1193bd1429a8284513725de7764f2a3eceedaaa984cfd763b4; scaling 0.992 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: Java method-reference/SAM callable flow facts with invocation-result suppression. Prior 062d754764aaa8a6772fb90875c710502a63e3e7a300e633942381ed914faada -> d5c59d7dc9e206637515d5aea1163f7c1cdd76410c38c5fe6143d13d19677d6a; scaling 1.074 < 1.5.",
@ -98,7 +98,8 @@
"_rebaselined_2522_review_fixes": "PR #2522 review fixes: get/test dropped from callableProtocolMethods. Prior 004a3592998dca1193bd1429a8284513725de7764f2a3eceedaaa984cfd763b4 -> f3b4f4b6610e07c3ac90deb1c53d3572b6ad55a36e5d7134984876d30031ff67; scaling ratio re-verified within budget.",
"_rebaselined_2550_instance_model": "PR #2549 (#2550): anonymous class bodies emit synthesized @declaration.class/@declaration.name (Worker$N), an @reference.inherits to the constructed type, and receiver @type-binding.* captures; six new java-* fixtures joined the corpus. Prior f3b4f4b6610e07c3ac90deb1c53d3572b6ad55a36e5d7134984876d30031ff67 -> d79c3b92acfc866094981499b977388ca14f90839bca0c040342ab1cec00aa90; scaling 1.058 < 1.5.",
"_rebaselined_2555_enum_constant_bodies": "PR for #2555: enum constant bodies emit synthesized E$N classes + @reference.inherits to the host enum; anonymous naming follows JLS 13.1 immediately-enclosing-type chains INCLUDING anonymous enclosing types (NestHost$1$1, N$1$1); six new java-* fixtures joined the corpus. Prior d79c3b92acfc866094981499b977388ca14f90839bca0c040342ab1cec00aa90 -> 975b68aaac6d06094260fb0c67f9b1bc03692ba7220669d192aca9dccd5fc0ca; scaling 1.05 < 1.5.",
"_rebaselined_2564_record_capture": "PR for #2564: JAVA_QUERIES gained a (record_declaration name: (identifier) @name) @definition.record capture, previously entirely missing (record_declaration had no structure-phase capture at all, unlike class/interface/enum) - a record's methods existed as ownerless Method nodes with no HAS_METHOD edge. Two new java-* fixtures (java-record-methods, java-new-expr-chain-call) joined the corpus. Prior 975b68aaac6d06094260fb0c67f9b1bc03692ba7220669d192aca9dccd5fc0ca -> 85fc7af9c3c1bceac76cb4f27214410b04967682a2eaa7e468e26efd1f4e2537; scaling 1.059 < 1.5."
"_rebaselined_2564_record_capture": "PR for #2564: JAVA_QUERIES gained a (record_declaration name: (identifier) @name) @definition.record capture, previously entirely missing (record_declaration had no structure-phase capture at all, unlike class/interface/enum) - a record's methods existed as ownerless Method nodes with no HAS_METHOD edge. Two new java-* fixtures (java-record-methods, java-new-expr-chain-call) joined the corpus. Prior 975b68aaac6d06094260fb0c67f9b1bc03692ba7220669d192aca9dccd5fc0ca -> 85fc7af9c3c1bceac76cb4f27214410b04967682a2eaa7e468e26efd1f4e2537; scaling 1.059 < 1.5.",
"_rebaselined_2561_enum_constant_receiver": "PR for #2561: synthesizeJavaAnonymousClassDeclarations now emits a class-scope @type-binding.annotation/name/type per enum constant (constant simple name -> its E$N synthesized class when bodied, else the host enum) so E.CONST.method() resolves through the existing compound-receiver chain walk. Two drivers of the drift, both in the java-enum-constant-body fixture (this bench's corpus IS test/fixtures/lang-resolution): (1) one extra type-binding match per enum_constant from the capture change; (2) review follow-up added a body-less Plain.java enum + EnumConst.dispatchToConstant/dispatchInherited methods (bodied-override, inherited-via-MRO, and body-less dispatch call sites). The review's fail-safe hardening (bodied constant binds ONLY to E$N, never the host enum, when name synthesis fails on a malformed tree) is output-neutral on this well-formed corpus (verified: fingerprint identical with and without it). Prior 85fc7af9c3c1bceac76cb4f27214410b04967682a2eaa7e468e26efd1f4e2537 -> d04298a91beec76d0fa7099b3d71265723be60c1df688969aa954f135dd49686; scaling < 1.5."
},
"typescript": {
"fingerprint": "3280b13d3f9378ab23eee31c2edc779b5a9ae1e7bb510c23a24855b44406d2f4",

View file

@ -372,23 +372,49 @@ function synthesizeJavaAnonymousClassDeclarations(rootNode: SyntaxNode): Capture
// constant's class extends its HOST ENUM (javac semantics), so the
// inherits reference names the enum — giving `mroFor(E$N) ∋ E` and
// keeping bare calls from the body to the enum's own helpers alive
// through the ownership gate's MRO arm. No receiver typeBinding piece:
// constants are not variable initializers; `E.A.hook()` dispatch rides
// the existing enum receiver machinery.
// through the ownership gate's MRO arm.
for (const constant of rootNode.descendantsOfType('enum_constant')) {
const name = synthesizeJavaAnonymousClassName(constant);
if (name === undefined) continue;
const body = constant.childForFieldName?.('body');
if (body === null || body === undefined || body.type !== 'class_body') continue;
out.push({
'@declaration.class': nodeToCapture('@declaration.class', body),
'@declaration.name': syntheticCapture('@declaration.name', body, name),
});
const hostEnum = javaEnclosingEnumNameOf(constant);
if (hostEnum !== undefined) {
const bodyNode = constant.childForFieldName?.('body');
const isBodied = bodyNode !== null && bodyNode !== undefined && bodyNode.type === 'class_body';
const bodiedName = synthesizeJavaAnonymousClassName(constant);
if (bodiedName !== undefined && isBodied) {
out.push({
'@reference.inherits': nodeToCapture('@reference.inherits', body),
'@reference.name': syntheticCapture('@reference.name', body, hostEnum),
'@declaration.class': nodeToCapture('@declaration.class', bodyNode),
'@declaration.name': syntheticCapture('@declaration.name', bodyNode, bodiedName),
});
if (hostEnum !== undefined) {
out.push({
'@reference.inherits': nodeToCapture('@reference.inherits', bodyNode),
'@reference.name': syntheticCapture('@reference.name', bodyNode, hostEnum),
});
}
}
// Receiver dispatch (#2561): `E.CONST.method()` resolves through the
// generic compound-receiver chain walk, which looks up each dotted
// segment via the owning class scope's `typeBindings` map — the same
// mechanism a field declaration uses (`private User user;` binds
// `user` on the class scope). Binding the constant's own simple name
// there — to its synthesized `E$N` class when bodied (MRO includes E,
// so members inherited from the enum still resolve), or to the host
// enum itself when body-less — makes `E.CONST.method()` resolve with
// no changes to the shared receiver-binding machinery.
//
// A bodied constant binds ONLY to its `E$N` class, never the host enum:
// if name synthesis fails on a malformed/error-recovery tree (`bodiedName`
// undefined despite a real body), emit nothing rather than silently
// misattributing an OVERRIDING constant's receiver to the enum's own
// (non-overridden) method — a wrong edge is worse than no edge. Mirrors
// the `object_creation_expression` branch, which skips on synthesis
// failure. `hostEnum` is used only for genuinely body-less constants.
const constantNameNode = constant.childForFieldName?.('name');
const constantType = isBodied ? bodiedName : hostEnum;
if (constantNameNode !== null && constantNameNode !== undefined && constantType !== undefined) {
out.push({
'@type-binding.annotation': nodeToCapture('@type-binding.annotation', constant),
'@type-binding.name': nodeToCapture('@type-binding.name', constantNameNode),
'@type-binding.type': syntheticCapture('@type-binding.type', constant, constantType),
});
}
}

View file

@ -21,4 +21,12 @@ class Unrelated {
public void caller() {
hook();
}
public void dispatchToConstant() {
EnumConst.A.hook();
}
public void dispatchInherited() {
EnumConst.A.log();
}
}

View file

@ -0,0 +1,13 @@
public enum Plain {
A;
public void m() {
System.out.println("plain m");
}
}
class PlainCaller {
public void callPlain() {
Plain.A.m();
}
}

View file

@ -3055,3 +3055,43 @@ describe('Java enum constant bodies (#2555)', () => {
expect(misattributed).toBeUndefined();
}, 60000);
});
// ---------------------------------------------------------------------------
// #2561: E.CONST.method() emits no CALLS edge — the receiver-side follow-up
// to #2555. A bodied constant's receiver must resolve to its synthesized
// E$N class; a body-less constant's receiver must resolve to the host enum
// itself.
// ---------------------------------------------------------------------------
describe('Java enum-constant receiver dispatch (#2561)', () => {
let result: PipelineResult;
beforeAll(async () => {
result = await runPipelineFromRepo(path.join(FIXTURES, 'java-enum-constant-body'), () => {});
}, 60000);
it('resolves EnumConst.A.hook() to the bodied constant override (EnumConst$1.hook#0)', () => {
const calls = getRelationships(result, 'CALLS');
const dispatch = calls.find((c) => c.source === 'dispatchToConstant' && c.target === 'hook');
expect(dispatch).toBeDefined();
expect(dispatch!.rel.targetId).toBe('Method:src/EnumConst.java:EnumConst$1.hook#0');
});
it("resolves EnumConst.A.log() to the host enum's inherited method via E$N's MRO (EnumConst.log#0)", () => {
// A's body overrides hook() but NOT log(); log() lives only on the enum.
// The bodied constant's receiver binds to EnumConst$1, whose MRO includes
// EnumConst (via @reference.inherits), so the qualified call reaches the
// host enum's own method — the inherited-dispatch capability the fix enables.
const calls = getRelationships(result, 'CALLS');
const dispatch = calls.find((c) => c.source === 'dispatchInherited' && c.target === 'log');
expect(dispatch).toBeDefined();
expect(dispatch!.rel.targetId).toBe('Method:src/EnumConst.java:EnumConst.log#0');
});
it("resolves Plain.A.m() to the body-less constant's inherited enum method (Plain.m#0)", () => {
const calls = getRelationships(result, 'CALLS');
const dispatch = calls.find((c) => c.source === 'callPlain' && c.target === 'm');
expect(dispatch).toBeDefined();
expect(dispatch!.rel.targetId).toBe('Method:src/Plain.java:Plain.m#0');
});
});