mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-29 06:32:28 +00:00
Merge branch 'main' into fix/2605-rename-edit-count
This commit is contained in:
commit
0e28486289
7 changed files with 141 additions and 18 deletions
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,4 +21,12 @@ class Unrelated {
|
|||
public void caller() {
|
||||
hook();
|
||||
}
|
||||
|
||||
public void dispatchToConstant() {
|
||||
EnumConst.A.hook();
|
||||
}
|
||||
|
||||
public void dispatchInherited() {
|
||||
EnumConst.A.log();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
gitnexus/test/fixtures/lang-resolution/java-enum-constant-body/src/Plain.java
vendored
Normal file
13
gitnexus/test/fixtures/lang-resolution/java-enum-constant-body/src/Plain.java
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue