Skip to content

Commit cb2fde2

Browse files
committed
Use inline format args in tracing logs
Convert tracing arguments that are bare identifiers to inline format captures ("...{this:?}"), including partial conversion where only some arguments qualify (e.g. "{}.{name}:{descriptor}" keeps the method-call argument positional). Method calls, field accesses, and raw identifiers (r#in, r#type) stay positional since they cannot be captured inline.
1 parent c5cf231 commit cb2fde2

111 files changed

Lines changed: 1605 additions & 509 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# 버그 11건 수정 아키텍처
2+
3+
## 1. 설계 개요
4+
5+
기존 구조를 바꾸지 않는 국소 수정. 유일한 인터페이스 변경은 `ClassDefinition` trait에 인터페이스 목록 노출 메서드 추가(버그 #3). 나머지는 함수 단위 수정 + 예외 클래스 3종 추가.
6+
7+
테스트 인프라 현황 (조사 결과):
8+
- classfile: `classfile/tests/test.rs``test_data/*.class``include_bytes!`로 파싱해 opcode 오프셋 단위 assert. `test_data/Interface.class`가 이미 invokeinterface를 포함하며(`b9 000a 01 00` @ offset 9), 현재 파서는 count/zero 2바이트를 다음 opcode(aconst_null, nop)로 오파싱함 — 단순 실행에선 우연히 통과하므로 기존 E2E가 못 잡았음.
9+
- jvm: `jvm/tests/*.rs``test_utils::test_jvm()` 사용 (dev-dep).
10+
- java_runtime: `java_runtime/tests/classes/java/{lang,io,util}/test_*.rs``test_jvm()` + `invoke_virtual/invoke_static` 패턴. 예외 검증은 `JavaError::JavaException` 매칭(test_object.rs:117 참고).
11+
- 루트: `tests/test_class.rs``test_data/*.class`(.txt와 쌍) 전수 실행. **주의**: `$` 없는 .class는 모두 main 실행 + .txt 필요. 단위테스트용 클래스는 `test_data/unit/` 하위에 둠(디렉토리는 스킵됨).
12+
- javac 26 사용 가능 — 새 테스트 클래스는 `javac --release 21`로 컴파일해 커밋 (기존 .class도 major 65).
13+
14+
## 2. 빌딩 블록
15+
16+
| 순서 | 블록 | 핵심 | 변경 파일 | 의존 |
17+
|---|---|---|---|---|
18+
| 1 | classfile opcode 파싱 || classfile/src/opcode.rs, classfile/tests/test.rs | 없음 |
19+
| 2 | 인터페이스 instanceof || jvm/src/class_definition.rs, jvm/src/jvm.rs, jvm_rust/src/class_definition.rs, jvm/tests/, test_data | 없음 |
20+
| 3 | Putstatic 좁히기 | | jvm_rust/src/interpreter.rs, tests/(루트), test_data/unit/ | 없음 |
21+
| 4 | to_rust_string lossy | | jvm/src/runtime/java_lang_string.rs, jvm/tests/ | 없음 |
22+
| 5 | java.io (skip/mark) | | java_runtime io 2파일 + tests | 없음 |
23+
| 6 | lang/util + 예외 3종 | | java_runtime (integer/string/vector + 신규 예외 3파일 + lang.rs/util.rs/loader.rs) + tests | 없음 |
24+
25+
블록 간 의존 없음. 순차 진행(1→6), 블록마다 실패 테스트 → 수정 → 통과 확인.
26+
27+
## 3. 블록별 상세
28+
29+
### 블록 1: classfile (★)
30+
- **테스트(선행, 실패 확인)**: `classfile/tests/test.rs`
31+
- `test_interface`: Interface.class main 코드 — `code.len() == 7`, offset 9에 `Invokeinterface(_, 1, 0)`, offset 14에 `Return`, offset 12/13 없음. (현재: len 9, offset 12에 AconstNull)
32+
- `test_invokedynamic_operands`: `Opcode::parse(&[0xba, 0x00, 0x01, 0x00, 0x00], ...)` — remaining이 비어야 함. 상수풀은 최소 구성(Utf8 1개). `ConstantPoolItem` export 필요 시 lib.rs 확인.
33+
- **수정**: 0xb9 → `(be_u16, u8, u8)` 4바이트 소비, count/zero 실값 보존. 0xba → index 뒤 2바이트 추가 소비.
34+
35+
### 블록 2: 인터페이스 instanceof (★)
36+
- **테스트(선행)**:
37+
- `jvm/tests/test_is_instance.rs`: ByteArrayInputStream+DataInputStream 생성 후 `is_instance(dis, "java/io/DataInput")` == true (proto: data_input_stream.rs:19). 음성 케이스도 확인.
38+
- E2E: `test_data/InterfaceCast.java` (신규, javac 컴파일·커밋): 중첩 타입 `I`/`Base implements I`/`Derived extends Base`, main에서 `instanceof` + checkcast 결과 출력. expected `true\ntrue\n`. 클래스파일 경로(from_classfile) 검증.
39+
- **수정**:
40+
- `ClassDefinition` trait에 `fn interface_names(&self) -> Vec<String>` (기본 구현 `vec![]` — ArrayClassDefinitionImpl은 변경 불요).
41+
- `ClassDefinitionImpl`: inner에 interfaces 저장. `from_class_proto``proto.interfaces`, `from_classfile``class.interfaces` (`Vec<Arc<String>>`).
42+
- `jvm.rs is_inherited_from`: 이름 일치 → 인터페이스 목록(이름 직접 일치 또는 로드된 인터페이스 정의로 재귀, 미로드 인터페이스는 skip) → 슈퍼클래스 재귀. lock 가드 수명 주의(definition clone 후 재귀).
43+
44+
### 블록 3: Putstatic
45+
- **테스트(선행)**: `test_data/unit/StaticFlag.java` (`static boolean FLAG = true;` — <clinit>에 putstatic). 루트 `tests/test_putstatic.rs`: `ClassDefinitionImpl::from_classfile` + `jvm.register_class``get_static_field::<bool>` — 현재는 "Expected boolean, got Int" 패닉.
46+
- 루트 Cargo.toml에 `[dev-dependencies] test_utils` 추가 필요.
47+
- **수정**: interpreter.rs Putstatic에 Putfield(911-917)와 동일한 디스크립터 기반 좁히기 적용 (공통 헬퍼로 추출).
48+
49+
### 블록 4: to_rust_string
50+
- **테스트(선행)**: `jvm/tests/test_string.rs` (신규): `instantiate_array("C",1)` + `store_array(0xD800u16)` + `new_class("java/lang/String","([C)V",...)` + `to_rust_string` — 현재 패닉, 수정 후 `"\u{FFFD}"`.
51+
- **수정**: `String::from_utf16``String::from_utf16_lossy`.
52+
53+
### 블록 5: java.io
54+
- **테스트(선행)**: `java_runtime/tests/classes/java/io/test_byte_array_input_stream.rs` (신규):
55+
- mark/reset: [10,20,30] 읽기 1회 → mark(100) → 읽기 1회 → reset → 다음 read가 20 다시. 현재는 pos=100으로 점프해 -1.
56+
- `test_file_input_stream.rs`에 skip 테스트 추가: 5바이트 파일에서 `skip(10)` == 5 (현재 10 반환), 이후 read() == -1. FileInputStream은 skip 미오버라이드 → InputStream.skip 경로.
57+
- **수정**: ByteArrayInputStream.mark — `pos`를 mark 필드에 저장. InputStream.skip — 고정 크기(4096) 버퍼로 read 루프, EOF(-1) 중단, 실제 스킵 바이트 수 반환.
58+
59+
### 블록 6: lang/util + 예외 3종
60+
- **신규 예외 클래스** (arithmetic_exception.rs 패턴 복제):
61+
- `java/lang/NumberFormatException` (parent: IllegalArgumentException)
62+
- `java/lang/StringIndexOutOfBoundsException` (parent: IndexOutOfBoundsException)
63+
- `java/util/NoSuchElementException` (parent: RuntimeException)
64+
- lang.rs/util.rs mod·re-export + loader.rs 등록.
65+
- **테스트(선행)**:
66+
- test_integer.rs: `parseInt("abc")``JavaError::JavaException` + `is_instance(e, "java/lang/NumberFormatException")`. 현재 unwrap 패닉.
67+
- test_string.rs: `substring(3,1)` → StringIndexOutOfBoundsException (현재 underflow 패닉); `getBytes("ISO-8859-1")` with "a한b" → `[0x61, 0x3F, 0x62]` (현재 절단값).
68+
- test_vector.rs: 빈 Vector `firstElement()` → NoSuchElementException (현재 null); null 요소 add 후 `indexOf(null)` == 인덱스 (현재 -1).
69+
- **수정**: integer.rs parse 에러 매핑, string.rs substring_with_end 범위 검증(begin<0 || end>len || begin>end), encode_str ISO-8859-1 0x3F 치환, vector.rs firstElement throw + indexOf null 매칭.
70+
71+
## 4. 에러 처리 전략
72+
73+
기존 `jvm.exception(class, msg)` 패턴 유지. 신규 예외 클래스는 기존 예외 proto와 동일 구조(생성자 2종, 메시지는 부모에 위임).
74+
75+
## 5. 가정 및 제약
76+
77+
- 인터페이스 검사에서 아직 로드되지 않은 슈퍼인터페이스는 검사에서 제외(is_inherited_from이 sync라 로드 불가). 클래스 등록 시 직접 인터페이스 이름은 항상 알 수 있으므로 1단계 매칭은 보장됨.
78+
- 배열의 Cloneable/Serializable 구현은 비목표(기본 구현 `vec![]`).
79+
- invokedynamic은 파싱만 수정(상수풀 tag 15~18 미지원, 인터프리터 todo!() 유지).
80+
- 신규 .class 파일은 javac --release 21로 컴파일해 커밋(소스 .java는 기존 관례상 미커밋이나, 재현성을 위해 test_data에 .java도 커밋할지는 사용자 결정 — 일단 .java도 같이 둠. 기존 테스트 러너는 .class/.jar 외 확장자를 스킵하므로 안전).
81+
82+
## 6. 프로젝트 분류 및 보충 지침
83+
84+
Rust 라이브러리/런타임 버그픽스 — 해당 보충 지침 없음.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Forge 최종 리포트: 버그 11건 수정
2+
3+
## 1. 요약
4+
5+
- **최종 상태**: 완료
6+
- **QA 판정**: PASS (`cargo test --workspace` 25개 타깃 전부 통과)
7+
- **빌딩 블록**: 6개 (핵심 2개: classfile opcode 파싱, 인터페이스 instanceof)
8+
- **신규 테스트**: 13건 (모두 수정 전 실패 → 수정 후 통과 확인)
9+
10+
## 2. 요구사항 달성 현황
11+
12+
| # | 버그 | 상태 | 실패 테스트 (선작성) |
13+
|---|------|------|---------------------|
14+
| 1 | invokeinterface 2/4바이트 소비 | ✅ 수정 | `classfile/tests/test.rs::test_invokeinterface` (Interface.class 오프셋 검증), `opcode.rs::test_invokeinterface_consumes_count_and_zero` |
15+
| 2 | invokedynamic 2/4바이트 소비 | ✅ 수정 | `opcode.rs::test_invokedynamic_consumes_reserved_bytes` |
16+
| 3 | 인터페이스 instanceof/checkcast 전면 실패 | ✅ 수정 | `jvm/tests/test_is_instance.rs::test_is_instance_interface`, E2E `test_data/InterfaceCast.{java,class,txt}` |
17+
| 4 | Putstatic 좁히기 누락 | ✅ 수정 | `tests/test_putstatic.rs` (+ `test_data/unit/StaticFlag.{java,class}`) |
18+
| 5 | to_rust_string unpaired surrogate 패닉 | ✅ 수정 | `jvm/tests/test_string.rs::test_to_rust_string_unpaired_surrogate` |
19+
| 6 | ByteArrayInputStream.mark이 readlimit 저장 | ✅ 수정 | `test_byte_array_input_stream.rs::test_mark_reset` |
20+
| 7 | InputStream.skip 항상 n 반환 + 거대 할당 | ✅ 수정 | `test_data_input_stream.rs::test_skip_past_eof` |
21+
| 8 | parseInt unwrap 패닉 | ✅ 수정 | `test_integer.rs::test_parse_int_invalid` |
22+
| 9 | substring(begin,end) underflow | ✅ 수정 | `test_string.rs::test_substring_invalid_range` |
23+
| 10 | ISO-8859-1 인코딩 silent 절단 | ✅ 수정 | `test_string.rs::test_get_bytes_unmappable_charset` |
24+
| 11a | firstElement가 null 반환 | ✅ 수정 | `test_vector.rs::test_first_element_empty` |
25+
| 11b | indexOf(null) 미동작 |**거짓 양성** | `test_vector.rs::test_index_of_null` — 기존 코드가 이미 올바름을 테스트로 확인, 회귀 방지용으로 유지 |
26+
27+
## 3. 주요 구현 내용
28+
29+
- **classfile**: `invokeinterface``(be_u16, u8, u8)`로, `invokedynamic``(be_u16, be_u16)`로 파싱해 4바이트 전부 소비. count/zero 실값 보존.
30+
- **jvm**: `ClassDefinition::interface_names()` 추가 (기본 구현 `vec![]` — 배열 클래스는 변경 불요). `is_inherited_from`이 인터페이스 이름 직접 일치 + 로드된 인터페이스의 전이적 검사 + 슈퍼클래스 재귀를 수행. lock 가드를 clone 후 해제하도록 변경.
31+
- **jvm_rust**: `ClassDefinitionImpl`이 proto/classfile의 인터페이스 목록을 저장·노출. Putfield의 좁히기 로직을 `to_field_type` 헬퍼로 추출해 Putstatic에도 적용.
32+
- **java_runtime**: 예외 클래스 3종 신규(`NumberFormatException`, `StringIndexOutOfBoundsException`, `NoSuchElementException`) + loader 등록. skip은 4096바이트 버퍼 루프, mark는 pos 저장, parseInt/substring은 예외 throw, ISO-8859-1은 `?` 치환, firstElement는 NoSuchElementException throw.
33+
34+
## 4. 검증
35+
36+
- 버그별 테스트: 수정 전 실패 확인(블록 5 skip은 stash로 재확인) → 수정 후 통과.
37+
- `cargo test --workspace`: 25개 테스트 타깃 전부 통과 (java_runtime 86건 포함).
38+
- `cargo clippy --workspace --all-targets`: 신규 경고 0건. **단, 기존 코드의 사전 존재 오류 1건**: `test_string.rs:309`의 리터럴 `3.14f64``clippy::approx_constant`(deny-by-default)에 걸림 — 커밋 79e364f(WIPI String API)부터 존재하던 문제로 이번 변경과 무관. 별도 수정 권장.
39+
40+
## 5. 알려진 한계 / 후속 제안
41+
42+
- 전이적 슈퍼인터페이스는 해당 인터페이스가 이미 로드된 경우에만 검사됨 (`is_inherited_from`이 sync여서 로드 불가). 직접 구현 인터페이스는 이름 매칭으로 항상 동작.
43+
- 배열의 `Cloneable`/`Serializable`, 배열 공변성은 비목표로 제외.
44+
- invokedynamic은 파싱만 수정 (상수풀 tag 15~18 미지원, 인터프리터 `todo!()` 유지).
45+
- TestRuntime의 `get_file``Box<dyn File>`을 clone해 반환하므로 FileInputStream의 순차 read가 위치를 유지하지 않음 — 테스트 인프라 개선 후보.
46+
- 기존 `test_string.rs:309`의 clippy approx_constant 오류 수정 권장.
47+
48+
## 6. 산출물
49+
50+
| 분류 | 경로 |
51+
|------|------|
52+
| 문서 | `docs/forge/20260610-fix-found-bugs/{prd,architecture,final-report}.md` |
53+
| 소스 수정 | `classfile/src/opcode.rs`, `jvm/src/{jvm,class_definition}.rs`, `jvm/src/runtime/java_lang_string.rs`, `jvm_rust/src/{class_definition,interpreter}.rs`, `java_runtime/src/classes/java/{io,lang,util}/...`, `java_runtime/src/loader.rs` |
54+
| 신규 클래스 | `java_runtime/.../lang/number_format_exception.rs`, `.../lang/string_index_out_of_bounds_exception.rs`, `.../util/no_such_element_exception.rs` |
55+
| 테스트 | `classfile/tests/test.rs`, `classfile/src/opcode.rs`(단위), `jvm/tests/{test_is_instance,test_string}.rs`, `tests/test_putstatic.rs`, `java_runtime/tests/classes/...` 5파일 |
56+
| 테스트 데이터 | `test_data/InterfaceCast.{java,class,txt}` + 중첩 클래스 3개, `test_data/unit/StaticFlag.{java,class}` |
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# PRD: 코드 분석에서 발견된 버그 11건 수정
2+
3+
- **날짜**: 2026-06-10
4+
- **방식**: 버그별로 실패하는 테스트를 먼저 작성하고, 수정 후 통과를 확인하는 TDD
5+
6+
## 배경
7+
8+
RustJava(러스트로 작성된 JVM 구현) 전체 코드베이스 분석에서 11건의 확인된 버그가 발견됐다. 가장 심각한 것은 invokeinterface opcode 디코딩 오류(바이트코드 스트림 전체 오염)와 인터페이스에 대한 instanceof/checkcast 전면 실패다.
9+
10+
## 목표
11+
12+
각 버그에 대해:
13+
1. 현재 코드에서 **실패하는** 테스트케이스를 먼저 작성한다 (버그 재현 확인).
14+
2. 버그를 수정한다.
15+
3. 테스트가 통과함을 확인한다.
16+
4. 기존 테스트 전체(`cargo test --workspace`)가 여전히 통과함을 확인한다.
17+
18+
## 수정 대상 버그 목록
19+
20+
### A. classfile 크레이트 (opcode 파싱)
21+
22+
| # | 위치 | 내용 | 기대 동작 |
23+
|---|------|------|----------|
24+
| 1 | `classfile/src/opcode.rs:324-327` | `invokeinterface`(0xb9)가 피연산자 4바이트(index u16 + count u8 + zero u8) 중 2바이트만 소비 → 이후 바이트코드 오프셋이 전부 밀림 | 4바이트 모두 소비, count/zero를 실제 값으로 파싱 |
25+
| 2 | `classfile/src/opcode.rs:320-323` | `invokedynamic`(0xba)도 index u16 + zero 2바이트 중 2바이트만 소비 | 4바이트 모두 소비 |
26+
27+
### B. jvm 크레이트 (코어)
28+
29+
| # | 위치 | 내용 | 기대 동작 |
30+
|---|------|------|----------|
31+
| 3 | `jvm/src/jvm.rs:571-581` + `jvm/src/class_definition.rs` | `is_inherited_from`이 슈퍼클래스 체인만 확인, 인터페이스 미확인. trait에 인터페이스 노출 메서드 부재 | `ClassDefinition``interface_names()` 추가, 직접/전이적(슈퍼인터페이스·슈퍼클래스의 인터페이스 포함) 인터페이스 검사 |
32+
| 5 | `jvm/src/runtime/java_lang_string.rs:15` | `String::from_utf16().unwrap()` — unpaired surrogate에서 패닉 | `from_utf16_lossy`로 대체 (JDK의 replacement char 동작과 일치) |
33+
34+
### C. jvm_rust 크레이트 (인터프리터)
35+
36+
| # | 위치 | 내용 | 기대 동작 |
37+
|---|------|------|----------|
38+
| 4 | `jvm_rust/src/interpreter.rs:921-925` | `Putstatic``Putfield`와 달리 디스크립터 기반 Int→Boolean/Byte/Char/Short 좁히기를 안 함 → static boolean 필드에 Int가 저장되어 이후 Rust 쪽 typed 읽기에서 패닉 | Putfield와 동일한 디스크립터 기반 좁히기 적용 |
39+
40+
### D. java_runtime 크레이트 (표준 라이브러리)www
41+
42+
| # | 위치 | 내용 | 기대 동작 |
43+
|---|------|------|----------|
44+
| 6 | `java_runtime/.../io/byte_array_input_stream.rs:154` | `mark(readlimit)`가 현재 `pos` 대신 `readlimit`을 mark 필드에 저장 | 현재 pos 저장 (readlimit은 무시 — ByteArrayInputStream 시맨틱) |
45+
| 7 | `java_runtime/.../io/input_stream.rs:50-57` | `skip(n)`이 read 결과 무시하고 항상 n 반환 + n 크기 배열 통째 할당 | 실제 skip된 바이트 수 반환, EOF(-1) 처리, 고정 크기 버퍼로 루프 |
46+
| 8 | `java_runtime/.../lang/integer.rs:79` | `parseInt`가 잘못된 입력에서 `unwrap()` 패닉 | `NumberFormatException` throw |
47+
| 9 | `java_runtime/.../lang/string.rs:356` | `substring(begin, end)``end < begin`에서 usize underflow | `StringIndexOutOfBoundsException` throw (begin<0, end>length 포함 검증) |
48+
| 10 | `java_runtime/.../lang/string.rs:781` | ISO-8859-1 인코딩에서 0xFF 초과 문자를 silent 절단 | 매핑 불가 문자를 `?`(0x3F)로 치환 (JDK 동작) |
49+
| 11 | `java_runtime/.../util/vector.rs:340-342, 321` | `firstElement()`가 빈 Vector에서 null 반환; `indexOf(null)`이 null 요소를 못 찾음 | 빈 Vector에서 `java/util/NoSuchElementException` throw; `indexOf(null)`은 첫 null 인덱스 반환 |
50+
51+
## 비목표
52+
53+
- `wide`, `invokedynamic` opcode의 인터프리터 **실행** 구현 (`todo!()` 유지 — 파싱만 수정)
54+
- `monitorenter`/`monitorexit` 구현
55+
- String의 UTF-16 코드유닛 인덱싱 전환 (chars() 기반 인덱싱의 non-BMP 문제는 별도 작업)
56+
- 배열 공변성(`String[] instanceof Object[]`) 지원
57+
- 로컬 변수 과할당(interpreter.rs:35-37) 최적화 — 동작 버그가 아님
58+
- `Integer.parseInt(String, int radix)` 오버로드 추가 — 기존 미선언 메서드
59+
60+
## 제약
61+
62+
- 기존 테스트 스타일을 따른다: 크레이트 내 `#[cfg(test)]` 단위 테스트, `test_utils` 크레이트(테스트용 클래스 생성 헬퍼), 루트 `tests/` 통합 테스트.
63+
- `no_std`(alloc 기반) 호환 유지.
64+
- 기존 코드 스타일 존중 (주석 최소화, 기존 에러 처리 패턴 사용).
65+
- 예외 throw는 기존 패턴(`jvm.exception(...)`)을 사용. 해당 예외 클래스(`NumberFormatException`, `StringIndexOutOfBoundsException`, `NoSuchElementException`)가 java_runtime에 없으면 추가한다.
66+
67+
## 검증 기준
68+
69+
- 버그별 신규 테스트가 수정 전 실패, 수정 후 통과.
70+
- `cargo test --workspace` 전체 통과.
71+
- `cargo clippy --workspace` 신규 경고 없음.

0 commit comments

Comments
 (0)