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
25 changes: 25 additions & 0 deletions REPORT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,29 @@
# REPORT
## [2026-09-16] `BootstrapMethods` 를 두 번 선언한 클래스를 거부한다 (rustjava-adopt-bound-bootstrap-method-attr-index-p0)
- 무엇을: JVMS 4.7.23 은 `BootstrapMethods` 를 **최대 한 개**만 허용한다. 두 개를 실은 파일을 ★**거부**한다
(종전에는 `find_map` 이 **첫 표**를 쓰고 나머지를 조용히 무시했다).
- 왜: 채택 제안 `2026-09-16-bound-bootstrap-method-attr-index#p0`(운영자 tower 패널 채택).
- 사용자 영향: ★**진단이 바뀐다** — `UnsupportedOperationException`(「이 런타임이 아직 못 한다」) →
★`ClassFormatError`(「이 파일이 깨졌다」). ★**참조 JVM 과 같은 판정**이 된다.
- ★★**참조 JVM 이 근거다**(observable behavior · OpenJDK 소스 미참조): OpenJDK 26.0.1 은 같은 파일에
**`ClassFormatError: Multiple BootstrapMethods attributes in class file`** 를 내고, ★**표가 하나뿐인 대조군은
rc=0 으로 로드**한다 ⇒ 픽스처의 결함이 «둘이라는 사실» 하나임이 참조 구현으로 확증된다.
- ★**고친 자리는 «한 곳»**: `validate_class` 에 술어 `at_most_one_bootstrap_methods_attribute` 를 이었다.
★`bootstrap_method_indices_resolve` 는 **무접촉** — 그 함수의 doc 이 스스로 「인덱스가 실재 항목을 가리키는가」라는
**한 문장**임을 선언하고, 「표가 몇 개인가」는 **다른 문장**이다(접어 넣으면 이름까지 바꿔야 한다).
★**새 관용 0** — 필드 `ConstantValue`·메서드 `Code` 가 이미 쓰는 **개수 세기** 모양 그대로다.
- ★**픽스처는 «결함이 하나»가 되게 지었다**: `LdcDynamicDuplicateBSM.class` = 같은 유효한 표를 ★**바이트 동일**하게 두 번.
어느 한 표만 있어도 정상 파일이라 ★**거부 원인이 «둘»로 고정**된다(둘째 표를 다르게 하면 다른 규칙이 먼저 물어
테스트가 «이름과 다른 이유»로 통과한다). 구조 실측: 속성 `['BootstrapMethods','BootstrapMethods']` · 본문 동일 `True`.
- ★★**제안의 한 문장은 «과했다»**: 「생성기가 만들 수 없는 픽스처가 필요하다」 — ★**8줄 래퍼로 됐다**
(속성 목록이 빌더에 그대로 전달된다). **관측은 맞았고 비용 추정이 틀렸다.**
- 검증: 개악 **양방향** — ⑴호출부에서 술어 제거(=제안 이전 상태) **red** ⑵술어 본문을 **`true`(상수 통과)** 로 **red**
(★⑵가 없으면 「검사가 상수로 뭉개진」 축을 못 잡는다) · 정상 **green** ·
`cargo test --all` **571 passed / 0 failed / 1 ignored**(27 스위트 **전건 합산**) · 픽스처 재생성 **멱등**(기존 11 바이트 동일) · DoD 7명령 rc=0.
- 후속 추천: 클래스 수준의 **다른 「최대 1개」 속성**(SourceFile·EnclosingMethod·Signature…)도 같은 규칙을 받아야 하는지 **판정**
— ★이번 건이 검사를 얻은 이유는 「중복이 하류에 임의 선택을 만든다」이고, 파서가 무시하는 속성엔 그 논거가 **전이되지 않는다**.
상세 = `docs/worklog/2026-09-16-reject-duplicate-bootstrap-methods.md`.

## [2026-09-16] `ldc` 태그 15/16/17 — ★**ASM 은 «낸다»**(Kotlin·Scala·Lombok 산출물은 0) (rustjava-ldc-tags-15-16-17-real-world-generator-survey)
- 무엇을: 선행 회차가 남긴 **「못 쟀다」**(ASM·Kotlin·Scala·Lombok)를 **쟀다**. 조사 회차 — ★**크레이트 무접촉**(파서·테스트 0).
- 왜: 채택 제안 `2026-09-16-ldc-tags-15-16-17#p2`. javac 은 안 낸다가 이미 증명됐고, **직접 바이트코드를 짜는 도구**가 남아 있었다.
Expand Down
29 changes: 29 additions & 0 deletions STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@
(없음 — 2026-09-16 실측: 착수 시 진행 티켓 0 · 열린 PR 0. ※「열린 PR 0」은 ★**이 회차 PR 착지 시점 기준**이다 — 회신 시점에는 그 PR 자신이 열려 있다)

## 완료
- [rustjava-adopt-bound-bootstrap-method-attr-index-p0] ★★**`BootstrapMethods` 를 «두 번» 선언한 클래스를 거부한다 — 임의 선택을 없앴다.**
채택 제안 `2026-09-16-bound-bootstrap-method-attr-index#p0`(운영자 tower 패널 채택 · worklog json `adoptedProposals` 기록).
★**JVMS 4.7.23 = 최대 한 개.** 종전에는 `find_map` 이 **첫 표**를 쓰고 나머지를 **조용히 무시**했다 ⇒
★**`bootstrap_method_attr_index` 가 «어느 표»에 대해 경계 검사되는지가 임의**였고 그 사실이 아무 데도 드러나지 않았다.
★**전/후**: `UnsupportedOperationException`(「아직 못 한다」) → ★`ClassFormatError`(「이 파일이 깨졌다」).
★★**참조 JVM 이 근거다**(observable behavior · OpenJDK 소스 미참조): OpenJDK 26.0.1 →
**`ClassFormatError: Multiple BootstrapMethods attributes in class file`** · ★**표 하나짜리 대조군은 rc=0 로드**.
★**고친 자리 «한 곳»** — `validate_class` 에 술어 `at_most_one_bootstrap_methods_attribute` 를 이었다.
★`bootstrap_method_indices_resolve` **무접촉**(그 doc 이 스스로 「인덱스가 실재 항목을 가리키는가」라는 한 문장임을
선언한다 — 「표가 몇 개인가」는 다른 문장이고, 접어 넣으면 **이름까지 바꿔야** 한다) · ★**새 관용 0**
(필드 `ConstantValue`·메서드 `Code` 가 이미 쓰는 **개수 세기** 모양 그대로).
★★**픽스처를 «결함이 하나»가 되게 지었다** — `LdcDynamicDuplicateBSM.class` = **같은 유효한 표를 바이트 동일하게 두 번**.
어느 한 표만 있어도 정상 파일이라 ★거부 원인이 «둘이라는 사실»로 **고정**된다(둘째를 다르게 하면 다른 규칙이 먼저 물어
테스트가 «이름과 다른 이유»로 통과한다 — 이 저장소가 #49 에서 세운 그 규율).
구조를 **측정**했다: 속성 `['BootstrapMethods','BootstrapMethods']` · 두 본문 **바이트 동일 True**.
★★**제안의 한 문장은 «과했다»** — 「생성기가 만들 수 없는 픽스처가 필요하다」는 **거짓**이고 ★**8줄 래퍼**로 됐다
(속성 목록이 빌더에 그대로 전달된다). ⇒ **관측은 맞았고 «비용 추정»이 틀렸다** — 다음 사람이 같은 이유로 미루지 않게 적는다.
★**개악 대조 양방향**: ⑴호출부에서 술어 제거(= 제안 이전 상태) **red** ⑵술어 본문을 **`true`(상수 통과)** 로 **red** ·
정상 **green**. ★**⑵가 없으면 「검사가 상수로 뭉개진」 축을 못 잡는다**(⑴만으로는 호출 삭제만 잡힌다).
★`cargo test --all` **571 / 0 failed / 1 ignored**(27 스위트 **전건 합산** — 꼬리만 세지 않았다) ·
픽스처 재생성 **멱등**(기존 11 전건 바이트 동일 · 신규 1) · DoD **7줄 전건 rc=0**.
★**잃는 것**: 지금까지 «로드되던» 파일 하나가 거부된다 — 다만 그 형상은 어제 이 저장소가 잰 대로
**javac·kotlinc·scalac·Lombok 산출물 5,479 클래스에 0**이고 ASM 으로도 «일부러» 만들어야 나온다.
★★**게이트③ 착지 — PR #53 · `--merge`**(등재 repo `contracts/upstream-sync-repos.conf:22` — 스쿼시는 부모 2개를 접어 계보를 지운다).
게이트② **1회차 approve**(반려 0) · 핀 `f2c83174` **불이동**(착수 실측 17:39:54Z · ★워밍 후 재조회 `MERGEABLE/CLEAN`).
★**충돌 0 · base 당김 0**(`merge-tree` rc=0) — 이 브랜치가 `origin/main` 위에서 갈렸고 그 뒤 착지한 형제가 없다.
★**여파**: 이 착지가 형제 **#54**(BSM 정적 인자)·**#55**(변이 감사)의 base 를 낡게 만든다.
★**#54 는 «코드 파일이 자동 병합»되도록 그 회차가 삽입 위치를 미리 갈라 뒀고**(그 done 회신의 `merge-tree` 실측),
**#55 는 원장 3파일만 만진다** ⇒ 두 형제 모두 충돌은 **원장 계열에 국한**된다(게이트③ 계약 2-c⒜ 범위).
- [rustjava-ldc-tags-15-16-17-real-world-generator-survey] ★★**「못 쟀다」를 «쟀다»로 바꿨다 — ASM 은 태그 15/16/17 을 «낸다».**
채택 제안 `2026-09-16-ldc-tags-15-16-17#p2`(worklog json `adoptedProposals` 기록). ★**조사 회차 · 크레이트 무접촉**(파서·테스트 0).
★★**ASM 9.7.1 = 낸다(실증)** — `visitLdcInsn(Handle)`·`(Type.getMethodType)`·`(ConstantDynamic)` 15줄로 만든 클래스에서
Expand Down
22 changes: 22 additions & 0 deletions classfile/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub(crate) fn validate_class(class: &ClassInfo) -> Result<(), ClassFileError> {
|| !validate_constant_pool(&class.constant_pool)
|| !constant_pool_tags_fit_the_class_file_version(class)
|| !bootstrap_method_indices_resolve(class)
|| !at_most_one_bootstrap_methods_attribute(class)
{
return Err(ClassFileError::InvalidFormat);
}
Expand Down Expand Up @@ -134,6 +135,27 @@ fn bootstrap_method_indices_resolve(class: &ClassInfo) -> bool {
})
}

/// JVMS 4.7.23: at most one `BootstrapMethods` attribute may appear in a ClassFile's attributes
/// table. A file carrying two is broken, not a file using a feature this runtime lacks.
///
/// Kept separate from `bootstrap_method_indices_resolve` because it is a different sentence: that
/// one asks whether an index names a real entry, this one asks how many tables exist. Folding it in
/// would also mean renaming that function for a rule it did not previously make.
///
/// It matters because `bootstrap_method_indices_resolve` resolves the table with `find_map`, which
/// stops at the first one. With two tables that choice is arbitrary — the index would be bounded
/// against whichever came first and the other silently ignored — so the honest answer is to reject
/// the file rather than pick. Counting is the same shape `validate_class` already uses for the
/// per-member "at most one" rules (`ConstantValue` on a field, `Code` on a method).
fn at_most_one_bootstrap_methods_attribute(class: &ClassInfo) -> bool {
class
.attributes
.iter()
.filter(|attribute| matches!(attribute, AttributeInfo::BootstrapMethods(_)))
.count()
<= 1
}

fn validate_constant_pool(constant_pool: &BTreeMap<u16, ConstantPoolItem>) -> bool {
constant_pool.values().all(|item| match item {
ConstantPoolItem::Class { name_index } => constant_pool
Expand Down
41 changes: 41 additions & 0 deletions docs/worklog/2026-09-16-reject-duplicate-bootstrap-methods.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"schema": "worklog/v1",
"date": "2026-09-16",
"taskId": "rustjava-adopt-bound-bootstrap-method-attr-index-p0",
"summary": "A class declaring BootstrapMethods twice is now rejected as malformed instead of being read with whichever table came first. JVMS 4.7.23 allows at most one; OpenJDK 26 answers ClassFormatError: Multiple BootstrapMethods attributes for the same file.",
"changes": [
"classfile/src/validation.rs: new at_most_one_bootstrap_methods_attribute predicate, wired into validate_class's rejection chain. bootstrap_method_indices_resolve is untouched — counting tables is a different sentence from bounding an index, and folding it in would have meant renaming that function",
"test-data/src/ldc/make_ldc_fixtures.py: new duplicate_bootstrap_methods(inner) wrapper that re-appends the attribute its inner builder wrote",
"test-data/ldc/LdcDynamicDuplicateBSM.class: new fixture, the same valid table byte-for-byte twice",
"tests/test_class_format.rs: test_a_class_declaring_bootstrap_methods_twice_is_malformed"
],
"verification": [
"proposal re-measured at start: the find_map it names is still at classfile/src/validation.rs:117, so the proposal is still live",
"reference JVM, OpenJDK 26.0.1: the fixture gives 'java.lang.ClassFormatError: Multiple BootstrapMethods attributes in class file LdcDynamicDuplicateBSM', while the single-table control LdcDynamic loads with rc=0 and no output",
"our runtime before the fix: UnsupportedOperationException 'ldc of a dynamically-computed constant'; after: ClassFormatError 'Invalid class file'; the single-table control's answer is unchanged, which shows the change is narrow",
"fixture structure measured, not assumed: class attributes are ['BootstrapMethods', 'BootstrapMethods'] and the two bodies are byte-identical",
"fixture regeneration is idempotent: 11 existing .class files byte-identical, 1 added",
"mutation M1, removing the call from validate_class (i.e. the pre-proposal state): the new test goes red",
"mutation M2, replacing the predicate body with `true` (constant pass): red at the same assertion — M1 alone would not catch a check flattened to a constant",
"restore: test_class_format 12 passed / 0 failed",
"full suite: 571 passed / 0 failed / 1 ignored, counted by summing every 'test result' line rather than reading the tail",
"DoD 7 commands all rc=0"
],
"issues": [
"The proposal's tradeoff said the fixture was one 'the generator cannot currently build'. That was too strong: the attribute list is handed to each builder, so an 8-line wrapper that re-appends what the inner builder wrote is enough. The observation was right, the cost estimate was not."
],
"adoptedProposals": [
"2026-09-16-bound-bootstrap-method-attr-index#p0"
],
"proposals": [
{
"title": "Decide whether the other class-level attributes also need cardinality rules",
"plainSummary": "We now reject a class that declares two bootstrap tables. Several other class attributes are also allowed only once each, and nothing checks those.",
"userBenefit": "The same honesty applied consistently: a file carrying two of something the spec allows one of is reported as broken, whichever attribute it is.",
"why": "JVMS 4.7 marks several ClassFile attributes as at-most-one (SourceFile, EnclosingMethod, Signature, SourceDebugExtension, and the module-related ones). This round added the rule for BootstrapMethods because a proposal named it and because the resolver silently picks the first of two. Whether the others deserve the same treatment is a separate question, and the answer may be no for the ones nothing reads.",
"tradeoff": "Rejecting more files that load today, for attributes whose duplication changes nothing we act on — the BootstrapMethods case earned its check because a duplicate makes an arbitrary choice observable downstream, and that argument does not transfer to an attribute the parser ignores. Doing it uniformly would also need a fixture per attribute.",
"effort": "S",
"target": "classfile/src/validation.rs"
}
]
}
Loading