diff --git a/docs/worklog/2026-10-05-ldc-real-jvm-check-and-new-fixture-target.json b/docs/worklog/2026-10-05-ldc-real-jvm-check-and-new-fixture-target.json new file mode 100644 index 00000000..8ec29bcf --- /dev/null +++ b/docs/worklog/2026-10-05-ldc-real-jvm-check-and-new-fixture-target.json @@ -0,0 +1,9 @@ +{ + "date": "2026-10-05", + "taskId": "rustjava-ldc-fixtures-real-jvm-check-and-new-fixture-target-rule", + "proposals": [], + "adoptedProposals": [ + "2026-09-17-ldc-asm-regeneration-declined#p0", + "2026-09-18-root-fixture-target-decision#p0" + ] +} diff --git a/docs/worklog/2026-10-05-ldc-real-jvm-check-and-new-fixture-target.md b/docs/worklog/2026-10-05-ldc-real-jvm-check-and-new-fixture-target.md new file mode 100644 index 00000000..4c8300ef --- /dev/null +++ b/docs/worklog/2026-10-05-ldc-real-jvm-check-and-new-fixture-target.md @@ -0,0 +1,13 @@ +## [2026-10-05] ldc 양성 픽스처 실 JVM 검사 + 새 픽스처 target 규칙 (rustjava-ldc-fixtures-real-jvm-check-and-new-fixture-target-rule) + +- 무엇을: `test-data/src/verify-javac-fixtures.sh` 가 javac 재빌드 뒤 ldc 양성 픽스처 4개(`LdcMethodHandle`·`LdcMethodType`·`LdcDynamic`·`Ldc2WDynamic`)를 찾은 javac 옆의 `java` 로 실행한다 — 하나라도 거부되면 rc=1. `test-data/class-file-versions.txt` 머리에 새 픽스처 규칙(`javac --release 21` = 65.0 · 다른 major 는 `.java` 머리 주석에 이유)을 적었다. +- 왜: 열린 카드 2장(`2026-09-17-ldc-asm-regeneration-declined#p0` · `2026-09-18-root-fixture-target-decision#p0`)의 남은 이유가 각각 «스크립트에 ldc 줄 없음» · «새 픽스처 규칙 미정»이었다. +- 사용자 영향: 없음(제품 코드 0줄). 손조립 ldc 바이트의 «진짜 JVM 이 받는다»는 주장이 다시 칠 수 있는 명령이 됐다. + +실측(OpenJDK 26.0.2.1 · homebrew): +- 정상: `33 rebuilt: 33 reproduced, 0 differed; 0 could not be rebuilt; 0 ldc positive(s) refused` rc=0. ldc 13개 전수 실행 — 양성 4 rc=0 · 음성 대조 9 rc=1. +- 개악: `LdcDynamic.class` major 55→52 → `✗ ldc/LdcDynamic: … java.lang.ClassFormatError: Class file version does not support constant tag 17` · rc=1. 복원 후 rc=0. +- 로캘: 한국어 런처는 원인을 «main 메소드 없음» 문장 안에 섞어 낸다 → `-Duser.language=en` + `java.lang.` 줄을 보여 준다. +- 규칙 근거 계수: root 115행 중 65.0 이 62 · indy javac 픽스처 33 중 31(나머지 2 = `LambdaCapturingThis` --release 8, REF_invokeSpecial 때문 — 규칙이 요구하는 «이유 주석»의 본보기). + +한계: CI 에는 JDK 가 없어 이 검사는 여전히 수동이다(형제 검사와 같다). JVM 수락은 «적법»의 증거이지 «어떤 생성기가 그 인코딩을 낸다»의 증거가 아니다. diff --git a/test-data/class-file-versions.txt b/test-data/class-file-versions.txt index 9a8c348b..d829f4c7 100644 --- a/test-data/class-file-versions.txt +++ b/test-data/class-file-versions.txt @@ -12,7 +12,16 @@ # axis). The 64 generator fixtures in cp/indy/ldc/attr carry zero of either, so this cohort is the # only coverage. Of the other 20 top-level 52 fixtures, 16 rebuild to an identical opcode sequence, # so unifying buys nothing. Reopen if the runtime drops major <= 52, if those paths gain coverage -# elsewhere, or if a third version axis turns up. The rule for *new* fixtures is left open. +# elsewhere, or if a third version axis turns up. +# +# New fixtures (decided 2026-10-05): build with `javac --release 21`, which stamps 65.0 — the most +# common major in the root (62 of 115 rows) and in indy/'s javac fixtures (31 of 33; the other two +# are LambdaCapturingThis at --release 8, for REF_invokeSpecial). Pick a +# different --release only when the test is about a version-specific shape (like the major-52 +# lowerings above), and say why in a comment at the top of the fixture's .java: a reason written on +# a row here would not survive, because record-class-file-versions.py keeps only `#` lines. +# test-data/src/indy/LambdaCapturingThis.java is the model: the reason, then the javac line. +# Hand-assembled fixtures (cp/indy/ldc/attr generators) set their major in the generator instead. # Re-measure, don't re-argue: docs/worklog/2026-09-18-root-fixture-target-decision.md. # # What that catches: regenerating a fixture with a different JDK. `javac Switch.java` with no diff --git a/test-data/src/verify-javac-fixtures.sh b/test-data/src/verify-javac-fixtures.sh index 9a02e3b5..815b71d9 100755 --- a/test-data/src/verify-javac-fixtures.sh +++ b/test-data/src/verify-javac-fixtures.sh @@ -29,8 +29,8 @@ # 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) +# Exit: 0 all reproduced and the ldc positives ran · 1 something differed, could not be rebuilt, or an +# ldc positive was refused · 2 no javac (nothing was checked — not a pass) set -eu @@ -100,11 +100,30 @@ for class in "$DIR"/*.class; do fi done +# The ldc positives are hand-assembled by test-data/src/ldc/make_ldc_fixtures.py, so there is no +# source to rebuild them from. The realism question for them is instead "does a real JVM accept and +# run these bytes" — its verifier is the oracle, run with the `java` beside the javac found above. +# Only the positives: every other file in test-data/ldc is a negative control the JVM must reject +# (measured 2026-10-05, OpenJDK 26.0.2.1: these four rc=0, the other nine rc=1). Accepting proves +# the bytes are legal, not that any generator would emit that exact encoding. +# Decided in docs/worklog/2026-09-17-ldc-asm-regeneration-declined.md (proposal #p0). +JAVA="${JAVAC%javac}java" +ldc_failed=0 +for base in LdcMethodHandle LdcMethodType LdcDynamic Ldc2WDynamic; do + # -Duser.language=en: a localized launcher garbles the cause into a "no main method" sentence. + if "$JAVA" -Duser.language=en -cp test-data/ldc "$base" >"$work/err" 2>&1; then + echo " ✓ ldc/$base (ran under java)" + else + echo " ✗ ldc/$base: the JVM refused it: $(grep -m1 'java\.lang\.' "$work/err" || head -1 "$work/err")" >&2 + ldc_failed=$((ldc_failed + 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 ] +echo "$checked rebuilt: $((checked - differed)) reproduced, $differed differed; $unbuildable could not be rebuilt; $ldc_failed ldc positive(s) refused" +[ "$differed" -eq 0 ] && [ "$unbuildable" -eq 0 ] && [ "$ldc_failed" -eq 0 ]