diff --git a/REPORT.md b/REPORT.md index b7f0758c..325f90b6 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,5 +1,16 @@ # REPORT +## [2026-09-12] 게스트 서브클래싱은 «된다» — `java/net` 가드 2곳 잠금 (rustjava-fixture-subclassing-abstract-runtime-classes) +- 무엇을: 「게스트 픽스처가 **추상 런타임 클래스**를 상속할 수 있는가」를 **먼저 쟀고**(미확인이었다), ★**된다**로 판정해 + `URLStreamHandler.setURL` · `JarURLConnection(URL)` 두 가드를 픽스처 `test-data/NullNetGuards` 로 **잠갔다**. ★**`.rs` 변경 0줄.** +- 왜: 그 2곳은 가드가 있어도 **지워도 아무도 모르는** 상태였다 — `protected` 진입점이라 **서브클래스 없이는 도달할 수 없다**. +- 사용자 영향: 런타임 동작 **불변**(시험만 늘었다). ★두 가드가 사라지면 이제 **CI 가 말한다**. +- 검증: ★**양방향 · 자리마다** — 픽스처를 **일시 제거**하면 두 개악이 **둘 다 green**(안 잠김) ↔ 있으면 **둘 다 RED**. + `cargo test --all` **554/0/1**(전체 실행) · DoD 7종 rc=0. +- ★**부수 발견**: 기존 픽스처가 이미 **`ClassLoader`** 를 상속하고 있었다 ⇒ 진짜 미확인은 「런타임 클래스 상속」이 아니라 + **«추상 + `protected` 진입점» 조합**뿐이었다. 조사 전제를 좁혔으면 더 쌌다. +- 후속 추천: 이 축의 **미커버가 0 이 됐다**는 사실 자체를 기록(다음 사람이 같은 20곳을 다시 세지 않도록). + ## [2026-09-12] `ZipFile.getInputStream` 가드 잠금 — ★`ZipOutputStream` 을 만들지 않았다 (rustjava-zip-output-stream-minimal-for-fixture-reachability) - 무엇을: 규격 근거로 넣었으나 **잠기지 않던** `ZipFile.getInputStream(ZipEntry)` 가드를 픽스처 `test-data/ZipGuards` 로 **잠갔다**. ★**런타임(`.rs`) 변경 0줄 · `ZipOutputStream` 구현 0줄 · 추가 바이너리 0.** diff --git a/STATE.md b/STATE.md index 659416e2..b5c54534 100644 --- a/STATE.md +++ b/STATE.md @@ -4,6 +4,26 @@ (없음 — 2026-09-11 실측: 진행 티켓 0 · 열린 PR 0. ※「열린 PR 0」은 ★**이 PR(#35) 착지 시점 기준**이다 — 회신 시점에는 #35 자신이 열려 있다) ## 완료 +- [rustjava-fixture-subclassing-abstract-runtime-classes] ★★**게스트 서브클래싱은 «된다» — `java/net` 가드 2곳을 잠갔다(`.rs` 0줄).** + 채택 제안 `2026-09-12-null-guard-spec-triage#p1`. 픽스처 `test-data/NullNetGuards`(2케이스). + ★**이 티켓은 «조사»가 1순위였다** — 제안 자신이 「**안 되면 그 사실이 산출물**」이라 적었다. ⇒ ★**판정 = «된다».** + ★**실측**: `URLStreamHandler` 를 상속해 추상 `openConnection(URL)` 만 구현한 **중첩 클래스**로 `protected setURL(...)` 호출 + ⇒ 인스턴스화·도달·**NPE** 전부 관측. `JarURLConnection` 도 `super(null)` + 추상 `getJarFile()` 구현으로 같은 형태가 선다. + ★★**선례가 이미 있었다** — 기존 픽스처가 **`ClassLoader`**(런타임 제공 클래스)를 상속한다 + ⇒ ★**진짜 미확인은 「런타임 클래스 상속」이 아니라 «추상 + `protected` 진입점» 조합뿐이었다**(조사 전제를 좁혔어야 더 쌌다). + ★접근 제어는 장애가 아니었다(이 런타임은 접근 플래그 미강제) — 막힐 수 있던 **클래스 링크·추상 메서드 구현**이 통과했다. + ★★**양방향 — «전/후»가 뒤집힌다**: 픽스처를 **일시 제거**하면 두 개악이 **둘 다 green**(안 잠김) ↔ 픽스처가 있으면 **둘 다 RED**(`class_instance.rs:108`) · 복원 **ok**. + ★`cargo test --all` **554 / 0 / 1**(전체 실행) · DoD 7종 rc=0 · ★`.rs` **무접촉** · 런타임 기능 추가 **0**(계약 3·대전제 ⓑ). + ★**감사 수 불변**(N 1,175 · M 25 · K 11) — 당연하다: 이 회차는 가드를 더하지 않고 **시험**만 더한다. + ★**미커버 나머지 1곳(`ZipFile.getInputStream`)은 끌어들이지 않았다**(대전제 ⓒ) — **형제 PR #40** 몫이고 파일이 겹치지 않는다. + ★★**게이트③ 착지 — PR #41 · `--merge`**(등재 repo · 스쿼시는 부모 2개를 1개로 접어 계보를 지운다). + 게이트② **1회차 approve**(반려 0) · 핀 `5bedc1bb` **불이동**(해소·동봉 전 실측) · `ci-presence` **rc=0 CI_GREEN** · + 자식 PR **0건** · ★**배포 워크플로 0개 ⇒ 배포 0**. + ★**착수 시 `CONFLICTING`** — PR #40 이 먼저 착지해 원장 2파일 최상단이 겹쳤다(★**코드 충돌 0** ⇒ 2-c⒜ 승인 범위). + **합집합**으로 해소(줄 단위 양방향 소실 **0** · 항목 바이트 동일). + ★**해소가 만든 «절 구분 빈 줄» 1곳을 복원했다** — `=======` 마커가 구분 역할을 하고 있었다. + ★**같은 형태의 «선재» 결손 4곳(2026-08-24 까지 거슬러 간다)은 «손대지 않았다»**(계약 7 — 총괄 승계 소재). + ★★**이 착지로 null 가드 축의 «미커버»가 «0»** 이 된다(#40 의 1곳 + 이 회차 2곳이 마지막이었다). - [rustjava-zip-output-stream-minimal-for-fixture-reachability] ★★**`ZipFile.getInputStream` 가드를 잠갔다 — 그런데 ★`ZipOutputStream` 을 «만들지 않았다»(런타임 0줄).** 채택 제안 `2026-09-12-null-guard-spec-triage#p0`. 픽스처 `test-data/ZipGuards`. ★★**제안의 «근인 진단»이 틀렸다** — 「근인은 `ZipOutputStream` 부재」였으나, `ZipFile` 인스턴스를 만들려면 diff --git a/docs/worklog/2026-09-12-net-guard-subclass-fixture.json b/docs/worklog/2026-09-12-net-guard-subclass-fixture.json new file mode 100644 index 00000000..937b3bc0 --- /dev/null +++ b/docs/worklog/2026-09-12-net-guard-subclass-fixture.json @@ -0,0 +1,44 @@ +{ + "schema": 1, + "date": "2026-09-12", + "title": "게스트 서브클래싱은 «된다» — java/net 가드 2곳 잠금(.rs 0줄)", + "services": [ + "RustJava" + ], + "taskId": "rustjava-fixture-subclassing-abstract-runtime-classes", + "summary": "이 티켓은 «조사»가 1순위였다 — 게스트 픽스처가 추상 런타임 클래스(URLStreamHandler · JarURLConnection)를 상속할 수 있는지가 미확인이었고 제안 자신이 「안 되면 그 사실이 산출물」이라 적었다. ★판정 = «된다»: URLStreamHandler 를 상속해 추상 openConnection(URL) 만 구현한 중첩 클래스로 protected setURL(...) 을 부르니 인스턴스화·호출 도달·NPE 가 모두 관측됐고, JarURLConnection 도 super(null) + 추상 getJarFile() 구현으로 같은 형태가 선다. ★선례도 이미 있었다 — 기존 픽스처가 ClassLoader(런타임 제공 클래스)를 상속한다 ⇒ 몰랐던 것은 「런타임 클래스 상속」이 아니라 «추상 + protected 진입점» 조합뿐이었다. 접근 제어는 장애가 아니었고(이 런타임은 접근 플래그를 강제하지 않는다) 막힐 수 있었던 클래스 링크·추상 메서드 구현 쪽이 통과했다. ⇒ 픽스처 test-data/NullNetGuards(2케이스)로 두 가드를 잠갔다. .rs 변경 0 — 가드는 이미 있었고 이 회차는 «잠그기»다. ★양방향: 픽스처를 일시 제거하면 두 개악이 모두 green(안 잠김)이고 픽스처가 있으면 모두 RED(class_instance.rs:108) 다.", + "changes": [ + "test-data/src/NullNetGuards.java + test-data/NullNetGuards.class(+ $Handler · $JarConn) + test-data/NullNetGuards.txt — 2케이스 회귀 픽스처", + "docs/worklog/2026-09-12-net-guard-subclass-fixture.md — 판정 근거·양방향 대조·형제 PR 순서", + "STATE.md · REPORT.md — 회차 기록" + ], + "verification": [ + "ⓐ 판정(된다): URLStreamHandler 상속 중첩 클래스로 protected setURL(null,...) 호출 → NPE 관측 · JarURLConnection 은 super(null) 로 같은 형태 성립", + "★양방향 — 자리마다: 픽스처 «없음»(일시 제거) 상태에서 두 가드 단일 제거 → 둘 다 green(안 잠김) · 픽스처 «있음» → 둘 다 RED @ jvm/src/class_instance.rs:108 · 복원 ok", + "정상 → green: cargo test --all 554 passed / 0 failed / 1 ignored(★전체 실행) · cargo fmt --check rc=0 · clippy stable/beta/wasm32 rc=0 · check-worklog-json.py rc=0 · check-dod-ci-parity.py rc=0", + "★범위: git status rustjava-runtime/ = 0(.rs 무접촉) · 런타임 기능 추가 0 · 감사 수 불변(N 1,175 · M 25 · K 11 — 시험만 더했으므로 당연하다)", + "픽스처 형식: javac --release 8 ⇒ major 52 · invokedynamic/CP 태그 15~18 0건" + ], + "issues": [ + "★[게이트③ 집행] PR #41 을 --merge 로 착지시켰다(등재 repo). 게이트② 1회차 approve · 핀 5bedc1bb 불이동(해소·동봉 «전» 실측) · ci-presence rc=0 CI_GREEN · 자식 PR 0건 · 배포 0. ★착수 시 CONFLICTING 이었으나 PR #40 선착지로 원장 2파일만 겹친 계약 2-c 의 정상 형태이고 코드 충돌 0 이라 합집합으로 해소했다.", + "★선례가 이미 있었다 — 기존 픽스처가 ClassLoader(런타임 제공 클래스)를 상속한다. 즉 「런타임 클래스 상속이 되는가」는 답이 있었고 진짜 미확인은 «추상 클래스 + protected 진입점» 조합뿐이었다. 조사 티켓의 전제를 좁혀 적었어야 더 쌌다.", + "★미커버 3곳 중 나머지 1곳(ZipFile.getInputStream)은 끌어들이지 않았다(대전제 ⓒ) — 형제 PR #40 의 몫이고 파일이 겹치지 않는다.", + "★형제 PR #40 과 STATE.md·REPORT.md 가 같은 자리에서 겹친다 ⇒ 나중에 착지하는 쪽이 그 2파일에서 충돌한다(코드 충돌 0 · 양쪽 다 .rs 무접촉). 계약 2-c 가 «정상»이라 규정한 형태이고 base 당김 + 합집합으로 해소된다.", + "★[해소 부산물] 마커(=======)가 REPORT.md 의 절 구분 빈 줄 역할을 하고 있어 마커 제거가 두 절을 붙였다 — 이 회차가 만든 1곳만 복원했다. ★같은 형태의 선재 결손이 4곳 더 있고(2026-08-24 까지) 계약 7 에 따라 손대지 않았다 ⇒ 총괄 승계 소재. 다음 union 해소 회차는 «마커가 구분선이었는지»를 함께 보라." + ], + "proposals": [ + { + "title": "rustjava-null-guard-axis-closed-no-further-tickets", + "plainSummary": "null 가드 축에서 «잠기지 않은 가드»가 0 이 됐다는 사실을 기록하고, 이 축에서 더 만들 티켓이 없음을 확인한다.", + "userBenefit": "다음 사람이 이 축을 다시 훑어 «남은 일이 있나»를 재조사하지 않는다.", + "why": "감사(K 20)→규격 삼분(⒜ 9 닫음)→파일 IO 픽스처(6 잠금)→zip(1 잠금 · PR #40)→이 회차(2 잠금)로 미커버가 0 이 됐고, 잔여 11 은 ⒝1+⒞8+오탐2 라 «규격상 닫을 것 0» 으로 이미 판정됐다. 그 종결을 한 곳에 적어 두지 않으면 다음 회차가 같은 20곳을 다시 센다.", + "tradeoff": "코드 산출이 0 인 기록 전용 회차다 — 값은 «다시 조사하지 않게 하는 것» 하나이고, 그것이 이 리니지가 반복해 지불한 비용이다.", + "effort": "S", + "target": "RustJava STATE.md · docs/worklog" + } + ], + "adoptedProposals": [ + "2026-09-12-null-guard-spec-triage#p1" + ], + "declinedProposals": [] +} diff --git a/docs/worklog/2026-09-12-net-guard-subclass-fixture.md b/docs/worklog/2026-09-12-net-guard-subclass-fixture.md new file mode 100644 index 00000000..ad971a0f --- /dev/null +++ b/docs/worklog/2026-09-12-net-guard-subclass-fixture.md @@ -0,0 +1,47 @@ +# 2026-09-12 — 게스트 서브클래싱은 «된다» ⇒ `java/net` 가드 2곳 잠금 + +채택 제안 `2026-09-12-null-guard-spec-triage#p1`. + +★**이 티켓은 «조사»가 1순위였다** — 「게스트 픽스처가 추상 런타임 클래스를 상속할 수 있는가」가 **미확인**이었고, +제안 자신이 「안 되면 **그 사실이 산출물**」이라 적었다. ⇒ ★**판정 = «된다».** 그래서 잠갔다. + +## ⓐ 판정 — ★**된다** (최소 게스트 클래스로 실측) + +`URLStreamHandler` 를 상속해 추상 `openConnection(URL)` 만 구현한 **중첩 클래스**로 `protected setURL(...)` 을 호출했다 +⇒ ★**인스턴스화 성공 · 호출 도달 · `NullPointerException` 관측**. 같은 형태가 `JarURLConnection` 에서도 선다(`super(null)` + 추상 `getJarFile()` 구현). + +★**선례도 이미 있었다** — 기존 픽스처가 **`ClassLoader`**(런타임 제공 클래스)를 상속한다(`test-data/src/` 의 `extends` 전수에서 확인). +⇒ ★**「런타임 클래스 상속이 되는가」는 사실 이미 답이 있었고, 몰랐던 것은 «추상 + protected 진입점» 조합뿐이다.** + +★**접근 제어가 장애가 아니었다** — 이 런타임은 접근 플래그를 강제하지 않는다(선행 회차 실측). +막힐 수 있었던 것은 **클래스 링크·추상 메서드 구현** 쪽이었고, 그쪽이 **통과**했다. + +## 산출물 — 픽스처 `test-data/NullNetGuards` (2케이스) + +★**`.rs` 변경 0** — 가드는 이미 있었고 이 회차는 **잠그기**다. +중첩 클래스 2개(`$Handler`·`$JarConn`)로 두 진입점에 **null** 을 넘긴다. +※하버스가 `$` 든 이름을 건너뛰므로 중첩 클래스에 별도 `.txt` 가 필요 없다(선행 회차와 같은 관용). + +## ★★양방향 — «전/후»가 뒤집힌다 + +| 판 | `url_stream_handler.set_url` | `jar_url_connection.init` | +|---|---|---| +| **픽스처 «없음»**(착수 시 형상 — 픽스처를 일시 제거해 재측) | ★**green**(안 잠김) | ★**green**(안 잠김) | +| **픽스처 «있음»** | ★**RED** `class_instance.rs:108` | ★**RED** `class_instance.rs:108` | + +⇒ ★**같은 개악이 전/후로 뒤집힌다** = 이 픽스처가 «실제로» 그 두 가드를 문다(한 자리로 일반화하지 않고 **자리마다** 쟀다). + +## 범위 — 「여기까지」 + +- ★**런타임 기능 «추가» 0**(계약 3 · 대전제 ⓑ) — `.rs` 무접촉(`git status rustjava-runtime/` = 0). +- ★**미커버 3곳 중 나머지 1곳(`ZipFile.getInputStream`)은 끌어들이지 않았다**(대전제 ⓒ) — 그것은 **형제 PR #40** 의 몫이고, 이 회차와 **파일이 겹치지 않는다**. +- 감사 수 **불변**(N 1,175 · M 25 · K 11) — ★**당연하다**: 이 회차는 가드를 더하지 않고 **시험**만 더한다. + +## ★형제 PR 과의 순서 (알고 남긴다) + +**PR #40**(`…-zip-getinputstream-guard-lock`)이 열려 있고 **`STATE.md`·`REPORT.md` 를 같은 자리에** 쓴다 +⇒ ★**나중에 착지하는 쪽이 그 2파일에서 충돌한다**(코드 충돌은 없다 — 양쪽 다 `.rs` 무접촉). +★이것은 계약 2-c 가 「정상」이라 규정한 형태이고, 그 회차가 **base 당김 + 합집합**으로 해소하면 된다. + +## 제안 +- p0: 미커버로 남는 것은 이제 **0** 이다(이 회차 2 + PR #40 의 1) — 다음은 «잠금»이 아니라 **잔여 ⒝⒞ 11곳의 처분**(규격 삼분이 이미 끝났으므로 «닫을 것 0» 이 결론이다). ⇒ ★**이 축에서 더 만들 티켓이 없다**는 것 자체를 기록해 둔다. diff --git a/test-data/NullNetGuards$Handler.class b/test-data/NullNetGuards$Handler.class new file mode 100644 index 00000000..dc4d05b2 Binary files /dev/null and b/test-data/NullNetGuards$Handler.class differ diff --git a/test-data/NullNetGuards$JarConn.class b/test-data/NullNetGuards$JarConn.class new file mode 100644 index 00000000..4f61e8fc Binary files /dev/null and b/test-data/NullNetGuards$JarConn.class differ diff --git a/test-data/NullNetGuards.class b/test-data/NullNetGuards.class new file mode 100644 index 00000000..5d2946ed Binary files /dev/null and b/test-data/NullNetGuards.class differ diff --git a/test-data/NullNetGuards.txt b/test-data/NullNetGuards.txt new file mode 100644 index 00000000..af4d7d1b --- /dev/null +++ b/test-data/NullNetGuards.txt @@ -0,0 +1,4 @@ +URLStreamHandler.setURL(URL,...): +caught NPE +JarURLConnection(URL): +caught NPE diff --git a/test-data/src/NullNetGuards.java b/test-data/src/NullNetGuards.java new file mode 100644 index 00000000..7ca8514a --- /dev/null +++ b/test-data/src/NullNetGuards.java @@ -0,0 +1,51 @@ +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.util.jar.JarFile; + +// Locks the two null guards that sit behind `protected` entry points on abstract runtime +// classes. Neither is reachable without a subclass, so the fixture supplies the smallest +// one that compiles: implement the abstract methods, then call the protected entry point. +class NullNetGuards { + static class Handler extends URLStreamHandler { + protected URLConnection openConnection(URL u) { + return null; + } + + void callSetUrl() { + setURL(null, "http", "host", 1, "file", "ref"); + } + } + + static class JarConn extends JarURLConnection { + JarConn(URL u) throws Exception { + super(u); + } + + public JarFile getJarFile() { + return null; + } + + public void connect() { + } + } + + public static void main(String[] args) throws Exception { + System.out.println("URLStreamHandler.setURL(URL,...):"); + try { + new Handler().callSetUrl(); + System.out.println("should not reach"); + } catch (NullPointerException e) { + System.out.println("caught NPE"); + } + + System.out.println("JarURLConnection(URL):"); + try { + new JarConn(null); + System.out.println("should not reach"); + } catch (NullPointerException e) { + System.out.println("caught NPE"); + } + } +}