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
Original file line number Diff line number Diff line change
@@ -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"
]
}
Original file line number Diff line number Diff line change
@@ -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 수락은 «적법»의 증거이지 «어떤 생성기가 그 인코딩을 낸다»의 증거가 아니다.
11 changes: 10 additions & 1 deletion test-data/class-file-versions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 23 additions & 4 deletions test-data/src/verify-javac-fixtures.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 ]
Loading