diff --git a/REPORT.md b/REPORT.md index 29d766e1..73a157f6 100644 --- a/REPORT.md +++ b/REPORT.md @@ -12,6 +12,17 @@ - 검증: `cargo test --all` **583 passed / 0 failed / 1 ignored**(불변 — 코드 무접촉 · ★이 브랜치 base `8c7b473f` 기준이다. 같은 날 앞 회차들의 **579** 는 PR #61 착지 «전» base 의 수라 다르다) · `check-dod-ci-parity` → **「OK 두 축 모두 대칭차 0 — 명령 6개 · toolchain 2개로 «둘 다 일치»」**. - ★후속 추천(★**worklog `.json` `proposals[]` 에 카드 2장으로 «기계 채널»에 실었다** — 초판은 `REPORT` 에만 적어 cockpit 에 **0장**이었다): ⑴**신규 fixture 의 target 규칙**을 세울 것인가(M) ⑵**되돌릴 조건에 «관측자»를 붙인다**(S · 넷 중 셋은 문서를 열어야만 발화한다 — 게이트② 실측). ★초판이 ⑴로 적은 「`NativeMethod` 제3 축 여부」는 ★**검수자가 규명해 닫혔다**(축 2 의 다른 얼굴) ⇒ 카드로 내지 않는다. 상세 = `docs/worklog/2026-09-18-root-fixture-target-decision.md`. +## [2026-09-18] 루트 픽스처 다섯이 재빌드되지 않는 이유 — ★**`-g` 다. 제안이 댄 두 설명은 «둘 다» 틀렸다** (rustjava-adopt-javac-fixture-provenance-verified-p0) +- 무엇을: 채택 제안 `2026-09-17-javac-fixture-provenance-verified#p0`(worklog json `adoptedProposals` 기록). ★**제품 Rust 0줄 · 커밋된 `.class` 바이트 «0 변경»** — 고친 것은 검증 스크립트 한 자리다. +- ★**답**: 커밋본은 **디버그 정보를 달고**(`-g`) 컴파일됐고 스크립트는 **그것 없이** 재빌드했다. javac 기본은 `-g:lines,source` 라 `LocalVariableTable` 이 안 나온다. 단서는 `javap -v -p` 대조(커밋본 902B 에만 `LocalVariableTable` 과 `this`·`args`·`oe`…). +- ★★**제안의 두 설명을 측정으로 반증했다**: ⒜**다른 컴파일러 아니다** — 같은 다섯이 **26.0.1 과 26.0.2.1 에서 똑같이** 다르고 둘 다 `-g` 면 **똑같이 동일**하다(두 판본 각각 직접 실행 · 26.0.1 keg 잔존) ⒝**소스 발산 아니다** — `-g` 만 주면 **지금 소스가 커밋 바이트를 정확히 낸다**. + ⇒ ★★**제안이 가장 걱정한 대가가 사라진다** — `tradeoff` 의 「재컴파일이 동작 변경이 될 수 있다」는 ★**재컴파일 자체가 불요**라 성립하지 않는다. +- ★**고친 한 자리**: 스크립트가 이미 `--release` 를 픽스처에서 읽으므로 **`-g` 도 같은 자리에서** 읽게 했다(상수풀의 `LocalVariableTable` 유무). ★**배선 전에 판별력을 쟀다** — 보유 **5** · 미보유 **107** · ★**5/5 · 오탐 0**. +- ★**양방향**: 정상 **109 rebuilt / 109 reproduced / 0 differed** ↔ ★개악(`-g` 파생 한 줄 no-op) **104 / 5 differed**(✗ 목록이 원래 다섯과 동일) · 복원 0. +- ★**잃는 것**: ⒜스크립트는 **여전히 `rc=1`** — 재빌드 불가 **3건**(형제 참조 소스 · `-sourcepath` 미사용)은 제안이 미해결로 적은 **별 축**이라 넓히지 않았다 ⒝판정이 **한 속성의 유무**에 걸린다(`-g:none` 재생성은 조용히 드리프트로 읽힌다). +- 검증: `cargo test --all` **579 passed / 0 failed / 1 ignored**(불변 — Rust 무접촉) · `check-dod-ci-parity` → **「OK 두 축 모두 대칭차 0 — 명령 6개 · toolchain 2개로 «둘 다 일치»」**. +- ★후속 추천: 재빌드 불가 3건의 컴파일 방법 결정(M) — 상세 = `docs/worklog/2026-09-18-five-fixtures-were-built-with-g.md`. + ## [2026-09-18] 거부된 클래스 파일이 «왜»를 말한다 — 세 층을 관통하는 사유 (rustjava-adopt-classfile-error-cause-decision-p0) - 무엇을: 채택 제안 `2026-09-17-classfile-error-cause-decision#p0`. ★**제품 동작 변경 있음** — `ClassFormatError` 메시지가 **모든 거부에 같던 「Invalid class file」** 에서 **사유별 문장**으로 바뀐다. - ★**제안이 스스로 all-or-nothing 이라 못박았다** — 타입만 고치면 **관측되는 것이 없고**, `||` 사슬을 안 쪼개면 **평평함이 사라지는 게 아니라 옮겨갈 뿐**이다. 넷 다 했다: diff --git a/STATE.md b/STATE.md index 26fe33fd..9b8f8928 100644 --- a/STATE.md +++ b/STATE.md @@ -14,6 +14,13 @@ ★**이유**: 제안의 이득(「숫자 하나로 예측」)이 뒤집힌다 — 52 는 「전-indy·전-nestmate」라는 뜻을 **실제로 갖고**, 펴면 그 구분이 사라지며 유일한 커버리지가 지워진다. ★**잃는 것**: 비균일 잔존(신규 target 규칙 **미수립**) · 16건은 그대로 · ★**본 것은 40 중 20** · `NativeMethod` 차이는 **미규명**. ★`--all` **583/0/1**(불변 · base `8c7b473f` — 앞 회차의 579 는 #61 착지 전 base 다) · 되돌릴 조건 4개를 결정 문서에 명시. +- [rustjava-adopt-javac-fixture-provenance-verified-p0] ★★**루트 픽스처 다섯이 재빌드되지 않는 이유 = `-g`.** 채택 제안 `2026-09-17-javac-fixture-provenance-verified#p0`(worklog json 기록). ★**제품 Rust 0줄 · 커밋 `.class` 바이트 0 변경.** + ★**제안이 댄 두 설명이 둘 다 틀렸다** — 컴파일러 아니다(**26.0.1·26.0.2.1 에서 같은 다섯이 같게 행동**) · 소스 발산 아니다(`-g` 면 **지금 소스가 커밋 바이트를 낸다**). + ⇒ ★**제안 `tradeoff` 의 「재컴파일이 동작 변경이 될 수 있다」가 «성립하지 않는다»** — 재컴파일이 불요다. + ★고친 한 자리: 스크립트가 `--release` 를 픽스처에서 읽듯 **`-g` 도 픽스처에서**(상수풀 `LocalVariableTable`). ★배선 전 판별력 실측 **5/5 · 오탐 0**(보유 5 · 미보유 107). + ★양방향: 정상 **109/109/0 differed** ↔ 개악(파생 한 줄 no-op) **104/5 differed**(원래 다섯과 동일). + ★**잃는 것**: 스크립트는 **여전히 rc=1**(재빌드 불가 3건 = 제안이 미해결로 적은 별 축 · 넓히지 않았다) · 판정이 한 속성 유무에 걸린다. + ★`--all` **579/0/1**(불변) · `check-dod-ci-parity` OK. - [rustjava-adopt-classfile-error-cause-decision-p0] ★★**거부 사유를 세 층에 꿴다 — 「Invalid class file」 하나가 **14개** 문장이 된다(클래스 8 · 필드 3 · 메서드 3).** 채택 제안 `2026-09-17-classfile-error-cause-decision#p0`. ★**제품 동작 변경 있음**(사용자가 보는 `ClassFormatError` 메시지). ★제안이 **all-or-nothing** 이라 못박은 넷을 다 했다: `InvalidFormat(&'static str)` · `InvalidClassFile(&'static str)`(★`From` 이 **버리던** 자리) · 경계 2자리 · ★**`validate_class` 8항 `||` → 규칙마다 `if`**. ★★**사유를 꿰자 «평평한 오류가 가리던 것 둘»이 나왔다**: ⑴테스트가 **어느 층이 거부하는지를 틀리게 믿었다**(검증 아닌 **파서**) ⇒ ★단언을 실측에 맞췄다 ⑵술어 **이름이 낡아 있었다**(「in_the_pool」인데 **적재 가능성까지** 본다) ⇒ 사유는 규칙대로, ★**이름은 안 바꿨다**(리팩터 금지). diff --git a/docs/worklog/2026-09-18-five-fixtures-were-built-with-g.json b/docs/worklog/2026-09-18-five-fixtures-were-built-with-g.json new file mode 100644 index 00000000..3916f33d --- /dev/null +++ b/docs/worklog/2026-09-18-five-fixtures-were-built-with-g.json @@ -0,0 +1,22 @@ +{ + "date": "2026-09-18", + "taskId": "rustjava-adopt-javac-fixture-provenance-verified-p0", + "summary": "The five root fixtures that would not rebuild to their committed bytes were compiled with -g and rebuilt without it. Both explanations the proposal offered — a different compiler, or a source edited after the fixture was built — are refuted by measurement. No fixture was recompiled and no committed byte changed; verify-javac-fixtures.sh now derives -g from the fixture the same way it already derived --release, and all 109 rebuildable fixtures reproduce exactly.", + "changes": [ + "test-data/src/verify-javac-fixtures.sh: per-fixture `debug=-g` derived from the presence of LocalVariableTable in the committed class, passed to the rebuild and echoed in the per-fixture line; header comment records the measurement" + ], + "verification": [ + "before: `sh test-data/src/verify-javac-fixtures.sh test-data` reported 109 rebuilt: 104 reproduced, 5 differed — the same counts the proposal reported, and under javac 26.0.2.1 rather than the 26.0.1 it was measured on", + "after: 109 rebuilt: 109 reproduced, 0 differed", + "MUTATION, kill the one line that derives -g (`debug=-g` becomes a no-op) in the script's own call path: 109 rebuilt: 104 reproduced, 5 differed — and the five named are exactly the original five. Restored: back to 0 differed", + "the discriminator was measured before it was wired: over all of test-data, exactly 5 fixtures carry LocalVariableTable and 107 do not, and those 5 are precisely the ones that differed — 5/5 with zero false positives", + "compiler axis ruled out by hand: rebuilding the five under javac 26.0.1 (the version the proposal measured) gives differ-without-g and byte-identical-with-g, the same as 26.0.2.1", + "cargo test --all: 579 passed / 0 failed / 1 ignored, unchanged — this round touches no Rust", + "no committed .class byte changed: the only file modified under test-data/ is the script" + ], + "issues": [ + "The script still exits 1, because 3 fixtures (ConstantsReader, IncompatibleStatic, VirtualDispatchSemantics) cannot be rebuilt alone — they reference siblings, and the script deliberately omits -sourcepath so that test-data/src/Exception.java does not shadow java.lang.Exception. That is a separate axis the proposal named as unresolved, and this round did not widen into it.", + "The -g axis is now derived, so a fixture rebuilt by hand without -g and committed would be reported as drift on the next run — which is the intended reading, but it means the script's answer depends on an attribute that a future `-g:none` build would remove silently." + ], + "adoptedProposals": ["2026-09-17-javac-fixture-provenance-verified#p0"] +} diff --git a/docs/worklog/2026-09-18-five-fixtures-were-built-with-g.md b/docs/worklog/2026-09-18-five-fixtures-were-built-with-g.md new file mode 100644 index 00000000..166d769e --- /dev/null +++ b/docs/worklog/2026-09-18-five-fixtures-were-built-with-g.md @@ -0,0 +1,68 @@ +# 2026-09-18 — 다섯 픽스처는 `-g` 로 만들어졌다 (rustjava-adopt-javac-fixture-provenance-verified-p0) + +채택 제안 `2026-09-17-javac-fixture-provenance-verified#p0` — +「루트 픽스처 다섯이 왜 커밋된 바이트로 재빌드되지 않는지 알아내라」의 처분이다. +★**제품 Rust 0줄 · 커밋된 `.class` 바이트 0 변경.** + +## 답 — `-g` 다 + +커밋본은 **디버그 정보를 달고** 컴파일됐고, 검증 스크립트는 **그것 없이** 재빌드하고 있었다. +javac 의 기본은 `-g:lines,source`(`LineNumberTable`+`SourceFile`)라 `LocalVariableTable` 은 **안 나온다**. + +| 픽스처 | `-g` 없이 | `-g` | +|---|---|---| +| `MonitorSemantics` · `$CounterWorker` · `$FailingWorker` (`--release 8`) | 다름 | ★**바이트 동일** | +| `NativeMethod` (`--release 8`) | 다름 | ★**바이트 동일** | +| `OddEven` (`--release 21`) | 다름 | ★**바이트 동일** | + +처음 단서는 `javap -v -p` 대조였다 — 커밋본(902B)에만 `LocalVariableTable` 과 그 이름들 +(`this`·`args`·`oe`·`arg`·`i`)이 있고 재빌드본(709B)에는 없다. + +## ★제안의 «두 설명»은 둘 다 틀렸다 + +제안 `why` 는 「**다른 컴파일러**와 **픽스처를 만든 뒤 편집된 소스**가 둘 다 이것을 설명한다」고 적었다. +측정은 **둘 다 반증한다**: + +- **컴파일러 아니다** — 같은 다섯이 **26.0.1 과 26.0.2.1 에서 똑같이** 다르고, 둘 다 `-g` 를 주면 + **똑같이 바이트 동일**하다(두 판본을 각각 직접 돌려 확인 · 26.0.1 keg 가 남아 있어 가능했다). + ※제안은 26.0.1 에서 측정됐고 이 회차는 26.0.2.1 이 기본인 맥에서 쟀는데 **수가 같았다** — 그 자체가 첫 반증이다. +- **소스 발산 아니다** — `-g` 만 주면 **지금 소스가 커밋된 바이트를 정확히 낸다**. 소스와 픽스처는 **어긋나 있지 않다.** + +★★**그래서 제안이 가장 걱정한 대가가 «사라진다»**: 제안 `tradeoff` 는 「두 번째 경우의 처방은 재컴파일이고, +여러 시험이 픽스처 출력을 커밋된 `.txt` 와 비교하므로 재컴파일이 **동작 변경**이 될 수 있다」였다. +⇒ ★**재컴파일이 필요 없다.** 이 회차는 `.class` 를 **한 바이트도** 건드리지 않았다. + +## 고친 «한 자리» + +스크립트는 이미 **`--release` 를 픽스처 자신에서** 읽는다(「nothing external has to be consulted or kept in sync」). +★**`-g` 도 같은 자리에서 같은 방식으로** 읽게 했다 — 커밋된 클래스의 상수풀에 `LocalVariableTable` 이 있으면 `-g`. + +★**배선 «전»에 판별력을 쟀다**: `test-data` 전체에서 `LocalVariableTable` 보유는 **정확히 5개**, +미보유 **107개**이고 그 5개가 **바로 그 다섯**이다 — ★**5/5 · 오탐 0**. + +## 검증 — 양방향 + +| | 결과 | +|---|---| +| 고친 뒤(정상) | ★**109 rebuilt: 109 reproduced, 0 differed** | +| ★개악 — `-g` 파생 **한 줄**을 no-op 으로 | ★**104 reproduced, 5 differed** · ✗ 목록이 **원래 다섯과 동일** | +| 복원 | 0 differed | + +`cargo test --all` **579 passed / 0 failed / 1 ignored**(불변 — Rust 무접촉). + +## ★잃는 것 — 그리고 «안 하면 무엇이 나쁜가» + +- ★**스크립트는 여전히 `rc=1` 이다.** 재빌드 불가 **3건**(`ConstantsReader`·`IncompatibleStatic`· + `VirtualDispatchSemantics`)이 남아 있고, 그것은 제안이 **따로 미해결로 적어 둔 축**이다(형제가 필요한 소스 · + `-sourcepath` 를 일부러 안 쓴다 — `test-data/src/Exception.java` 가 `java.lang.Exception` 을 가리기 때문). + ⇒ ★**드리프트 질문은 답했고, 종료 코드는 아직 0 이 아니다.** 넓히지 않았다. +- ★**파생 축이 하나 늘었다** — 누군가 `-g:none` 으로 다시 만들어 커밋하면 그 속성이 **조용히 사라지고** + 스크립트는 그것을 「드리프트」로 읽는다. 의도한 읽기이지만, **판정이 한 속성의 유무에 걸린다**는 뜻이기도 하다. +- ★**안 하면**: 스크립트가 **영구히 거짓 드리프트 5건**을 보고한다. 그러면 ⑴「재컴파일해서 맞추자」는 + 제안의 `tradeoff` 대로 **정말로 동작 변경 위험**을 감수하게 되거나 ⑵다음 사람이 **같은 조사를 처음부터 다시** 한다. + ★그리고 이 스크립트는 「픽스처를 재생성했을 때 사람이 돌리는 검사」라 ★**거짓 경보가 그 유일한 용도를 무디게 한다.** + +## 후속 + +- ★**재빌드 불가 3건** — 형제를 참조하는 소스를 어떻게 컴파일할 것인가(`-sourcepath` 없이). + ★제안이 「이걸 정하지 않으면 조사가 완결되지 않는다」고 적은 그 축이고, **여기서 넓히지 않았다.** diff --git a/test-data/src/verify-javac-fixtures.sh b/test-data/src/verify-javac-fixtures.sh index 42dbd6ea..9a02e3b5 100755 --- a/test-data/src/verify-javac-fixtures.sh +++ b/test-data/src/verify-javac-fixtures.sh @@ -11,6 +11,14 @@ # `javac 26.0.1 --release 21`, and five sampled root fixtures reproduce under the --release their # recorded major version implies (65->21, 66->22, 70->26). # +# "flags" in that sentence is not decoration. Measured 2026-09-18 over all of test-data with javac +# 26.0.2.1: five fixtures rebuilt to different bytes -- MonitorSemantics and its two named inner +# classes, NativeMethod, OddEven -- and all five reproduce exactly once -g is passed. They were +# built with debug info; the rebuild was not. Neither a different compiler nor a source edited +# after the fact was involved, which were the two explanations on offer: the same five differ under +# 26.0.1 and 26.0.2.1 alike, and the sources are in step with the committed bytes. So the -g is +# derived per fixture below, from the fixture, exactly as --release already was. +# # It is *not* wired into CI, and that is deliberate rather than an oversight: there is no JDK in # .github/workflows/rust.yml and none on PATH here, so a test that needed one would either fail # everywhere or skip everywhere. This is the manual check you run when you regenerate a fixture. @@ -63,23 +71,31 @@ for class in "$DIR"/*.class; do major=$(od -An -tu1 -j6 -N2 "$class" | tr -s ' \n' ' ' | awk '{ print $1 * 256 + $2 }') release=$((major - 44)) + # ...and its own debug attributes say which -g it was built with, read the same way and for the + # same reason: nothing external to keep in sync. javac's default is -g:lines,source, which emits + # LineNumberTable but not LocalVariableTable, so the presence of that attribute name in the pool + # is what separates a `-g` build from a default one. Measured 2026-09-18 over test-data: exactly + # the five fixtures that did not reproduce carry it, and none of the other 107 do. + debug= + if LC_ALL=C grep -aq LocalVariableTable "$class"; then debug=-g; fi + # Deliberately no -sourcepath: test-data/src holds Exception.java, Array.java, Method.java and # friends, so putting it on the source path makes javac resolve `Exception` to the fixture # rather than java.lang.Exception. Measured — it turns clean rebuilds into type errors. The # cost is that a source needing a sibling cannot be rebuilt alone, which is reported as # "could not be rebuilt" rather than as drift. rm -rf "$work/out" && mkdir -p "$work/out" - if ! "$JAVAC" --release "$release" -d "$work/out" "$source" 2>"$work/err"; then - echo " ? $base: could not rebuild at --release $release: $(grep -m1 error "$work/err" || head -1 "$work/err")" >&2 + if ! "$JAVAC" ${debug:+"$debug"} --release "$release" -d "$work/out" "$source" 2>"$work/err"; then + echo " ? $base: could not rebuild at --release $release${debug:+ $debug}: $(grep -m1 error "$work/err" || head -1 "$work/err")" >&2 unbuildable=$((unbuildable + 1)) continue fi checked=$((checked + 1)) if cmp -s "$work/out/$base.class" "$class"; then - echo " ✓ $base (--release $release)" + echo " ✓ $base (--release $release${debug:+ $debug})" else - echo " ✗ $base (--release $release): rebuilt bytes differ from the committed fixture" >&2 + echo " ✗ $base (--release $release${debug:+ $debug}): rebuilt bytes differ from the committed fixture" >&2 differed=$((differed + 1)) fi done