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
57 changes: 57 additions & 0 deletions docs/lambda-class-reflection-visibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# 람다 클래스는 리플렉션에 보여야 하는가 — 결정: **현 상태 유지(보인다)**

2026-09-26 · 티켓 `rustjava-2026-09-17-link-lambdametafactory-reflection-visibility-decision` ·
제안 `2026-09-17-link-lambdametafactory#p1` 의 처분.

## 지금 어떻게 동작하나 (코드에서 읽은 것)

- `jvm-bytecode/src/lambda.rs` `lower` 가 호출 지점마다 `<Host>$$Lambda$<n>` 이름을 붙이고,
`instantiate` 가 **처음 실행될 때** `jvm.register_class` 로 일반 클래스 레지스트리에 넣는다.
- `getClass().getName()` → `Host$$Lambda$0`.
- `Class.forName` (`rustjava-runtime/src/classes/java/lang/class.rs` `for_name`) → `jvm.resolve_class` →
레지스트리를 **먼저** 본다(`jvm/src/jvm.rs` `resolve_class_internal`). 그래서
- 그 호출 지점이 한 번이라도 실행된 **뒤**에는 그 이름으로 클래스를 찾는다.
- 실행되기 **전**에는 로더에 그런 파일이 없으므로 `ClassNotFoundException`.

## 실제 JVM 은 (공개 규격)

- `LambdaMetafactory` Javadoc 은 생성 클래스의 이름·정체를 규정하지 않는다.
- JDK 15+ 는 hidden class(JEP 371)로 만든다 — `Class.forName` 으로 찾을 수 **없고**, `getName()` 에 `/` 가 섞인다
(그 이전 판도 익명 클래스라 이름으로 찾을 수 없었다).
- 즉 차이는 둘이다: ⑴이름 모양 ⑵실행 후 `forName` 이 찾는다. ⑴은 규격상 미정이라 차이가 아니다. 남는 것은 ⑵ 하나.

## 이 런타임의 게스트가 그 차이에 닿는가

- **wie(J2ME 타이틀): 닿지 않는다 — 구조적으로.** `invokedynamic`·`MethodHandle`·`MethodType` 상수는 class 파일
major 51 이상에서만 합법이고, `classfile/src/validation.rs` `constant_pool_tags_fit_the_class_file_version` 이
그 미만을 로드 단계에서 거부한다. J2ME/CLDC 클래스는 49 이하다.
실측(wie `origin/main` `5434ab0a` 의 jar 46개 · class 12,954개): 게스트 jar(`test_data/*_j2me.jar`)는 major **47** ·
`LambdaMetafactory` 참조 **0**. 참조 365건은 전부 `game_lab/vendor_sdk/…/KEmulator*`·SWT·LWJGL — 데스크톱 JDK 에서
도는 **벤더 에뮬레이터 도구**이고 이 런타임이 실행하지 않는다.
- **`rust_java` CLI 로 최신 클래스를 돌리는 경우: 닿을 수는 있다.** 그러나 차이 ⑵를 관측하려면 프로그램이
`getClass().getName()` 결과를 다시 `Class.forName` 에 넣어야 한다 — 실제 JVM 에서는 늘 실패하는 코드라
그렇게 쓰는 프로그램은 없다고 보아도 된다. 테스트·픽스처 중 람다에 `getClass`/`forName` 을 쓰는 것은 **0**
(`test-data/src/indy/*`).

## 선택지와 대가

| | 무엇을 | 대가 |
|---|---|---|
| **A. 유지(채택)** | 지금 그대로 | 호출 지점당 레지스트리 1칸(JVM 수명 동안). 실행 후 `forName` 이 찾는다는 차이 1개 |
| B. `forName` 에서만 가린다 | `for_name` 에서 `$$Lambda$` 이름을 거부 | 3줄. 그러나 이름 모양으로 판정하므로 그 이름을 가진 **정상 게스트 클래스**도 못 찾게 된다(`$` 는 합법 문자) |
| C. hidden class 로 등록 | 레지스트리 밖 두 번째 등록 경로 | 새 등록 경로 하나 — 아무도 필요로 하지 않은 성질을 위한 기구 |

## 결정

**A.** 차이에 닿는 게스트 경로가 wie 에는 없고(버전 게이트), CLI 에서도 실제 JVM 에서 실패하는 코드만 닿는다.
B·C 는 아무도 필요로 하지 않은 성질을 위해 비용이나 새 오답을 산다. 제품 코드 변경 0.

**알고 두는 것**: 게스트가 `Host$$Lambda$0` 이라는 이름의 클래스를 **직접 싣고** 그것이 먼저 로드되면,
`instantiate` 의 `has_class` 가 참이 되어 람다 대신 그 클래스를 인스턴스화한다. 누가 일부러 그렇게 짓지 않는 한
생기지 않는다.

## 다시 열 조건

- `rust_java` 또는 다른 임베더가 major ≥ 51 게스트를 돌리는데, 람다 클래스 이름·`forName` 결과에 **의존하는** 프로그램이 실제로 관측될 때.
- 또는 위 「알고 두는 것」의 이름 충돌이 실제로 관측될 때.
「오래됐다」는 사유가 아니다.
2 changes: 1 addition & 1 deletion docs/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ for f in sorted(glob.glob('docs/worklog/*.json')):
print(*[r for r in refs if r not in done],sep='\n')"
```

1. **선행 사슬**: `2026-09-17-link-lambdametafactory#p1`(결정) → `#p0`(어댑터) → `java.lang.invoke` 패키지(카드 없음 · 근거 = `rustjava-runtime/src/classes/java/lang/invoke` **부재**) → `2026-09-17-string-concat-recipe-arity#p1`.
1. **선행 사슬**: `2026-09-17-link-lambdametafactory#p0`(어댑터 · ★지금 착수 가능 — `#p1` 은 2026-09-26 「현 상태 유지」로 닫혔고 등록 방식이 안 바뀌므로 선행 조건 없음 · `docs/lambda-class-reflection-visibility.md`) → `java.lang.invoke` 패키지(카드 없음 · 근거 = `rustjava-runtime/src/classes/java/lang/invoke` **부재**) → `2026-09-17-string-concat-recipe-arity#p1`.
2. **카드 밖**: 없음. PR #81 은 2026-09-26 에 닫혔다 — #83(`750d30d4`)과 같은 변경이었다(`docs/worklog/2026-09-26-pr81-closed-duplicate-of-pr83.md`).

지난 판(⓪-사료·①~⑤)은 동결된 `STATE.md` `## 다음` 아래에 그대로 있다.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"date": "2026-09-26",
"taskId": "rustjava-2026-09-17-link-lambdametafactory-reflection-visibility-decision",
"proposals": [],
"adoptedProposals": ["2026-09-17-link-lambdametafactory#p1"]
}
4 changes: 4 additions & 0 deletions docs/worklog/2026-09-26-lambda-class-reflection-visibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## [2026-09-26] 람다 클래스의 리플렉션 노출 — 현 상태 유지로 결정 (rustjava-2026-09-17-link-lambdametafactory-reflection-visibility-decision)
- 무엇을: 제안 `2026-09-17-link-lambdametafactory#p1` 을 결정했다 — 람다 클래스는 지금처럼 일반 레지스트리에 등록되고 `getName`·(실행 후) `forName` 에 보인다. 선택지 3개와 대가·재개 조건은 `docs/lambda-class-reflection-visibility.md`. 제품 코드 변경 0.
- 왜: 차이에 닿는 wie 게스트 경로가 없다. `invokedynamic` 은 major ≥ 51 에서만 합법이고 `validation.rs` 가 그 미만을 거부하는데, wie 게스트 jar 는 major 47 · `LambdaMetafactory` 참조 0(참조 365건은 전부 데스크톱용 벤더 에뮬레이터 도구 jar).
- 사용자 영향: 없음. 사슬 다음 칸 `#p0`(어댑터)이 선행 조건 없이 열렸다(`docs/next.md`). 후속 제안 없음.
Loading