From 53ca34090369d518bf8a1ef59a0ab9809d9b0d07 Mon Sep 17 00:00:00 2001 From: jun0 Date: Thu, 17 Sep 2026 04:31:19 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[rustjava-adopt-cp-tag-passthrough-detectab?= =?UTF-8?q?le-p1]=20docs(classfile):=20=EC=9B=90=EC=9D=B8=EC=9D=84=20?= =?UTF-8?q?=EC=8B=A4=EC=9D=84=20=EA=B0=92=EC=9D=80=20=EC=9E=88=EB=8B=A4=20?= =?UTF-8?q?=E2=80=94=20=EC=A0=9C=EC=95=88=EC=9D=98=20=EC=97=AD=EC=82=AC?= =?UTF-8?q?=C2=B7=EC=B1=85=EC=9E=84=C2=B7=EB=B2=94=EC=9C=84=EB=A5=BC=20?= =?UTF-8?q?=EB=B0=94=EB=A1=9C=EC=9E=A1=EB=8A=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 낱말이 Decide 다. 판정은 「할 값 있다」이지만 제안의 세 전제가 틀렸다. 역사: 822504b 는 error.rs 를 자르지 않고 만들었다. 그 이전 ClassInfo::parse 는 Option 을 돌려줘 실패에 정보가 0 이었다 — 그 커밋은 개선이었고, 「again」·「restore」는 성립하지 않는다. 책임: upstream 이 막고 있지 않다. 우리는 5커밋 뒤이고 그중 이 파일들을 만지는 것은 0건이며, 우리 fork 는 이미 이 crate 에서 +560 줄 갈렸다. read-only 규율은 보내는 것을 금할 뿐이다. 범위: target 이 1파일인데 평탄화는 3층이다. error.rs 만 고치면 관측 변화가 0 이고, 진짜 비용은 validate_class 의 8항 || 사슬을 쪼개는 것 — 이 티켓이 금지한 리팩터다. 그래서 구현하지 않고 범위를 바로잡아 후속으로 넘긴다. 제안이 근거로 인용한 주석이 거짓이었으므로 그 자리에서 고쳤다. --- REPORT.md | 25 +++++ STATE.md | 26 +++++ ...-09-17-classfile-error-cause-decision.json | 38 ++++++++ ...26-09-17-classfile-error-cause-decision.md | 94 +++++++++++++++++++ tests/test_class_format.rs | 13 ++- 5 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 docs/worklog/2026-09-17-classfile-error-cause-decision.json create mode 100644 docs/worklog/2026-09-17-classfile-error-cause-decision.md diff --git a/REPORT.md b/REPORT.md index 7cc068db..e30a0118 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,4 +1,29 @@ # REPORT +## [2026-09-17] `ClassFileError` 가 «원인»을 실어야 하는가 — ★**판정: 할 값 있다. 단 제안의 이름·이유·범위가 셋 다 틀렸다** (rustjava-adopt-cp-tag-passthrough-detectable-p1) +- 무엇을: 낱말이 **`Decide`** 인 제안을 **판정**했다. ★**코드 변경은 «틀린 주석 한 곳» 정정뿐** — 구현은 후속으로 넘겼다. +- 왜: 채택 제안 `2026-09-16-cp-tag-passthrough-detectable#p1`. +- 사용자 영향: 없다(판정). ★바뀐 것은 ★**착지한 트리가 «거짓 전제»를 나르지 않게 된 것**이다. +- ★★**제안의 «역사»가 거짓이다 — 두 겹으로**: ⑴`822504b` 는 `classfile/src/error.rs` 를 **«자르지» 않고 «만들었다»** + (`new file` · 내용이 지금과 **동일한 2변형**) ⑵그 이전에는 `ClassInfo::parse` 가 **`Option`** 를 돌려줬다 + (실패에 정보 **0**) ⇒ ★**그 커밋은 «후퇴»가 아니라 «개선»이었고, 「again」·「restore」는 성립하지 않는다.** +- ★★**「upstream 이 해야 한다」도 거짓**: `upstream/main` 기준 **5커밋 뒤**이고 그중 이 파일들을 만지는 것 **0건** · + upstream 이 `error.rs` 를 만진 커밋은 **1건(생성)** 뿐 · ★**우리는 이미 이 crate 에서 크게 갈렸다** + (`constant_pool.rs` **+211/−6** · `validation.rs` **+137/−0** · `attribute.rs` **+129/−3** · `opcode.rs` **+83/−7**). + ※`AGENTS.md` 의 read-only 규율은 **upstream 으로 «보내는 것»**을 금할 뿐 로컬 변경을 금하지 않는다. +- ★★**ⓑ 설계는 «한 enum 건너» 이미 증명돼 있다**: `ClassDefinitionError::UnsupportedFeature(&'static str)` 가 + **5곳**에서 쓰이며 `"ldc of a method handle"` 같은 문장을 낸다 ⇒ ★**새 발명이 아니라 «일관성 회복»**이고, 모양도 자명하다. +- ★**이득(과장하지 않는다)**: kind-only 단언 **8곳**이 원인을 이름 부를 수 있게 되고, 참조 JVM 격차가 줄어든다 + (OpenJDK 는 `Multiple BootstrapMethods attributes…`·`argument_index 65535 has bad constant type` 를 내는데 우리는 전부 `Invalid class file`). + ★**그러나 「픽스처보다 강한 자물쇠」는 «절반만» 참이다** — 원인은 「어느 검사가 울렸나」를, 픽스처는 「그 검사가 관측 가능한가」를 잠근다. + ★**증거**: 직전 `-fix` 가 찾은 구멍(신원 4축 중 3축 미관측)은 **링크 축**이라 원인을 실었어도 **안 잡혔다**. +- ★★**비용 — 제안의 `target: classfile/src/error.rs` 는 틀렸다(1파일이 아니라 3층)**: 평탄화가 아래로 두 번 더 일어난다 + (`InvalidFormat` → `ClassDefinitionError::InvalidClassFile`(From 이 원인을 버린다) → ★**하드코딩 문자열 2곳**). + ⇒ ★**`error.rs` 만 고치면 관측 변화가 «0»** 이다. ★**진짜 비용은 `validate_class` 의 8항 `||` 사슬을 쪼개는 것**이고, + 그것은 이 티켓이 **금지한 리팩터**다 ⇒ ★**여기서 구현하지 않고 «범위를 바로잡아» 넘겼다.** +- 검증: `cargo test --all` **572 / 0 failed / 1 ignored**(주석만 바꿔 불변) · DoD 7명령 rc=0. +- 후속 추천: 원인을 **3층에 관통**시키고 `validate_class` 를 **검사마다 반환**으로 쪼갠다(M) — + ★「⑴만 하고 멈추면 관측 변화 0 · ⑷ 없이 하면 평탄함이 «이사»할 뿐」까지 제안에 적었다. + 상세 = `docs/worklog/2026-09-17-classfile-error-cause-decision.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..c93a4d96 100644 --- a/STATE.md +++ b/STATE.md @@ -4,6 +4,32 @@ (없음 — 2026-09-16 실측: 착수 시 진행 티켓 0 · 열린 PR 0. ※「열린 PR 0」은 ★**이 회차 PR 착지 시점 기준**이다 — 회신 시점에는 그 PR 자신이 열려 있다) ## 완료 +- [rustjava-adopt-cp-tag-passthrough-detectable-p1] ★★**판정 — `ClassFileError` 에 «원인»을 실을 값은 있다. 단 제안의 이름·이유·범위가 «셋 다» 틀렸다.** + 채택 제안 `2026-09-16-cp-tag-passthrough-detectable#p1`(worklog json `adoptedProposals` 기록). 낱말이 **`Decide`** 다 + ⇒ ★**코드 변경은 «틀린 주석 한 곳» 정정뿐** · 구현은 **범위를 바로잡아 후속으로** 넘겼다. + ★★**⑴역사가 거짓이다**: `822504b` 는 `error.rs` 를 **«자르지» 않고 «만들었다»**(`new file` · 지금과 동일한 2변형)이고, + 그 이전 `ClassInfo::parse` 는 **`Option`**(실패에 정보 **0** · `.unwrap()` 투성이)였다 ⇒ ★**그 커밋은 «개선»이었다.** + ⇒ ★**「carry a cause **again**」·「restoring」은 성립하지 않는다 — 이 리니지에 원인이 실렸던 시기는 «없다».** + ★★**⑵「upstream 이 해야 한다」도 거짓**: `upstream/main` 기준 **5커밋 뒤** · 그중 이 파일들을 만지는 것 **0건** · + upstream 의 `error.rs` 접촉은 **1건(생성)** 뿐(이 crate 에서 가장 안정된 파일) · ★**우리는 이미 이 crate 에서 크게 갈렸다** + (`constant_pool.rs` +211/−6 · `validation.rs` +137/−0 · `attribute.rs` +129/−3 · `opcode.rs` +83/−7). + ※`AGENTS.md` read-only 는 **upstream 으로 «보내는 것»** 금지이지 로컬 변경 금지가 아니다. + ★★**⑶범위도 틀렸다 — `target: classfile/src/error.rs` 는 1파일인데 평탄화는 «3층»이다**: + `InvalidFormat`(생산 9곳) → `ClassDefinitionError::InvalidClassFile`(**From 이 원인을 버린다**) → + ★**`"Invalid class file"` 하드코딩 2곳**(`src/runtime.rs:189` · `test-utils/src/lib.rs:334`). + ⇒ ★**`error.rs` 만 고치면 «관측 변화 0»** — 아무도 원인을 넣지 않고 아무도 읽지 않는, 이 저장소가 규탄하는 그 형태다. + ★★**진짜 비용은 `validate_class` 의 «8항 `||` 사슬»을 쪼개는 것**이다(원인이 갈리는 유일한 자리) — + ★그것은 이 티켓이 **명시적으로 금지한 리팩터**(계약 3)라 ★**여기서 구현하지 않았다.** + ★★**ⓑ 그런데 설계는 «한 enum 건너» 이미 증명돼 있다** — `ClassDefinitionError::UnsupportedFeature(&'static str)` 가 + **5곳**에서 쓰이며 `"ldc of a method handle"` 같은 문장을 낸다 ⇒ ★**새 발명이 아니라 «일관성 회복»**이고 이것이 「할 값 있다」의 근거다. + ★**이득을 과장하지 않는다**: kind-only 단언 **8곳**이 원인을 이름 부를 수 있고 참조 JVM 격차가 준다. + ★**그러나 제안의 「픽스처보다 강한 자물쇠」는 «절반만» 참** — 원인은 «어느 검사가 울렸나», 픽스처는 «그 검사가 관측 가능한가»를 잠근다. + ★**증거**: 직전 `-fix` 가 찾은 구멍(신원 4축 중 3축 미관측)은 **링크 축**이라 ★**원인을 실었어도 안 잡혔다** ⇒ **대체가 아니라 «더하기»다.** + ★**지금 고친 것**: `tests/test_class_format.rs` 머리 주석 — ★**제안이 근거로 인용한 바로 그 문장**이 거짓이었다 + (「cut 822504b」·「Restoring it needs upstream variants」) ⇒ 그 자리에서 정정했다. + ★**착지한 트리가 거짓을 나르면 다음 사람이 같은 전제로 같은 제안을 다시 만든다.** + ★두 번째 언급(「`ClassFileError` 가 평탄화한다」)은 **참**이라 **건드리지 않았다**(과잉 편집 0). + ★`cargo test --all` **572 / 0 failed / 1 ignored**(주석만 바꿔 **불변**) · 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-classfile-error-cause-decision.json b/docs/worklog/2026-09-17-classfile-error-cause-decision.json new file mode 100644 index 00000000..6da62877 --- /dev/null +++ b/docs/worklog/2026-09-17-classfile-error-cause-decision.json @@ -0,0 +1,38 @@ +{ + "schema": "worklog/v1", + "date": "2026-09-17", + "taskId": "rustjava-adopt-cp-tag-passthrough-detectable-p1", + "summary": "Decided whether ClassFileError should carry a cause. Verdict: worth doing, but the proposal's history, blame and scope are all wrong, and doing it properly needs a refactor this ticket forbids. Recorded the corrected scope and fixed the false comment the proposal cited.", + "changes": [ + "tests/test_class_format.rs: the header comment claimed the variants were cut upstream at 822504b and that restoring them needs upstream changes. Both are false; replaced with what is actually true and why.", + "docs/worklog/2026-09-17-classfile-error-cause-decision.{md,json}: the decision, its evidence, and the scope handed to a follow-up" + ], + "verification": [ + "current state: classfile/src/error.rs still has two variants, and 8 assertions in tests/test_class_format.rs check only the ClassFormatError kind", + "822504b created classfile/src/error.rs as a new file with these same two variants; before it, ClassInfo::parse returned Option, so failure carried nothing at all — the commit was an improvement, not a cut", + "upstream is not the blocker: we are 5 commits behind upstream/main and none of them touch these files; upstream has touched classfile/src/error.rs exactly once (its creation); our fork already diverges by +211/-6 in constant_pool.rs, +137/-0 in validation.rs, +129/-3 in attribute.rs, +83/-7 in opcode.rs", + "the pattern already exists one enum over: ClassDefinitionError::UnsupportedFeature(&'static str) is used at 5 sites and produces messages like 'ldc of a method handle'", + "the flattening happens at three layers, not one: ClassFileError::InvalidFormat (9 production sites) -> ClassDefinitionError::InvalidClassFile (the From impl drops any cause) -> a hardcoded \"Invalid class file\" string at src/runtime.rs:189 and test-utils/src/lib.rs:334", + "validate_class evaluates an eight-term || chain and returns one InvalidFormat, so a per-check cause requires splitting that chain — a refactor this ticket forbids", + "cargo test --all: 572 passed / 0 failed / 1 ignored, unchanged (the only code change is a comment)", + "DoD 7 commands all rc=0" + ], + "issues": [ + "The proposal's claim that a cause would be 'a stronger lock than a carefully shaped fixture' is half true: a cause locks which check fired, a fixture locks whether the check is observable at all. The hole the sibling -fix round found was in linking, not parse errors, so a cause would not have caught it.", + "Implementing only the proposal's stated target (classfile/src/error.rs) would produce no observable change, since nothing would set the cause and nothing would read it." + ], + "adoptedProposals": [ + "2026-09-16-cp-tag-passthrough-detectable#p1" + ], + "proposals": [ + { + "title": "Thread a cause through ClassFileError, and split validate_class so it can differ", + "plainSummary": "Give class file rejections a reason, all the way out to the exception message a user sees.", + "userBenefit": "A rejected class file says what is wrong with it — 'unknown constant pool tag', 'bootstrap argument names nothing' — instead of the same 'Invalid class file' for every cause, which is what a real JVM does.", + "why": "Decided in this round after measuring: the design already exists one enum over (ClassDefinitionError::UnsupportedFeature carries &'static str at 5 sites), upstream is not the blocker, and 8 assertions currently can only check the exception kind. The work is ClassFileError::InvalidFormat(&'static str), the same on ClassDefinitionError::InvalidClassFile so the From impl stops dropping it, the two hardcoded message sites (src/runtime.rs, test-utils/src/lib.rs), and splitting validate_class's eight-term || chain so each check can name itself.", + "tradeoff": "Doing only the error type changes nothing observable, and doing it without splitting the || chain just moves the flatness rather than removing it — so this is an all-or-nothing scope, and it includes a refactor of validate_class. It also does not replace the shaped-fixture discipline: a cause says which check fired, not whether each axis of a multi-axis check is observable.", + "effort": "M", + "target": "classfile/src/error.rs, classfile/src/validation.rs, jvm-bytecode/src/error.rs, src/runtime.rs, test-utils/src/lib.rs" + } + ] +} diff --git a/docs/worklog/2026-09-17-classfile-error-cause-decision.md b/docs/worklog/2026-09-17-classfile-error-cause-decision.md new file mode 100644 index 00000000..b55f8371 --- /dev/null +++ b/docs/worklog/2026-09-17-classfile-error-cause-decision.md @@ -0,0 +1,94 @@ +# 2026-09-17 — `ClassFileError` 가 «원인»을 실어야 하는가 — 판정 + +티켓 `rustjava-adopt-cp-tag-passthrough-detectable-p1` — 채택 제안 `2026-09-16-cp-tag-passthrough-detectable#p1`. +낱말이 **`Decide`** 다. ★**코드 변경은 «틀린 주석 한 곳» 정정뿐**이고 산출물은 **판정과 그 근거**다. + +## 판정 + +> ★**할 값이 있다. 단 제안이 적은 이름·이유·범위가 «셋 다» 틀렸으므로 그대로 집행하면 안 된다.** +> ★**이 회차에서 구현하지 «않는다»** — 제대로 하려면 `validate_class` 의 8항 `||` 사슬을 쪼개야 하고, +> 그것은 이 티켓이 **명시적으로 금지한 리팩터**다(계약 3). ⇒ **범위를 바로잡아 후속으로 넘긴다.** + +## ⓐ 제안이 «지금도» 참인가 — 현상은 참이다 + +`classfile/src/error.rs` 는 지금도 **2변형**뿐이다(`InvalidFormat` · `UnsupportedVersion(u16)`), +그래서 모든 파스·검증 실패가 **같은 문장**으로 나온다. 실측: `tests/test_class_format.rs` 에서 +★**`ClassFormatError` «종류만» 단언하는 자리가 8곳**이다. ⇒ 「왜 거부됐는지 단언할 수 없다」는 **참**. + +## ⓒ ★그러나 제안의 «역사»는 거짓이다 — 두 겹으로 + +제안 제목: 「carry a cause **again**」 · why: 「(cut 822504b)」 · tradeoff: 「**needs upstream changes** … +this repo is a fork whose upstream contact is deliberately read-only」. + +실측: +1. ★**822504b 는 «자르지» 않았다 — 그 파일을 «만들었다».** `git show 822504b -- classfile/src/error.rs` 는 + **`new file`** 이고 내용이 지금과 **동일한 2변형**이다. +2. ★**그 이전에는 원인이 아니라 «아무것도» 없었다** — `git show 822504b^:classfile/src/class.rs` 기준 + `ClassInfo::parse` 는 **`Option`** 를 돌려줬다(실패에 정보 0 · 게다가 `.unwrap()` 투성이). + ⇒ ★**822504b 는 «후퇴»가 아니라 «개선»이었다**(`Option` → `Result<_, ClassFileError>`). +3. ⇒ ★**「again」도 「restore」도 성립하지 않는다.** 이 리니지에 원인이 실려 있던 시기는 **없다.** + +그리고 「upstream 이 해야 한다」는 ★**우리 fork 의 실측과 어긋난다**: +- `upstream/main` 기준 우리는 **5커밋 뒤**이고, 그 5개 중 이 파일들을 만지는 것은 **0건**이다. +- `classfile/src/error.rs` 를 upstream 이 만진 커밋은 **1건**(=생성)뿐 — ★**이 crate 에서 가장 안정된 파일**이다. +- ★**우리는 이미 이 crate 에서 크게 갈라져 있다**: `constant_pool.rs` **+211/−6** · `validation.rs` **+137/−0** · + `attribute.rs` **+129/−3** · `opcode.rs` **+83/−7**. +⇒ ★**「순수 로컬이 아니다」는 거짓**이다. `AGENTS.md` 의 read-only 규율은 **upstream 으로 «보내는 것»**을 금할 뿐, +로컬 변경을 금하지 않는다(우리는 매 회차 이 crate 를 고친다). + +## ⓑ ★이미 같은 축이 «한 enum 건너» 있다 — 이것이 판정을 「할 값 있다」로 민다 + +`jvm-bytecode/src/error.rs` 의 형제 타입은 **이미 원인을 싣는다**: +`UnsupportedFeature(&'static str)`(**5곳**에서 사용 · `"ldc of a method handle"` 같은 문장이 그 결과다) · +`UnsupportedClassVersion(u16)`. ⇒ ★**설계는 이 저장소에서 이미 «증명»돼 있고, `InvalidClassFile` 만 예외다.** +★그러니 이것은 «새 발명»이 아니라 **일관성 회복**이고, 모양도 자명하다(`InvalidFormat(&'static str)`). + +## 이득 — 실측하되 «과장하지 않는다» + +- ★**8곳**의 kind-only 단언이 원인을 직접 이름 부를 수 있게 된다. +- ★**참조 JVM 격차가 줄어든다**: OpenJDK 26 은 같은 파일들에 `Multiple BootstrapMethods attributes in class file X` · + `argument_index 65535 has bad constant type` 를 낸다(이 리니지가 직접 관측한 문장들). 우리는 전부 `Invalid class file` 이다. + ★**이 리니지의 주제가 «정직한 진단»인데 진단 문면 자체가 평탄하다** — 그 모순이 이 제안의 진짜 값이다. +- ★★**그러나 제안의 「a stronger lock than a carefully shaped fixture」는 «절반만» 참이다.** + 원인은 **「어느 검사가 울렸나」**를 잠그고, 픽스처는 **「그 검사가 관측 가능한가」**를 잠근다 — **다른 자물쇠다.** + ★**증거**: 바로 직전 `-fix` 회차가 찾은 구멍(신원 4축 중 3축 미관측)은 ★**파스 오류가 아니라 «링크» 축**이라 + 원인을 실었어도 **잡히지 않았다.** ⇒ ★**원인은 픽스처 규율을 «대체»하지 못한다. 더한다.** + +## 비용 — 제안이 적은 `target: classfile/src/error.rs` 는 «틀렸다»(1파일이 아니라 3층) + +평탄화는 ★**아래로 두 번 더** 일어난다(실측): +``` +ClassFileError::InvalidFormat (classfile/src/error.rs · 생산 9곳: validation 6 · class 3) + → ClassDefinitionError::InvalidClassFile (jvm-bytecode/src/error.rs · From impl 이 원인을 버린다) + → jvm.exception("java/lang/ClassFormatError", "Invalid class file") ← ★문자열 «하드코딩» 2곳 + (src/runtime.rs:189 · test-utils/src/lib.rs:334) +``` +⇒ ★**`error.rs` 만 고치면 관측 가능한 변화가 «0» 이다** — 원인을 아무도 넣지 않고 아무도 읽지 않는, +이 저장소가 반복해 규탄한 「통과하지만 아무것도 재지 않는」 형태가 된다. + +★★**그리고 진짜 비용은 따로 있다**: `validate_class` 는 **8항 `||` 사슬**을 한 번에 평가하고 **하나의** `InvalidFormat` 을 낸다. +원인을 «검사마다» 다르게 하려면 ★**그 사슬을 쪼개야** 한다 — ★그것은 이 티켓이 금지한 **리팩터**다(계약 3). +⇒ ★**그래서 여기서 구현하지 않는다.** 범위를 틀린 채로 집행하는 것보다 **바로잡아 넘기는 것**이 싸다. + +## 후속에 넘기는 «정확한» 범위 + +⑴`ClassFileError::InvalidFormat(&'static str)` — 형제 타입의 `UnsupportedFeature(&'static str)` 와 **같은 모양**(no_std 안전). +⑵`ClassDefinitionError::InvalidClassFile(&'static str)` + `From` 이 원인을 **버리지 않게**. +⑶예외 문면 **2곳**이 그 문자열을 쓰게(`src/runtime.rs` · `test-utils/src/lib.rs`) — ★**두 곳을 같이 고쳐야** 테스트가 본다. +⑷`validate_class` 의 8항 사슬을 **검사마다 반환**으로 쪼갠다(원인이 갈리는 유일한 자리). +⑸그 뒤에 **단언을 조인다** — kind-only 8곳 중 «원인이 갈리는» 것부터. +★**⑴만 하고 멈추면 안 된다**(관측 변화 0). ★**⑷ 없이 ⑴~⑶만 하면** 모든 검증 실패가 같은 원인 문자열을 이고 **평탄함이 이사할 뿐**이다. + +## 이 회차가 «지금» 고친 것 — 틀린 주석 한 곳 + +`tests/test_class_format.rs` 머리 주석이 **제안의 근거로 인용된 바로 그 문장**이고, 그것이 **거짓**이었다 +(「cut 822504b」·「Restoring it needs upstream variants」). ⇒ ★**그 자리에서 고쳤다** — 착지한 트리가 거짓을 나르면 +다음 사람이 같은 잘못된 전제로 같은 제안을 다시 만든다. ★**두 번째 언급(`ClassFileError` 가 평탄화한다)은 «참»이라 건드리지 않았다.** + +## 잃는 것 / 안 하면 무엇이 나쁜가 + +⒜**잃는 것(이 회차)**: ★**주석 한 곳의 변경뿐** — 동작·테스트 수 불변(`cargo test --all` **572 / 0 failed / 1 ignored**). +★**판정을 «지금» 집행하지 않는 대가**는 있다: 8곳의 단언이 당분간 kind-only 로 남고, 그 사이 새로 들어오는 +검증 규칙도 같은 평탄한 문장을 쓴다(이 회차 직전에 착지한 `#p0`·`#p1` 이 정확히 그 예다 — 둘 다 `InvalidFormat` 을 낸다). +⒝**안 하면**: 사용자는 «무엇이 잘못됐는지» 못 듣고, 테스트는 ★**「깨졌다」와 「다른 이유로 깨졌다」를 구별하지 못한다** — +그 구별 불가가 #49 의 테스트를 단언 조이기로 못 고치게 만든 **바로 그 원인**이다(그 회차는 픽스처를 다시 지어 우회했다). diff --git a/tests/test_class_format.rs b/tests/test_class_format.rs index 334dd3a5..40bd848a 100644 --- a/tests/test_class_format.rs +++ b/tests/test_class_format.rs @@ -28,9 +28,16 @@ fn hello_class() -> Vec { fs::read("test-data/Hello.class").unwrap() } -// Only the exception *kind* is asserted, not the message: upstream `ClassFileError` -// (cut 822504b) collapses every parse failure into a flat "Invalid class file", -// so per-cause wording is no longer available. Restoring it needs upstream variants. +// Only the exception *kind* is asserted, not the message: `ClassFileError::InvalidFormat` carries +// no cause, and the two places that turn it into a Java exception hardcode the string "Invalid +// class file", so there is no per-cause wording to assert. +// +// This note used to say the variants were "cut" upstream at 822504b and that restoring them "needs +// upstream variants". Both halves were wrong, measured: that commit *created* classfile/src/error.rs +// — before it, `ClassInfo::parse` returned `Option`, so failure carried nothing at all — and this +// fork already diverges by hundreds of lines in this crate, so nothing about the change is upstream's +// to make. What it does need is a cause threaded through three layers and `validate_class`'s eight-term +// `||` chain split so the cause can differ per check. See docs/worklog/2026-09-17-classfile-error-cause-decision.md. #[tokio::test] async fn test_truncated_class_raises_class_format_error() { let (dir, path) = fixture("TruncatedHello.class", &hello_class()[..60]); From b80edce9a9978ca4037d6b78830692eac4d03dfa Mon Sep 17 00:00:00 2001 From: jun0 Date: Thu, 17 Sep 2026 09:46:22 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[rustjava-adopt-cp-tag-passthrough-detectab?= =?UTF-8?q?le-p1]=20docs(state):=20=EA=B2=8C=EC=9D=B4=ED=8A=B8=E2=91=A2=20?= =?UTF-8?q?=EC=B0=A9=EC=A7=80=20=EA=B8=B0=EB=A1=9D=20=EB=8F=99=EB=B4=89=20?= =?UTF-8?q?(PR=20#56=20=C2=B7=20--merge)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- STATE.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/STATE.md b/STATE.md index eb7f0b72..8a48fee3 100644 --- a/STATE.md +++ b/STATE.md @@ -30,6 +30,14 @@ ★**착지한 트리가 거짓을 나르면 다음 사람이 같은 전제로 같은 제안을 다시 만든다.** ★두 번째 언급(「`ClassFileError` 가 평탄화한다」)은 **참**이라 **건드리지 않았다**(과잉 편집 0). ★`cargo test --all` **572 / 0 failed / 1 ignored**(주석만 바꿔 **불변**) · DoD **7줄 전건 rc=0**. + ★★**게이트③ 착지 — PR #56 · `--merge`**(등재 repo · `merge_strategy: merge` 선언분). 게이트② **approve** · + 핀 `53ca3409` **불이동**(착수 실측 2026-09-17T00:42:56Z · 핀에서 `ci-presence` **rc=0 CI_GREEN**). + ★**충돌은 원장 2파일뿐**(`REPORT.md`·`STATE.md`) — 형제 **#55** 착지분과 겹쳤고 **코드 파일 충돌 0**. + ★★**`tests/test_class_format.rs` 는 «자동 병합»됐고 그것을 믿지 않고 쟀다** — 양측 델타가 둘 다 살아 있다 + (이쪽 **10/3** · main 측 **32/0** · 추가·삭제줄 다중집합 **전건 일치** · 스위트 14/14 green). + 원장 해소는 전건 보존·합집합·**시간순**: 이 회차(`53ca3409` 04:31)가 main 쪽 최신 항목(`30a31bda` 04:04)보다 **뒤**라 위에 얹었다. + ★줄 소실 **0**(양방향) · 합집합 밖 신규줄 **0** · 계약 12 착지 diff numstat **해소 전후 동일**. + ★배포 **0**(이 저장소에 배포 워크플로 없음) · 자식 PR **0건** · 주기 자동 커밋 **0건**. - [rustjava-adopt-cp-tag-passthrough-detectable-p0-fix] ★★**신원 4축을 «각각» 관측 가능하게 했다 — 감사의 「고칠 것이 없다」를 정정한다.** 게이트② **request-changes** 승계(PR #55 · 핀 `ab13a3c7`). ★**제품 코드 무접촉** — 없던 것은 **픽스처**다. ★★**무엇이 틀렸나**: 직전 감사의 **M7**(「신원 4축 검사 제거」)은 네 비교를 ★**한꺼번에** 지운다 ⇒ 그 red 가 증명하는 것은 From a48a5931e8e7602eb226060de776401575c0fd89 Mon Sep 17 00:00:00 2001 From: jun0 Date: Thu, 17 Sep 2026 11:33:51 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[rustjava-adopt-cp-tag-passthrough-detectab?= =?UTF-8?q?le-p1]=20docs(state):=20=EA=B2=8C=EC=9D=B4=ED=8A=B8=E2=91=A2=20?= =?UTF-8?q?2=ED=9A=8C=EC=B0=A8=20=EA=B8=B0=EB=A1=9D=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20(base=20=EC=9E=AC=EB=8B=B9=EA=B9=80=20=C2=B7=20PR=20#56)?= 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 4998ed46..1d37cacf 100644 --- a/STATE.md +++ b/STATE.md @@ -113,6 +113,9 @@ 원장 해소는 전건 보존·합집합·**시간순**: 이 회차(`53ca3409` 04:31)가 main 쪽 최신 항목(`30a31bda` 04:04)보다 **뒤**라 위에 얹었다. ★줄 소실 **0**(양방향) · 합집합 밖 신규줄 **0** · 계약 12 착지 diff numstat **해소 전후 동일**. ★배포 **0**(이 저장소에 배포 워크플로 없음) · 자식 PR **0건** · 주기 자동 커밋 **0건**. + ★★**게이트③ 2회차 — 형제 #59(`66bc49e8`)·#58(`6e016a27`)이 그 사이 착지해 base 를 다시 당겼다.** 충돌은 또 **원장 2파일뿐**(코드 충돌 0). + ★**시간순이 이 회차를 «맨 아래»로 보낸다**(06:52 · 05:22 > 04:31) — 「내 것이 위」가 아니라 «잰 시각»이 규칙이다. + ★`tests/test_class_format.rs` 는 두 번 다 **자동 병합**됐고 두 번 다 양방향으로 쟀다(이쪽 **10/3 불변** · main 델타 **누락 0**). - [rustjava-adopt-cp-tag-passthrough-detectable-p0-fix] ★★**신원 4축을 «각각» 관측 가능하게 했다 — 감사의 「고칠 것이 없다」를 정정한다.** 게이트② **request-changes** 승계(PR #55 · 핀 `ab13a3c7`). ★**제품 코드 무접촉** — 없던 것은 **픽스처**다. ★★**무엇이 틀렸나**: 직전 감사의 **M7**(「신원 4축 검사 제거」)은 네 비교를 ★**한꺼번에** 지운다 ⇒ 그 red 가 증명하는 것은