diff --git a/REPORT.md b/REPORT.md index 7a63bc1c..90a75e96 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,4 +1,15 @@ # REPORT +## [2026-09-18] 「어느 bootstrap argument 가 왜 나빴나」 — ★**대전제 ⓒ 에서 끝난다: 그 일을 하는 축이 이미 떠 있다**(rustjava-adopt-loadable-bootstrap-arguments-diagnostic) +- 무엇을: 채택 제안 `2026-09-17-loadable-bootstrap-arguments#p0` 의 처분(worklog json `adoptedProposals` 기록). ★**코드 0행** — `classfile/src/{error,validation}.rs` **무접촉**. +- ★**제안의 전제는 참이다 — CLI 로 돌려서 봤다**(`main` @ `8c7b473f`): 서로 다른 세 규칙(`LdcDynamicBSMArgPastEnd` 나쁜 argument · `LdcDynamicDuplicateBSM` 중복 속성 · `LdcDynamicOldMajor` 버전 게이트)이 ★**글자 하나 다르지 않은 `java.lang.ClassFormatError: Invalid class file`** 를 낸다. +- ★★**겹침을 «두 축»으로 갈라야 한다**(초판은 「전부」로 뭉쳤고 ★그 낱말이 카드의 과소 산정을 낳았다): ★**편집 영역은 전부 겹친다** — **PR #67** 이 제안의 `target` **바로 그 두 파일**을 고치고(merge-base 대비 `error.rs` **10/1** · `validation.rs` **61/27**), ★**이 술어에 이미 사유를 준다**(`"a bootstrap method argument names nothing or is not a loadable constant"`), ★밋밋한 문면이 박힌 **두 경계 자리**(`src/runtime.rs:189`·`test-utils/src/lib.rs:334`)도 **둘 다** 고쳤다. ⇒ main 에서 시작하면 **같은 enum 을 세 번째로 고치고** 같은 커버리지 구멍을 다시 발견한다. + ★**제안 자신이 그렇게 적어 두었다** — `tradeoff`: 「the two should be done together rather than twice」. +- ★**남는 것은 있다 · 다만 좁다**: `#67` 의 payload 는 **`&'static str`** 이라 ★**런타임 인덱스를 구조적으로 못 담는다** ⇒ 「기대」는 **달성**, 「인덱스」·「실제」는 **미달**. ⇒ ★**새 카드를 좁혀 냈다**(effort **S**) — 원 제안은 처분하고 잔여만 정확한 범위로 다시 세운다(안 그러면 카드와 함께 잔여도 사라진다). +- ★**제안이 적지 않은 설계 제약**: `ClassFileError` 는 **`Copy`** 이고 ★**파일 4 · 크레이트 «2»**(`classfile` 3 + `jvm-bytecode` 1)가 그것을 쓴다 — ★초판의 「네 크레이트」는 **명사가 틀렸다**(수는 맞다 · 루트 2건은 **주석**이다)(`tests/test.rs` 13 · `validation.rs` 8 · `class.rs` 7 · `jvm-bytecode/src/error.rs` 5). ★**깨지 않고도 된다** — 정적 사유 + `u16` 인덱스 + `u8` 태그면 셋 다 `Copy`. 후속이 다시 발견하지 않도록 새 카드에 적었다. +- ★**#67 위에 쌓지 않은 이유**(선택이지 누락 아님): ⑴아직 approve 아님(게이트② 재검 중) ⑵head 가 회차마다 움직임 ⑶**자식 PR** 이 되어 base 소멸 시 자동으로 닫힌다(게이트③ 계약 5). +- ★**잃는 것**: ★**main 은 #67 착지까지 밋밋한 채로 남는다**(오늘 사용자는 **규칙 이름조차** 못 받는다) · 이 회차는 제안의 값을 **전혀 전달하지 않았고** 전달한 것은 **순서**다 · #67 이 폐기되면 이 판단은 **한 회차를 버린 것**이 된다. +- 검증: `cargo test --all` **583 passed / 0 failed / 1 ignored**(불변 — 코드 무접촉 · base `8c7b473f`) · `check-dod-ci-parity` → **「OK 두 축 모두 대칭차 0 — 명령 6개 · toolchain 2개로 «둘 다 일치»」**. +- ★후속 추천: 새 카드 「**구조화된 variant 로** bootstrap argument 의 인덱스와 태그를 말한다」(★**M** · ★초판은 `S` 였다 — **실측 후 올렸다**: 잔여도 #67 과 **같은 3층**을 건넌다(중간층 `jvm-bytecode` 도 `&'static str` · 경계는 `&str`) ⇒ `target` **5파일 / 4크레이트**(`classfile`·`jvm-bytecode`·`RustJava`·`test-utils` — ★**층은 3인데 크레이트는 4다**: 경계 층 하나가 두 크레이트에 걸친다). ★`InvalidFormat` 을 넓히면 생성 **17**곳 + 값 매치 **11**곳이라 **새 variant** 를 고르되 ★**두 갈래가 생기는 대가**를 카드에 적었다) — ★**#67 «뒤»에** · 상세 = `docs/worklog/2026-09-18-bootstrap-argument-diagnostic-sequencing.md`. ## [2026-09-18] 루트 fixture 를 한 target 으로 모을 것인가 — ★**모으지 않는다**(rustjava-adopt-test-data-version-freeze-uniform-target-p0) - 무엇을: 채택 제안 `2026-09-17-test-data-version-freeze#p0` 의 **결정**(worklog json `adoptedProposals` 기록). ★**코드 0행 · 재컴파일 0 · `.class` 바이트 0 변경** — 산출물은 `docs/test-data-target-policy.md` 와 그 근거다. - ⑴**분포**(동결 파일이 아니라 fixture 자신에서 읽었다): 루트 **114**건 · major **52×40 · 65×62 · 66×8 · 68×1 · 70×3** — 동결 파일 머리주석과 일치. diff --git a/STATE.md b/STATE.md index 446a2e72..17507717 100644 --- a/STATE.md +++ b/STATE.md @@ -7,6 +7,12 @@ (둘 다 이것보다 오래됐고 MERGEABLE/CONFLICTING 처분이 이미 걸려 있다). 겹침은 전부 **append 형 합집합**이라 해소는 기계적이다) ## 완료 +- [rustjava-adopt-loadable-bootstrap-arguments-diagnostic] ★★**대전제 ⓒ 에서 끝났다 — 그 일을 하는 축(PR #67)이 이미 떠 있다.** 채택 제안 `2026-09-17-loadable-bootstrap-arguments#p0`(worklog json 기록). ★**코드 0행.** + ★**전제는 참**(CLI 실행: 세 규칙이 전부 `ClassFormatError: Invalid class file` 동일 문면) — 그러나 ★**편집 영역은 전부 겹친다**(전달된 값은 **3 중 1** — 아래): #67 이 제안 `target` 두 파일을 고치고, 이 술어에 **이미 사유를 주며**, 밋밋한 문면 **두 자리**를 둘 다 고쳤다. ★제안 `tradeoff` 자신이 「두 번 하지 말고 함께 하라」고 적었다. + ★**남는 잔여는 좁다** — `&'static str` 이라 **인덱스를 못 담는다** ⇒ 「기대」 달성 · 「인덱스·실제」 미달 ⇒ ★**새 카드를 냈다**(★**M** — 초판 `S` 에서 **실측 후 올렸다**: 잔여도 **같은 3층**을 건너 `target` **5파일/4크레이트**(`classfile`·`jvm-bytecode`·`RustJava`·`test-utils` — ★층 3 ↔ 크레이트 4: 경계 층이 두 크레이트에 걸친다) · `InvalidFormat` 을 넓히면 생성 17 + 매치 11 이라 **새 variant** 를 고르고 **두 갈래의 대가**를 적었다). + ★**설계 제약 기록** — `ClassFileError` 는 `Copy`(파일 4 · 크레이트 2 의존) · 정적사유+`u16`+`u8` 로 **깨지 않고 된다**. + ★**잃는 것**: main 은 #67 착지까지 **밋밋한 채**(규칙 이름조차 없다) · 이 회차가 전달한 것은 **값이 아니라 순서**다. + ★`--all` **583/0/1**(불변 · base `8c7b473f`). - [rustjava-adopt-test-data-version-freeze-uniform-target-p0] ★★**루트 fixture 를 한 target 으로 모을 것인가 — «모으지 않는다».** 채택 제안 `2026-09-17-test-data-version-freeze#p0`(worklog json 기록). ★**코드 0행 · 재컴파일 0 · `.class` 0 변경** — 산출물 = `docs/test-data-target-policy.md`. ★분포 **114**건 · **52×40 · 65×62 · 66×8 · 68×1 · 70×3**(fixture 자신에서 읽음). ★★**양방향**: ⒜**버전이 답** — `StringBuilder` 3건을 21 로 재컴파일 → ★indy 생기고 StringBuilder 사라짐(★`StringConcat` 이 루트의 **유일한** indy) · `ThreadInterruption` `access$`×10 → **0 + NestMembers**(JEP 181) ⒝**아무 버전이나** — 20건 중 ★**16건 명령 시퀀스 완전 동일**. diff --git a/docs/worklog/2026-09-18-bootstrap-argument-diagnostic-sequencing.json b/docs/worklog/2026-09-18-bootstrap-argument-diagnostic-sequencing.json new file mode 100644 index 00000000..cd6fa96e --- /dev/null +++ b/docs/worklog/2026-09-18-bootstrap-argument-diagnostic-sequencing.json @@ -0,0 +1,35 @@ +{ + "date": "2026-09-18", + "taskId": "rustjava-adopt-loadable-bootstrap-arguments-diagnostic", + "summary": "Stopped at the ticket's own precondition: an axis already doing most of this is in flight. PR #67 rewrites exactly the two files this proposal names, already gives this very predicate a cause string, and already replaces the flat 'Invalid class file' at both boundary sites. Doing it now on main would be a third competing edit to the same enum and would rediscover the same duplicated boundary. The proposal's residue after #67 is real but narrower than the proposal describes, and it is recorded as a new, correctly scoped card.", + "changes": [ + "no code: classfile/src/error.rs and classfile/src/validation.rs untouched", + "docs/worklog only — the finding, the measurements, and a narrowed successor proposal" + ], + "verification": [ + "PREMISE CONFIRMED on main @ 8c7b473f, measured by running the CLI rather than by reading: three different rules all print the identical message. `rust_java -cp ./test-data/ldc LdcDynamicBSMArgPastEnd` (bad bootstrap argument), `LdcDynamicDuplicateBSM` (duplicate attribute) and `LdcDynamicOldMajor` (version gate) each answer `java.lang.ClassFormatError: Invalid class file`", + "OVERLAP, split into the two axes it was conflating. By EDIT AREA it is total: this proposal's target is classfile/src/error.rs and classfile/src/validation.rs, and PR #67 changes exactly those two (error.rs 10/1, validation.rs 61/27 against their merge base), already gives this predicate the cause 'a bootstrap method argument names nothing or is not a loadable constant', and already rewrites both boundary sites that hardcode 'Invalid class file'. By DELIVERED CAPABILITY — which is what precondition (c) actually asks — it is 1 of 3: the proposal wants expected, index and actual, and #67's &'static str payload supplies the expectation only. An earlier draft wrote 'OVERLAP IS TOTAL' unqualified, which contradicted the residue entry three lines down and is the same word that produced the under-sized effort on the card.", + "CONTRACT 2 SWEEP: the literal 'Invalid class file' is hardcoded at two boundary sites, src/runtime.rs:189 and test-utils/src/lib.rs:334. #67 rewrites both; a round starting from main would have to change both again and would hit the same test-coverage gap #67's round already reported", + "RESIDUE IS REAL BUT NARROWER: #67's payload is &'static str, which structurally cannot carry a runtime index. So after #67 lands the proposal's 'expected' is delivered by the rule wording and its 'index' and 'actual' are not", + "DESIGN CONSTRAINT the proposal does not name: ClassFileError derives Copy. Carrying an owned String would drop Copy for every consumer. Counted at 8c7b473f with git grep -c: classfile/tests/test.rs 13, classfile/src/validation.rs 8, classfile/src/class.rs 7, jvm-bytecode/src/error.rs 5 — that is 4 FILES across 2 CRATES (classfile three, jvm-bytecode one), not four crates as an earlier draft said; the two further hits under tests/ are comments, not code. A Copy-preserving shape exists — the static cause plus a u16 index plus the u8 tag found — so the successor does not need to break it.", + "cargo test --all: 583 passed / 0 failed / 1 ignored on base 8c7b473f, unchanged — this round adds no code" + ], + "issues": [ + "Not stacking on #67's branch was a choice, not an oversight: #67 is not approved (its gate2 re-review is in flight), its head moves each round, and a PR based on it would be a child PR whose base can vanish on merge — the hazard gate3 contract 5 exists for.", + "This leaves the flat message in place on main until #67 lands. That is the cost of not duplicating: a user bisecting a rejected class file today still gets no cause at all, not even the rule." + ], + "adoptedProposals": [ + "2026-09-17-loadable-bootstrap-arguments#p0" + ], + "proposals": [ + { + "title": "Add a structured variant that names the failing bootstrap argument's index and tag", + "plainSummary": "Once the rejection says which rule failed, give that one rule a richer error that also carries which argument number was wrong and what it actually pointed at.", + "userBenefit": "OpenJDK says 'argument_index 4 has bad constant type'. Naming the rule gets a reader to the right sentence; naming the index and the tag gets them to the right byte without bisecting the file.", + "why": "This is the measured residue of 2026-09-17-loadable-bootstrap-arguments#p0 after PR #67. That PR carries the cause as a &'static str, which cannot hold a runtime index, so of the proposal's three asks — expected, index, actual — it delivers the first and not the other two. The predicate already knows the index at the moment it returns false and throws it away.", + "tradeoff": "Measured rather than estimated, against #67's tree @56bb54fa. Widening InvalidFormat's payload would touch 17 construction sites (validation.rs 14, class.rs 3) and force all 11 value matches in classfile/tests/test.rs to change. Adding a separate variant instead touches one construction site and forces none of the 11, so that is the shape proposed — but it leaves TWO KINDS of rejection error side by side, and 'why is this one structured and that one a bare string' becomes a standing question every later rule has to answer. Either way the work crosses the same three layers, because the middle layer (jvm-bytecode ClassDefinitionError::InvalidClassFile) is also &'static str and the boundary (jvm exception(&str)) takes a plain string, so the text has to be formatted at src/runtime.rs and test-utils/src/lib.rs. ClassFileError also derives Copy and 4 files across 2 crates use it, so the payload must stay Copy: static cause plus u16 index plus u8 tag. And it must be sequenced after #67, not beside it — both touch the same enum and function.", + "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-18-bootstrap-argument-diagnostic-sequencing.md b/docs/worklog/2026-09-18-bootstrap-argument-diagnostic-sequencing.md new file mode 100644 index 00000000..511683bb --- /dev/null +++ b/docs/worklog/2026-09-18-bootstrap-argument-diagnostic-sequencing.md @@ -0,0 +1,93 @@ +# 2026-09-18 — 「어느 bootstrap argument 가 왜 나빴나」 — ★**이 회차는 대전제 ⓒ 에서 끝난다** (rustjava-adopt-loadable-bootstrap-arguments-diagnostic) + +채택 제안 `2026-09-17-loadable-bootstrap-arguments#p0` 의 처분. ★**코드 0행**(`classfile/src/error.rs`·`validation.rs` **무접촉**). + +## 제안의 전제는 «참»이다 — 내가 실행으로 확인했다 + +읽어서가 아니라 **CLI 로 돌려서** 봤다(`main` @ `8c7b473f`): + +``` +rust_java -cp ./test-data/ldc LdcDynamicBSMArgPastEnd → java.lang.ClassFormatError: Invalid class file +rust_java -cp ./test-data/ldc LdcDynamicDuplicateBSM → java.lang.ClassFormatError: Invalid class file +rust_java -cp ./test-data/ldc LdcDynamicOldMajor → java.lang.ClassFormatError: Invalid class file +``` + +★**서로 다른 세 규칙**(나쁜 argument · 중복 속성 · 버전 게이트)이 **글자 하나 다르지 않은 같은 문장**을 낸다. +제안이 말한 「밋밋한 `Invalid class file`」은 정확한 서술이다. + +## ★그런데 대전제 ⓒ 가 «발화한다» — 이미 그 일을 하는 축이 «떠 있다» + +**PR #67**(`rustjava-adopt-classfile-error-cause-decision-p0` 리니지)이 **바로 그것**을 한다. +★**두 축으로 갈라 읽어야 한다** — ⒜**편집 영역**은 «전부» 겹치고 ⒝**전달된 값**은 «3 중 1» 이다. +★초판은 이것을 「전부」로 뭉쳤고, ★**그 낱말이 카드를 `S` 로 과소 산정하게 만들었다**(둘은 같은 뿌리다). + +**⒜ 편집 영역 — 전부 겹친다:** + +| 축 | 제안 | PR #67 | +|---|---|---| +| `target` | `classfile/src/error.rs` · `classfile/src/validation.rs` | ★**정확히 그 둘**(merge-base 대비 `error.rs` **10/1** · `validation.rs` **61/27**) | +| 이 술어 | 「어느 argument 가 왜」를 말하게 하라 | ★**이미 사유를 준다** — `"a bootstrap method argument names nothing or is not a loadable constant"` | +| 밋밋한 문면 | 없애라 | ★**두 경계 자리**(`src/runtime.rs:189` · `test-utils/src/lib.rs:334`)를 **둘 다** 고쳤다 | + +★**제안 자신이 이것을 적어 두었다**: `tradeoff` 에 「The same follow-up is already proposed from the p2-fix round, +so the two should be done together rather than twice.」 ⇒ ★**제안이 «따로 하지 마라»고 말한 그 상황이 지금이다.** + +★**계약 2(문면을 match 하는 곳 전수 조회)도 같은 결론을 가리킨다**: `"Invalid class file"` 리터럴은 **두 곳**에 박혀 있고, +#67 이 그 둘을 이미 고쳤다. main 에서 시작하면 **같은 두 곳을 다시 고치고**, #67 회차가 이미 보고한 +「마지막 홉이 두 번 쓰여 있고 한 쪽만 테스트가 본다」는 **커버리지 구멍을 다시 발견**하게 된다. + +## ⒝ 전달된 값 — ★**3 중 1** (= 남는 것은 «있다») + +★**`#67` 의 payload 는 `&'static str` 이라 «런타임 인덱스»를 «구조적으로» 담을 수 없다.** + +| 제안이 요구한 것 | #67 착지 후 | +|---|---| +| 「기대」(무엇이어야 했나) | ★**달성** — 규칙 문면이 그것을 말한다 | +| 「인덱스」(몇 번째 argument) | ★**미달** | +| 「실제」(실제로 무엇이 있었나) | ★**미달** | + +⇒ 그래서 **새 카드**를 냈다(`proposals[0]` · effort ★**M**) — 원 제안은 `adoptedProposals` 로 처분하고, +**남은 절반만** 정확한 범위로 다시 세운다. ★**그렇게 하지 않으면 카드가 사라지면서 잔여도 함께 사라진다.** + +## ★카드를 `S` → `M` 으로 올렸다 — 두 선택지를 «재고» 골랐다 + +★**초판의 `S` 는 «편집 영역이 전부 겹친다»를 «남은 일도 작다»로 읽은 결과**였다. 재보니 반대다 — +★**잔여도 #67 과 «같은 3층»을 건넌다**: 중간층 `jvm-bytecode/src/error.rs:5` 가 **`InvalidClassFile(&'static str)`** 이고 +경계 `jvm/src/jvm.rs` 의 `exception(&str)` 이 **평범한 문자열**을 받으므로, 「argument_index 4 …」는 +`src/runtime.rs:189`·`test-utils/src/lib.rs:334` 에서 **포맷**되어야 한다. + +| 선택지 | 생성 자리 | 값 매치(`classfile/tests/test.rs`) | 층 | 판정 | +|---|---|---|---|---| +| ⒝ `InvalidFormat` 페이로드를 **넓힌다** | `validation.rs` **14** + `class.rs` **3** = **17곳** | **11곳 전건** | 3 | 편집 ≈30+ | +| ★⒜ **새 variant** | ★**1곳**(그 규칙만) | ★**0곳 강제** | 3 | 편집 ≈6 | + +⇒ ★**⒜ 를 골랐다**(편집이 1/5 이고 기존 단정 11 을 건드리지 않는다). ★**그래도 `M` 이다** — 크기를 정하는 것은 +**편집 수가 아니라 «건너는 층»** 이고, 잔여는 **5파일 / 4크레이트**(`classfile`·`jvm-bytecode`·`RustJava`·`test-utils`)를 건넌다. +★**층은 3인데 크레이트는 4다 — 둘을 같은 수로 적지 마라**: 경계 층 하나가 `RustJava`(`src/runtime.rs`)와 +`test-utils`(`test-utils/src/lib.rs`) **두 크레이트**에 걸친다(`cargo metadata --no-deps` 귀속). 이 사실은 `M` 의 근거를 **강화한다**. +★★**원 제안이 «전체»를 `M` 으로 값했는데 «미전달된 절반»을 `S` 로 값하면 순서가 뒤집힌다** — 그 역전을 되돌린 것이다. + +★**⒜ 의 대가**(카드 `tradeoff` 에도 적었다): ★**거부 오류가 «두 갈래»가 된다** — `InvalidFormat(&'static str)` 과 +구조화된 variant 가 나란히 서고, 「왜 이건 구조화고 저건 맨 문자열인가」를 **이후 규칙마다** 답해야 한다. + +## ★제안이 «적지 않은» 설계 제약 하나 — 후속이 다시 발견하지 않도록 + +`ClassFileError` 는 ★**`Copy` 를 derive 한다**. 인덱스를 담겠다고 `String` 을 넣으면 **`Copy` 가 깨지고** +그것은 그 소비자에게 전파된다 — ★**파일 4 · 크레이트 «2»**(`classfile` 3 + `jvm-bytecode` 1 · ★초판의 「네 크레이트」는 명사가 틀렸다 · 수는 맞다 · 루트 2건은 **주석**)(`classfile/tests/test.rs` **13** · `validation.rs` **8** · `class.rs` **7** · +`jvm-bytecode/src/error.rs` **5**). +★**깨지 않고도 된다**: 정적 사유 + `u16` 인덱스 + 발견된 `u8` 태그면 **셋 다 `Copy`** 다. 그 모양을 새 카드에 적었다. + +## 왜 #67 브랜치 «위에» 쌓지 않았나 — 선택이지 누락이 아니다 + +⑴#67 은 **아직 approve 가 아니다**(게이트② 재검이 진행 중) ⑵그 head 는 회차마다 **움직인다** +⑶그 위에 PR 을 내면 **자식 PR** 이 되고, ★**base 가 사라지면 자식이 자동으로 닫힌다**(게이트③ 계약 5 가 있는 이유). + +## 잃는 것 — 「없다」로 적지 않는다 + +- ★**main 은 #67 이 착지할 때까지 밋밋한 채로 남는다.** 오늘 거부된 클래스 파일을 든 사람은 + **규칙 이름조차** 못 받는다. 그것이 «중복하지 않기»의 대가이고, 숨기지 않는다. +- ★**이 회차는 제안의 값을 «전혀» 전달하지 않았다** — 전달한 것은 **순서**다. + 그 판단이 틀렸다면(예: #67 이 폐기되면) 이 회차는 **한 회차를 버린 것**이 된다. +- ★**「#67 이 착지하면 잔여가 정확히 둘」은 «오늘의 #67»** 기준이다. 그 PR 은 아직 움직이고 있다. +- ★**`S` 에서 `M` 으로 올린 대가 — 카드가 뒤로 밀린다.** 실측이 올린 값이라 되돌리지 않지만, + `S` 였다면 먼저 집혔을 자리에서 내려가고 그만큼 열린 카드가 는다. 그 손실을 여기 적는다.