Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 이라 못박았다** — 타입만 고치면 **관측되는 것이 없고**, `||` 사슬을 안 쪼개면 **평평함이 사라지는 게 아니라 옮겨갈 뿐**이다. 넷 다 했다:
Expand Down
7 changes: 7 additions & 0 deletions STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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」인데 **적재 가능성까지** 본다) ⇒ 사유는 규칙대로, ★**이름은 안 바꿨다**(리팩터 금지).
Expand Down
22 changes: 22 additions & 0 deletions docs/worklog/2026-09-18-five-fixtures-were-built-with-g.json
Original file line number Diff line number Diff line change
@@ -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"]
}
68 changes: 68 additions & 0 deletions docs/worklog/2026-09-18-five-fixtures-were-built-with-g.md
Original file line number Diff line number Diff line change
@@ -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` 없이).
★제안이 「이걸 정하지 않으면 조사가 완결되지 않는다」고 적은 그 축이고, **여기서 넓히지 않았다.**
24 changes: 20 additions & 4 deletions test-data/src/verify-javac-fixtures.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Loading