From 38c02a2d1c70bbbbff961115a4f76d3af06e435f Mon Sep 17 00:00:00 2001 From: jun0 Date: Thu, 17 Sep 2026 05:22:21 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[rustjava-adopt-indy-fixture-jdk-pin-and-sl?= =?UTF-8?q?ot-accounting-p1]=20test(fixtures):=20=EC=96=B4=EB=8A=90=20java?= =?UTF-8?q?c=20=EC=9D=B4=20=EB=A7=8C=EB=93=A4=EC=97=88=EB=82=98=EB=A5=BC?= =?UTF-8?q?=20=EA=B8=B0=EB=A1=9D=EC=97=90=EC=84=9C=20=EA=B2=80=EC=A6=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B0=94=EA=BE=BC=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 제안의 결론 둘이 실측으로 반증됐다. 「offline 으로 검증할 수 없다」는 거짓이다 — javac 은 같은 소스·플래그·컴파일러에 결정적이라 기록된 도구로 재빌드하면 indy 6개가 바이트 단위로 재현된다. 「형상 단언이 한 픽스처만 덮는다」도 거짓이다 — javac 산출 indy 3/3 이 이미 갖고 있고, 제안이 든 condy 위험은 ConstantKinds 의 Dynamic 개수가 잠근다. 그래서 제안이 불가능하다고 본 쪽을 만들었다. --release 는 픽스처 자신의 major-44 로 읽어 외부 표에 의존하지 않고, 명시한 JAVAC 가 안 되면 조용히 대체하지 않고 rc=2 로 멈추며, 「못 만들었다」와 「만들었는데 다르다」를 가른다. JDK 가 CI 에도 PATH 에도 없어 배선하지 않았다. -sourcepath 는 넣었다가 되돌렸다: test-data/src/Exception.java 가 java.lang.Exception 을 가린다. 루트 일반화는 109 rebuilt / 104 재현 / 5 상이 — 원인은 단정하지 않고 후속으로 넘겼다. --- REPORT.md | 23 +++++ STATE.md | 29 ++++++ ...-17-javac-fixture-provenance-verified.json | 39 ++++++++ ...09-17-javac-fixture-provenance-verified.md | 87 +++++++++++++++++ test-data/src/verify-javac-fixtures.sh | 94 +++++++++++++++++++ tests/test_fixture_pins.rs | 16 +++- 6 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 docs/worklog/2026-09-17-javac-fixture-provenance-verified.json create mode 100644 docs/worklog/2026-09-17-javac-fixture-provenance-verified.md create mode 100755 test-data/src/verify-javac-fixtures.sh diff --git a/REPORT.md b/REPORT.md index 7cc068db..91de9a3e 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,4 +1,27 @@ # REPORT +## [2026-09-17] 「어느 javac 이 만들었나」를 «기록»이 아니라 «검증»으로 바꿨다 (rustjava-adopt-indy-fixture-jdk-pin-and-slot-accounting-p1) +- 무엇을: 기록된 도구로 **다시 빌드해 바이트를 비교**하는 검사를 만들었다. ★**제품 코드 무접촉**(Rust 변경은 doc 주석 1곳). +- 왜: 채택 제안 `2026-09-16-indy-fixture-jdk-pin-and-slot-accounting#p1`. +- 사용자 영향: 없다(시험 위생). ★바뀐 것은 ★**「javac 26.0.1 로 만들었다」가 «주장»에서 «검증된 사실»이 된 것**이다. +- ★★**제안의 결론 «둘»이 실측으로 반증됐다**: + ⑴「**Nothing offline can verify** a recorded compiler version … buys **provenance, not enforcement**」 → + ★**거짓**. javac 은 같은 소스·플래그·컴파일러에 **결정적**이라 ★**`test-data/indy` 6개가 바이트 단위로 재현된다**. + ⑵「강제 가능한 축은 `constant_pool.rs` 의 상수 개수뿐이고 **한 픽스처만** 덮는다」 → ★**거짓**. + ★**javac 산출 indy 픽스처 3/3 이 형상 단언을 갖는다** — `ConstantKinds`(태그별 정확한 개수) · + `StringConcat`(신원 4축 + 인자가 «가리키는 값» + ★**바이트 창** `[15,6,0,35]`) · `Lambda`(`args[0]==args[2]!=args[1]`). + ★제안이 든 위험(「현대 javac 이 enum switch 를 condy 로 낸다」)은 ★**`ConstantKinds` 의 Dynamic 개수 3 이 이미 잠근다.** +- ★**만든 것**: `test-data/src/verify-javac-fixtures.sh` — ★`--release` 를 **픽스처 자신의 major − 44** 로 읽어 + **외부 표에 의존하지 않고**, ★명시한 `JAVAC` 가 안 되면 **조용히 대체하지 않고 rc=2** 로 멈추며, + ★**「못 만들었다」와 「만들었는데 다르다」를 «가른다»**(합치면 발견을 과장한다). + ★**CI 에 배선하지 «않았다»** — 워크플로에도 PATH 에도 JDK 가 없어 **어디서나 실패하거나 어디서나 건너뛴다**. +- ★**실패담 둘(밟은 대로 적는다)**: ⑴`command -v javac` 이 macOS **스텁**을 고른다 ⇒ **실행해서** 판별 ⑵★`-sourcepath` 를 넣었다가 + **더 나빠졌다** — `test-data/src/Exception.java` 가 `java.lang.Exception` 을 가려 멀쩡하던 재현이 타입 오류로 무너졌다 ⇒ **되돌리고 그 대가를 따로 보고**. +- ★**개악 대조**: 커밋본 **1바이트 반전** → ★**✗ 감지** · 복원 → 6/6 재현 · 명시 `JAVAC` 부재 → ★**rc=2(통과 아님)**. +- ★**일반화 — 값만 적고 고치지 않았다**: 루트에 돌리니 **109 rebuilt · 104 재현 · 5 상이 · 3 재빌드 불가**. + 상이 5건(`MonitorSemantics`×3 · `NativeMethod` @8 · `OddEven` @21)의 ★**원인은 단정하지 않는다**(다른 컴파일러 ↔ 빌드 후 소스 수정이 둘 다 맞는다). + ★그래도 적는 이유: ★**제안이 걱정한 드리프트가 «실재»한다는 첫 직접 증거**다. +- 검증: `cargo test --all` **572 / 0 failed / 1 ignored**(doc 주석만 바꿔 **불변**) · DoD 7명령 rc=0. +- 후속 추천: 루트 5건이 **왜** 재현되지 않는지 규명(M) — 상세 = `docs/worklog/2026-09-17-javac-fixture-provenance-verified.md`. ## [2026-09-16] 부트스트랩 메서드의 «정적 인자» 인덱스를 경계 검사한다 (rustjava-adopt-bound-bootstrap-method-attr-index-p1) - 무엇을: JVMS 4.7.23 의 `bootstrap_arguments` 는 상수 풀 인덱스인데 ★**아무도 그것이 실재하는지 보지 않았다.** 이제 풀에 «없는» 인덱스를 가리키면 **거부**한다. diff --git a/STATE.md b/STATE.md index 3b4a97fe..f5917f81 100644 --- a/STATE.md +++ b/STATE.md @@ -4,6 +4,35 @@ (없음 — 2026-09-16 실측: 착수 시 진행 티켓 0 · 열린 PR 0. ※「열린 PR 0」은 ★**이 회차 PR 착지 시점 기준**이다 — 회신 시점에는 그 PR 자신이 열려 있다) ## 완료 +- [rustjava-adopt-indy-fixture-jdk-pin-and-slot-accounting-p1] ★★**「어느 javac 이 만들었나」를 «기록»에서 «검증»으로 바꿨다.** + 채택 제안 `2026-09-16-indy-fixture-jdk-pin-and-slot-accounting#p1`(worklog json `adoptedProposals` 기록). ★**제품 코드 무접촉.** + ★★**제안의 결론 «둘»이 실측으로 반증됐다 — 그래서 제안이 «불가능»하다고 적은 쪽을 만들었다**: + ⑴「**Nothing offline can verify** a recorded compiler version … buys **provenance, not enforcement**」 → ★**거짓**: + javac 은 같은 소스·플래그·컴파일러에 **결정적**이라, 기록된 도구(`javac 26.0.1 --release 21`)로 재빌드하니 + ★**`test-data/indy` 의 6개가 «바이트 단위로 동일»**했다. ⇒ ★**기록이 «재현»으로 검증된다.** + ⑵「강제 가능한 축은 `constant_pool.rs` 의 상수 개수뿐이고 **한 픽스처만** 덮는다」 → ★**거짓**: + ★**javac 산출 indy 픽스처 «3/3»이 형상 단언 보유** — `ConstantKinds`(MethodType 1·Dynamic 3·MethodHandle 7·InvokeDynamic 3) · + `StringConcat`(부트스트랩 **4축** + 인자가 «가리키는 값» `"a\u{1}"` + ★**바이트 창** `[15,6,0,35]` · 「layout changed」로 실패) · + `Lambda`(`LambdaMetafactory.metafactory` · 인자 3 · ★`args[0]==args[2]!=args[1]`). + ★**제안이 든 위험(「현대 javac 은 enum switch 를 condy 로 낸다」)은 `ConstantKinds` 의 Dynamic **3** 이 이미 잠그고 있다.** + ★**만든 것**: `test-data/src/verify-javac-fixtures.sh` — ⑴★`--release` 를 **픽스처 자신의 major − 44** 에서 읽어 + ★**외부 표(형제 PR #57 의 버전 표)에 의존하지 않는다**(동기화할 것이 없다 · 미착지 의존도 없다) + ⑵★명시한 `JAVAC` 가 안 되면 **조용히 다른 컴파일러로 대체하지 않고 rc=2** — 「무엇이 검증했나」가 흐려지면 안 된다 + ⑶★**「못 만들었다」와 「만들었는데 다르다」를 «가른다»** — 합치면 발견을 과장한다. + ★**CI 에 배선하지 «않았다»**: `.github/workflows/rust.yml` 에 JDK 가 없고 PATH 에도 없다 ⇒ + JDK 를 요구하는 테스트는 ★**어디서나 실패하거나 어디서나 건너뛴다.** 이건 «재생성했을 때 사람이 돌리는» 검사다(doc 주석에 명시). + ★★**실패담 둘을 남긴다 — 이 회차가 실제로 밟았다**: ⑴`command -v javac` 이 macOS **스텁**(실행되는데 「JDK 없음」)을 고른다 + ⇒ 경로가 아니라 **실행해서** 판별한다 ⑵★**`-sourcepath` 를 넣었다가 «더 나빠졌다»** — `test-data/src` 에 **`Exception.java`**· + `Array.java`·`Method.java` 가 있어 javac 이 `Exception` 을 ★**`java.lang.Exception` 이 아니라 그 픽스처로** 해석했다 + (멀쩡히 재현되던 파일들이 `incompatible types` 로 무너졌다) ⇒ **되돌리고 그 대가**(형제 참조 소스는 홀로 재빌드 불가)를 **따로 보고**한다. + ★**개악 대조**: 커밋본 **마지막 1바이트 반전** → ★**✗ 감지** · 복원 → **6 reproduced** · 명시 `JAVAC` 부재 → ★**rc=2 「nothing was verified」**(통과 아님). + ★★**일반화 — 시켜 보고 «나온 값»만 적었다(고치지 않았다)**: 루트 `sh … test-data` → + **109 rebuilt · 104 재현 · ★5 상이 · 3 재빌드 불가**. 상이 5건 = `MonitorSemantics`(+내부 2) · `NativeMethod`(`--release 8`) · `OddEven`(`--release 21`). + ★**원인은 단정하지 않는다** — 「다른 컴파일러」와 「빌드 뒤 소스 수정」이 **둘 다 이 관측과 맞는다**. ★범위 밖이라 후속(M)으로 넘겼다. + ★**그래도 적는 이유**: ★**제안이 걱정한 드리프트가 «실재»한다는 첫 «직접» 증거**다(그전까지는 버전 분포에서의 추론이었다). + ★**직전 회차가 «커밋하지 않기로» 한 개악 하네스와 다른 종류다** — 그건 **제품 소스를 치환**해 죽으면 트리를 오염시켰고, + 이건 **읽고 비교만** 한다(실패해도 트리 무변) ⇒ 그래서 **남겼다.** + ★`cargo test --all` **572 / 0 failed / 1 ignored**(doc 주석만 바꿔 **불변**) · DoD **7줄 전건 rc=0**. - [rustjava-adopt-bound-bootstrap-method-attr-index-p1] ★★**부트스트랩 «정적 인자» 인덱스를 경계 검사한다 — 「감지되나 판정되지 않던」 자리를 닫았다.** 채택 제안 `2026-09-16-bound-bootstrap-method-attr-index#p1`(worklog json `adoptedProposals` 기록). ★**전/후**: `UnsupportedOperationException` → ★`ClassFormatError`. ★참조 JVM(OpenJDK 26.0.1) → diff --git a/docs/worklog/2026-09-17-javac-fixture-provenance-verified.json b/docs/worklog/2026-09-17-javac-fixture-provenance-verified.json new file mode 100644 index 00000000..010670a7 --- /dev/null +++ b/docs/worklog/2026-09-17-javac-fixture-provenance-verified.json @@ -0,0 +1,39 @@ +{ + "schema": "worklog/v1", + "date": "2026-09-17", + "taskId": "rustjava-adopt-indy-fixture-jdk-pin-and-slot-accounting-p1", + "summary": "Turned the recorded compiler from an assertion into a check: rebuilding test-data/indy with the recorded javac reproduces all six class files byte for byte. Both of the proposal's blocking claims — that nothing offline can verify a compiler record, and that shape assertions cover only one fixture — are false, measured.", + "changes": [ + "test-data/src/verify-javac-fixtures.sh: new. Rebuilds javac-compiled fixtures and compares bytes, reading the --release from each fixture's own major version so nothing has to be kept in sync", + "tests/test_fixture_pins.rs: the pin's doc comment now records that the compiler was verified by rebuilding, with the command and the date, instead of only naming it" + ], + "verification": [ + "test-data/indy: 6 rebuilt, 6 reproduced byte-for-byte, 0 differed, under javac 26.0.1 --release 21 — the toolchain the comment records", + "the proposal's claim that shape assertions cover one fixture is false: ConstantKinds has exact tag counts in classfile/src/constant_pool.rs, StringConcat has the four identity axes plus what its static argument points at plus a byte-window check in classfile/tests/test.rs, and Lambda has its bootstrap identity plus the args[0] == args[2] != args[1] relation", + "controlled mutation: flipping the last byte of the committed StringConcat.class is detected as differed; restoring returns 6 reproduced", + "an explicitly set JAVAC that does not work exits 2 with 'nothing was verified' rather than silently falling back to another compiler", + "generalization probe over test-data root: 109 rebuilt, 104 reproduced, 5 differed (MonitorSemantics and two inner classes, NativeMethod at --release 8, OddEven at --release 21), 3 could not be rebuilt alone", + "-sourcepath was tried and reverted: test-data/src contains Exception.java, so putting it on the source path makes javac resolve Exception to the fixture instead of java.lang.Exception and turns clean rebuilds into type errors", + "cargo test --all: 572 passed / 0 failed / 1 ignored, unchanged — the only Rust change is a doc comment", + "DoD 7 commands all rc=0" + ], + "issues": [ + "The check cannot run in CI: there is no JDK in the workflow and none on PATH here. It is a manual check, so it is only as good as the habit of running it after regenerating a fixture.", + "Five root fixtures do not reproduce. Whether that is a different compiler or a source edited after the fixture was built was not determined — both fit the observation, and chasing it is outside this ticket's target of test-data/src/indy.", + "Three root fixtures cannot be rebuilt in isolation because their sources reference sibling classes, and -sourcepath is not usable here for the reason above." + ], + "adoptedProposals": [ + "2026-09-16-indy-fixture-jdk-pin-and-slot-accounting#p1" + ], + "proposals": [ + { + "title": "Find out why five root fixtures do not rebuild to their committed bytes", + "plainSummary": "Rebuilding the main test-data fixtures reproduces 104 of them exactly. Five come out different, and we do not know why.", + "userBenefit": "Either the fixtures get back in step with their sources, or we learn that a test has been asserting against bytecode nobody can regenerate — both are better than not knowing.", + "why": "Measured by the script this round added: MonitorSemantics and its two inner classes and NativeMethod differ at --release 8, OddEven differs at --release 21. A different compiler and a source edited after the fixture was built both explain it, and the difference matters: the first is harmless provenance drift, the second means the fixture and its source have diverged.", + "tradeoff": "The likely fix for the second case is recompiling, which changes bytes — and several tests compare fixture output against committed .txt files, so a recompile can turn into a behaviour change. Also three more fixtures cannot be rebuilt alone at all, so the survey cannot be completed without deciding how to compile sources that reference siblings, in a directory where Exception.java shadows java.lang.Exception.", + "effort": "M", + "target": "test-data/, test-data/src/" + } + ] +} diff --git a/docs/worklog/2026-09-17-javac-fixture-provenance-verified.md b/docs/worklog/2026-09-17-javac-fixture-provenance-verified.md new file mode 100644 index 00000000..6850eefc --- /dev/null +++ b/docs/worklog/2026-09-17-javac-fixture-provenance-verified.md @@ -0,0 +1,87 @@ +# 2026-09-17 — 「어느 javac 이 만들었나」를 «기록»이 아니라 «검증»으로 바꿨다 + +티켓 `rustjava-adopt-indy-fixture-jdk-pin-and-slot-accounting-p1` — 채택 제안 +`2026-09-16-indy-fixture-jdk-pin-and-slot-accounting#p1`. + +★**제안의 결론 두 개가 실측으로 반증됐다.** 그래서 제안이 «불가능»하다고 적은 쪽을 만들었다. + +## ⓒ 반증 1 — 「검증할 수 없다」는 거짓이다 + +제안 tradeoff: 「**Nothing offline can verify a recorded compiler version** — a record is only as good +as the person writing it, so this buys **provenance, not enforcement**.」 + +★**실측**: `javac` 은 같은 소스·같은 플래그·같은 컴파일러에 대해 **결정적**이다. 기록된 도구 +(`javac 26.0.1 --release 21`)로 다시 빌드하니 ★**`test-data/indy` 의 6개 클래스 파일이 «바이트 단위로 동일»**했다. + +``` +$ sh test-data/src/verify-javac-fixtures.sh +javac: javac 26.0.1 + ✓ ConstantKinds$Op · ConstantKinds$Suit · ConstantKinds · Lambda$Op · Lambda · StringConcat (--release 21) +6 rebuilt: 6 reproduced, 0 differed; 0 could not be rebuilt +``` + +⇒ ★**기록이 «검증»된다.** 「사람이 적은 만큼만 믿을 수 있다」가 아니라 **재현으로 확인된다.** + +## ⓐ·ⓒ 반증 2 — 「강제 가능한 축은 한 픽스처만 덮는다」도 거짓이다 + +제안 why: 「The only assertions holding that axis today are the constant counts in +`classfile/src/constant_pool.rs`, and they **cover one fixture**.」 + +★**실측 — javac 산출 indy 픽스처 «셋 모두» 형상 단언을 갖고 있다**: + +| 픽스처 | 형상 단언 | 어디 | +|---|---|---| +| `ConstantKinds` | 태그별 **정확한 개수**(MethodType 1 · Dynamic 3 · MethodHandle 7 · InvokeDynamic 3) | `classfile/src/constant_pool.rs` | +| `StringConcat` | 부트스트랩 **kind·class·name·descriptor** 4축 + 인자 수 + ★**인자가 «가리키는 값»**(`"a\u{1}"` 레시피) · 게다가 ★**바이트 창** `[15, 6, 0, 35]` 를 찾아 「layout changed」로 실패한다 | `classfile/tests/test.rs` | +| `Lambda` | 부트스트랩 `LambdaMetafactory.metafactory` · 인자 **3개** · ★**`args[0] == args[2] != args[1]`**(samMethodType/instantiatedMethodType 동일성) | `classfile/tests/test.rs` | + +⇒ ★**제안이 「더 만들어야 한다」고 본 「enforceable half」는 이미 3/3 있다.** +★그리고 제안이 예로 든 위험(「enum switch 가 현대 javac 에서 condy 가 된다」)은 ★**`ConstantKinds` 의 Dynamic 개수 3 이 정확히 그것을 잠그고 있다.** + +## 만든 것 — `test-data/src/verify-javac-fixtures.sh` + +기록을 **검사**로 바꾸는 최소 도구. ★**재빌드해서 바이트를 비교한다.** +- ★**`--release` 를 «픽스처 자신»에서 읽는다**(major − 44) — 외부 표에 의존하지 않아 **동기화할 것이 없다**. +- ★**명시한 `JAVAC` 가 안 되면 «다른 컴파일러로 조용히 대체하지 않는다»** — rc=2 로 멈춘다. + (그러지 않으면 「무엇이 검증했나」가 흐려진다.) +- ★**CI 에 배선하지 «않았다»** — `.github/workflows/rust.yml` 에 JDK 가 없고 이 맥의 PATH 에도 없다. + JDK 를 요구하는 테스트는 **어디서나 실패하거나 어디서나 건너뛴다**. 이건 «재생성했을 때 사람이 돌리는» 검사다. +- ★**「못 만들었다」와 「만들었는데 다르다」를 «가른다»** — 합치면 발견을 과장하게 된다(아래 실패담 참조). + +## ★실패담 둘 — 이 회차가 실제로 밟은 것 + +⑴**`command -v javac` 이 macOS 스텁을 찾는다**(실행 파일인데 「JDK 없음」을 출력한다) ⇒ 경로가 아니라 **실행해서** 고른다. +⑵★**`-sourcepath` 를 넣었다가 «더 나빠졌다»** — `test-data/src` 에는 **`Exception.java`·`Array.java`·`Method.java`** 가 있어 +소스 경로에 올리면 javac 이 `Exception` 을 ★**`java.lang.Exception` 이 아니라 그 픽스처로** 해석한다 +(실측: 멀쩡히 재현되던 파일들이 `incompatible types: Exception cannot be converted to Throwable` 로 무너졌다). +⇒ **되돌렸고, 그 대가**(형제 클래스를 참조하는 소스는 홀로 재빌드 불가)를 ★**「could not be rebuilt」로 «따로» 보고**한다. + +## 개악 대조(양방향) + +| 조작 | 결과 | +|---|---| +| 커밋된 `StringConcat.class` 의 **마지막 1바이트** 반전 | ★**✗ 감지** — `rebuilt bytes differ from the committed fixture` | +| 복원 | **6 reproduced, 0 differed** | +| 명시한 `JAVAC` 가 없는 경우 | ★**rc=2 「nothing was verified」** — ★**«통과»가 아니다** | + +## ★일반화 — 시켜 보고 «나온 값»을 적는다(고치지는 않았다) + +같은 도구를 루트에 돌렸다(`sh … test-data`): +``` +109 rebuilt: 104 reproduced, 5 differed; 3 could not be rebuilt +``` +★**다른 5개**: `MonitorSemantics`(+내부 클래스 2) · `NativeMethod` — 셋 다 `--release 8` · `OddEven` — `--release 21`. +★**원인은 여기서 «단정하지 않는다»** — 「다른 컴파일러로 빌드됐다」와 「빌드 뒤 소스가 수정됐다」가 **둘 다 이 관측과 맞는다.** +★**3개는 홀로 재빌드 불가**(형제 클래스 참조) — 위 `-sourcepath` 제약의 결과다. +⇒ ★**이 회차의 범위(제안 대상 = `test-data/src/indy/`)를 넘으므로 고치지 않았고, 후속으로 넘겼다.** +★**그래도 적는 이유**: 이 수가 ★**제안이 걱정한 드리프트가 «실재»한다는 첫 직접 증거**다 — 가정이 아니다. + +## 잃는 것 / 안 하면 무엇이 나쁜가 + +⒜**잃는 것**: ⑴**스크립트 1개**가 는다(≈70줄 · 읽기 전용 · 트리를 고치지 않는다) ⑵★**CI 가 돌리지 않는다** — +JDK 가 없으니 **자동으로 지켜지지 않는다**. 이것이 이 축의 진짜 한계이고, 숨기지 않는다. +⑶픽스처를 의도적으로 재생성하면 **한 번 돌려 확인하는 습관**이 필요하다(문서·doc 주석에 적었다). +★**왜 그래도 남기나**: 직전 회차가 «커밋하지 않기로» 한 개악 하네스와 **다른 종류**다 — +그건 **제품 소스를 치환**해서 죽으면 트리를 오염시켰고, 이건 **읽고 비교만** 한다(실패해도 트리 무변). +⒝**안 하면**: 「javac 26.0.1 로 만들었다」가 ★**영원히 «주장»으로 남는다.** 그리고 루트의 5건이 보여 주듯 +★**드리프트는 이미 일어나 있다** — 확인할 수단이 없으면 다음 회차도 그것을 못 본다. diff --git a/test-data/src/verify-javac-fixtures.sh b/test-data/src/verify-javac-fixtures.sh new file mode 100755 index 00000000..42dbd6ea --- /dev/null +++ b/test-data/src/verify-javac-fixtures.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# Rebuild the javac-compiled fixtures and check they come out byte-for-byte identical. +# +# What this answers: *which compiler* built a fixture. The class file version pin +# (tests/test_fixture_pins.rs, test-data/class-file-versions.txt) proves what a fixture targets — +# javac 21 and javac 26 both stamp 65.0 at --release 21 — but the bytes differ if the compiler or +# its flags differ, so reproducing them is the check the version alone cannot be. +# +# javac is deterministic for the same source, flags and compiler, which is what makes this work: +# measured 2026-09-17, all six test-data/indy class files reproduce exactly under +# `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). +# +# 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. +# +# Usage: sh test-data/src/verify-javac-fixtures.sh [directory] # default: test-data/indy +# JAVAC=/path/to/javac sh test-data/src/verify-javac-fixtures.sh +# +# The --release to rebuild with is read from the fixture itself: a class file's major version is +# its target (major - 44), so nothing external has to be consulted or kept in sync. +# +# Exit: 0 all reproduced · 1 something differed or could not be rebuilt · 2 no javac (nothing was +# checked — not a pass) + +set -eu + +DIR="${1:-test-data/indy}" + +# `command -v javac` finds macOS's stub, which is executable and then reports there is no JDK — so +# candidates are probed by running them, not by testing the path. +if [ -n "${JAVAC:-}" ]; then + candidates="$JAVAC" # explicit wins outright: falling back would +else # hide which compiler actually did the check + candidates="javac /opt/homebrew/opt/openjdk/bin/javac" +fi +found="" +for candidate in $candidates; do + if "$candidate" -version >/dev/null 2>&1; then found="$candidate"; break; fi +done +if [ -z "$found" ]; then + echo "no working javac in: $candidates (set JAVAC=...); nothing was verified" >&2 + exit 2 +fi +JAVAC="$found" +echo "javac: $("$JAVAC" -version 2>&1)" + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +checked=0 +differed=0 +unbuildable=0 + +for class in "$DIR"/*.class; do + base=$(basename "$class" .class) + outer=${base%%\$*} # Foo$Bar.class comes from Foo.java + sub=${DIR#test-data} # "test-data" -> "", "test-data/indy" -> "/indy" + source="test-data/src${sub}/$outer.java" + [ -f "$source" ] || continue # generator output has no .java; not this check's job + + # The fixture's own major version is the target it was built for (JVMS 4.1: 65 = Java 21). + major=$(od -An -tu1 -j6 -N2 "$class" | tr -s ' \n' ' ' | awk '{ print $1 * 256 + $2 }') + release=$((major - 44)) + + # 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 + unbuildable=$((unbuildable + 1)) + continue + fi + + checked=$((checked + 1)) + if cmp -s "$work/out/$base.class" "$class"; then + echo " ✓ $base (--release $release)" + else + echo " ✗ $base (--release $release): rebuilt bytes differ from the committed fixture" >&2 + differed=$((differed + 1)) + fi +done + +if [ "$checked" -eq 0 ]; then + echo "no javac-compiled fixtures found in $DIR; nothing was verified" >&2 + exit 2 +fi +# "could not rebuild" is reported apart from "rebuilt and differs": one says the fixture drifted, +# the other says this script could not ask the question. Conflating them overstates the finding. +echo "$checked rebuilt: $((checked - differed)) reproduced, $differed differed; $unbuildable could not be rebuilt" +[ "$differed" -eq 0 ] && [ "$unbuildable" -eq 0 ] diff --git a/tests/test_fixture_pins.rs b/tests/test_fixture_pins.rs index bf6f8caf..b3708d8e 100644 --- a/tests/test_fixture_pins.rs +++ b/tests/test_fixture_pins.rs @@ -18,8 +18,20 @@ use std::{ffi::OsStr, fs, path::Path}; /// catches — "whatever javac happened to be installed" is how these files got here in the first /// place. It does *not* pin the compiler binary; two javac versions at `--release 21` both emit /// 65.0. That second axis is held by the constant-shape assertions on the same fixtures -/// (`classfile/src/constant_pool.rs`, `tests/test_class_format.rs`), which count the exact -/// constants javac put in them. +/// (`classfile/src/constant_pool.rs`, `classfile/tests/test.rs`, `tests/test_class_format.rs`), +/// which pin the exact constants javac put in them. +/// +/// The line above used to be the whole record, and a record is only as good as whoever wrote it. +/// It is not just a record any more: javac is deterministic for the same source, flags and +/// compiler, so rebuilding is a check. +/// +/// ```text +/// sh test-data/src/verify-javac-fixtures.sh # needs a JDK; CI has none, so it is manual +/// ``` +/// +/// Measured 2026-09-17 with `javac 26.0.1`: all six class files here rebuild **byte for byte**. +/// So the compiler recorded above is the compiler that produced them, verified rather than +/// asserted. Re-run it after regenerating a fixture; a mismatch means the toolchain moved. const PINNED_MAJOR: u16 = 65; const PINNED_MINOR: u16 = 0; From 12c857fe945bd0f6ba913e0b0195345449d696ed Mon Sep 17 00:00:00 2001 From: jun0 Date: Thu, 17 Sep 2026 09:30:13 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[rustjava-adopt-indy-fixture-jdk-pin-and-sl?= =?UTF-8?q?ot-accounting-p1]=20docs(state):=20=EA=B2=8C=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=E2=91=A2=20=EC=B0=A9=EC=A7=80=20=EA=B8=B0=EB=A1=9D=20=EB=8F=99?= =?UTF-8?q?=EB=B4=89=20(PR=20#58=20=C2=B7=20--merge)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- STATE.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/STATE.md b/STATE.md index 12ef7981..ee40bf8a 100644 --- a/STATE.md +++ b/STATE.md @@ -33,6 +33,12 @@ ★**직전 회차가 «커밋하지 않기로» 한 개악 하네스와 다른 종류다** — 그건 **제품 소스를 치환**해 죽으면 트리를 오염시켰고, 이건 **읽고 비교만** 한다(실패해도 트리 무변) ⇒ 그래서 **남겼다.** ★`cargo test --all` **572 / 0 failed / 1 ignored**(doc 주석만 바꿔 **불변**) · DoD **7줄 전건 rc=0**. + ★★**게이트③ 착지 — PR #58 · `--merge`**(등재 repo · `merge_strategy: merge` 선언분). 게이트② **approve** · + 핀 `38c02a2d` **불이동**(착수 실측 2026-09-17T00:27:39Z · 핀에서 `ci-presence` **rc=0 CI_GREEN**). + ★**충돌은 원장 2파일뿐**(`REPORT.md`·`STATE.md`) — 형제 **#55** 착지분과 겹쳤고 코드 파일 충돌 **0**. + 해소는 전건 보존·합집합·**시간순**: 이 회차(`38c02a2d` 05:22)가 main 쪽 최신 항목(`30a31bda` 04:04)보다 **뒤**라 위에 얹었다. + ★줄 소실 **0**(양방향) · 합집합 밖 신규줄 **0** · ★계약 12 착지 diff numstat **해소 전후 동일**(6파일 · 해소면 밖 변경 0). + ★배포 **0** — 이 저장소에 배포 워크플로가 **없다**(CI 2종 + 스케줄 2종 + PR 댓글 1종) · 자식 PR **0건** · 주기 자동 커밋 **0건**. - [rustjava-adopt-cp-tag-passthrough-detectable-p0-fix] ★★**신원 4축을 «각각» 관측 가능하게 했다 — 감사의 「고칠 것이 없다」를 정정한다.** 게이트② **request-changes** 승계(PR #55 · 핀 `ab13a3c7`). ★**제품 코드 무접촉** — 없던 것은 **픽스처**다. ★★**무엇이 틀렸나**: 직전 감사의 **M7**(「신원 4축 검사 제거」)은 네 비교를 ★**한꺼번에** 지운다 ⇒ 그 red 가 증명하는 것은 From 6f838ef08293a776578ff52dc9ffd1fc6268d12a Mon Sep 17 00:00:00 2001 From: jun0 Date: Thu, 17 Sep 2026 11:15:45 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[rustjava-adopt-indy-fixture-jdk-pin-and-sl?= =?UTF-8?q?ot-accounting-p1]=20docs(state):=20=EA=B2=8C=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=E2=91=A2=202=ED=9A=8C=EC=B0=A8=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(base=20=EC=9E=AC=EB=8B=B9=EA=B9=80=20?= =?UTF-8?q?=C2=B7=20PR=20#58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- STATE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/STATE.md b/STATE.md index f8cb5a71..3c297a14 100644 --- a/STATE.md +++ b/STATE.md @@ -76,6 +76,9 @@ 해소는 전건 보존·합집합·**시간순**: 이 회차(`38c02a2d` 05:22)가 main 쪽 최신 항목(`30a31bda` 04:04)보다 **뒤**라 위에 얹었다. ★줄 소실 **0**(양방향) · 합집합 밖 신규줄 **0** · ★계약 12 착지 diff numstat **해소 전후 동일**(6파일 · 해소면 밖 변경 0). ★배포 **0** — 이 저장소에 배포 워크플로가 **없다**(CI 2종 + 스케줄 2종 + PR 댓글 1종) · 자식 PR **0건** · 주기 자동 커밋 **0건**. + ★★**게이트③ 2회차 — 형제 #59 가 그 사이 착지(`66bc49e8`)해 base 를 다시 당겼다.** 충돌은 또 **원장 2파일뿐**(코드 충돌 0). + ★**이번엔 시간순이 «뒤집혔다»** — main 쪽 항목(`3b04712e` 06:52)이 이 회차(`38c02a2d` 05:22)보다 **뒤**라 **위**에 얹었다. + ★두 번의 base 당김을 거쳐도 이 PR 의 기여 numstat 은 **불변**(`94/0` 검증 스크립트 · `14/2` 핀 테스트 · worklog 2건). - [rustjava-adopt-cp-tag-passthrough-detectable-p0-fix] ★★**신원 4축을 «각각» 관측 가능하게 했다 — 감사의 「고칠 것이 없다」를 정정한다.** 게이트② **request-changes** 승계(PR #55 · 핀 `ab13a3c7`). ★**제품 코드 무접촉** — 없던 것은 **픽스처**다. ★★**무엇이 틀렸나**: 직전 감사의 **M7**(「신원 4축 검사 제거」)은 네 비교를 ★**한꺼번에** 지운다 ⇒ 그 red 가 증명하는 것은