|
| 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 라이브러리/런타임 버그픽스 — 해당 보충 지침 없음. |
0 commit comments