Skip to content

Commit 5ddb487

Browse files
committed
stability: bound large hex previews
1 parent 0f82ceb commit 5ddb487

4 files changed

Lines changed: 51 additions & 6 deletions

File tree

app/src/main/java/com/kyhsgeekcode/disassembler/ui/components/HexView.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ internal const val BYTES_PER_ROW = 16
2828
private val OFFSET_WIDTH = 90.dp
2929
private val HEX_CELL_WIDTH = 25.dp
3030
private val ASCII_CELL_WIDTH = 12.dp
31+
internal const val MAX_RENDERED_HEX_BYTES = 256 * 1024
32+
33+
data class HexPreview(
34+
val bytes: ByteArray,
35+
val originalSize: Int,
36+
val isTruncated: Boolean
37+
)
38+
39+
fun buildHexPreview(bytes: ByteArray, maxBytes: Int = MAX_RENDERED_HEX_BYTES): HexPreview {
40+
if (bytes.size <= maxBytes) {
41+
return HexPreview(bytes = bytes, originalSize = bytes.size, isTruncated = false)
42+
}
43+
return HexPreview(
44+
bytes = bytes.copyOf(maxBytes),
45+
originalSize = bytes.size,
46+
isTruncated = true
47+
)
48+
}
3149

3250
data class HexRow(
3351
val offset: Int,
Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
package com.kyhsgeekcode.disassembler.ui.tabs
22

33
import androidx.compose.foundation.ExperimentalFoundationApi
4+
import androidx.compose.foundation.layout.Column
45
import androidx.compose.runtime.Composable
56
import androidx.compose.runtime.collectAsState
67
import com.kyhsgeekcode.disassembler.project.ProjectDataStorage
78
import com.kyhsgeekcode.disassembler.ui.TabData
89
import com.kyhsgeekcode.disassembler.ui.TabKind
910
import com.kyhsgeekcode.disassembler.ui.components.HexView
11+
import com.kyhsgeekcode.disassembler.ui.components.buildHexPreview
1012
import com.kyhsgeekcode.disassembler.viewmodel.MainViewModel
13+
import androidx.compose.material3.Text
1114
import kotlinx.coroutines.flow.MutableStateFlow
1215
import kotlinx.coroutines.flow.StateFlow
1316

1417

1518
class HexTabData(val data: TabKind.Hex) : PreparedTabData() {
16-
private val _bytes = MutableStateFlow(byteArrayOf())
17-
val bytes = _bytes as StateFlow<ByteArray>
19+
private val _preview = MutableStateFlow(buildHexPreview(byteArrayOf()))
20+
val preview = _preview as StateFlow<com.kyhsgeekcode.disassembler.ui.components.HexPreview>
1821

1922
override suspend fun prepare() {
20-
_bytes.value = ProjectDataStorage.getFileContent(data.relPath)
23+
_preview.value = buildHexPreview(ProjectDataStorage.getFileContent(data.relPath))
2124
}
2225
}
2326

@@ -26,6 +29,11 @@ class HexTabData(val data: TabKind.Hex) : PreparedTabData() {
2629
@Composable
2730
fun HexTab(data: TabData, viewModel: MainViewModel) {
2831
val preparedTabData: HexTabData = viewModel.getTabData(data)
29-
val bytes = preparedTabData.bytes.collectAsState()
30-
HexView(bytes = bytes.value)
31-
}
32+
val preview = preparedTabData.preview.collectAsState()
33+
Column {
34+
if (preview.value.isTruncated) {
35+
Text("Showing first ${preview.value.bytes.size} bytes of ${preview.value.originalSize} bytes")
36+
}
37+
HexView(bytes = preview.value.bytes)
38+
}
39+
}

app/src/test/java/com/kyhsgeekcode/disassembler/ui/components/HexViewLayoutTest.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ import org.junit.jupiter.api.Assertions.assertTrue
66
import org.junit.jupiter.api.Test
77

88
class HexViewLayoutTest {
9+
@Test
10+
fun `buildHexPreview keeps short byte arrays unchanged`() {
11+
val preview = buildHexPreview(byteArrayOf(0x01, 0x02, 0x03), maxBytes = 4)
12+
13+
assertEquals(byteArrayOf(0x01, 0x02, 0x03).toList(), preview.bytes.toList())
14+
assertFalse(preview.isTruncated)
15+
}
16+
17+
@Test
18+
fun `buildHexPreview truncates long byte arrays`() {
19+
val preview = buildHexPreview(ByteArray(6) { it.toByte() }, maxBytes = 4)
20+
21+
assertEquals(byteArrayOf(0x00, 0x01, 0x02, 0x03).toList(), preview.bytes.toList())
22+
assertTrue(preview.isTruncated)
23+
assertEquals(6, preview.originalSize)
24+
}
25+
926
@Test
1027
fun `buildHexRows splits bytes into 16-byte rows with offsets`() {
1128
val input = ByteArray(18) { it.toByte() }

docs/maintenance/implementation-log.ko.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
| 이슈 `#123`, `#720` 프로젝트 export와 details 저장 부재 | ZIP 생성기를 재귀 상대경로 방식으로 고치고, 프로젝트 overview에 SAF `CreateDocument` 기반 `Export project` 버튼을 추가했으며, binary detail 탭에는 `Save Details to file` 버튼을 구현했다 | 깨져 있던 project ZIP export 경로를 실제로 복구하고, 최신 Android 저장소 모델에 맞게 상세 정보 텍스트를 `.txt` 문서로 저장할 수 있게 했다 | `app/src/main/java/com/kyhsgeekcode/Util.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/exporting/ExportDocuments.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/project/ProjectManager.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/viewmodel/MainViewModel.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/MainTab.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryDetailTab.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ProjectExportArchiveTest.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/exporting/BinaryDetailsExportTest.kt` | 완료 |
3737
| 이슈 `#219` 대용량 파일 detail 렌더링 OOM 완화 | binary detail 화면이 전체 문자열을 그대로 레이아웃하지 않도록 preview helper를 추가하고, 화면에는 잘린 preview만 유지하며 전체 내용은 export 시점에만 다시 생성하도록 바꿨다 | 큰 ELF/PE detail 문자열이 화면 레이아웃 단계에서 메모리를 과도하게 잡는 경로를 줄이고, 저장 기능은 유지한 채 UI 메모리 압박을 낮췄다 | `app/src/main/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryDetailTab.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryDetailsPreviewTest.kt` | 완료 |
3838
| 이슈 `#219`, `#523` 대용량 파일 확장 cache key OOM 완화 | archive/dex/app expansion cache 경로가 더 이상 파일 전체를 `readBytes()`로 읽지 않고, streaming SHA-256으로 cache key를 계산하도록 바꿨다 | 큰 파일을 확장하기 전에 해시 계산만으로 메모리를 크게 잡아먹던 경계를 제거해서 대용량 import의 선행 OOM 위험을 줄였다 | `app/src/main/java/com/kyhsgeekcode/filechooser/model/ExpandedFileCache.kt`, `app/src/test/java/com/kyhsgeekcode/filechooser/model/ExpandedFileCacheTest.kt` | 완료 |
39+
| 이슈 `#523` 대용량 Hex 탭 렌더링 완화 | Hex 탭이 매우 큰 파일을 열 때 전체 바이트 배열을 그대로 Compose에 전달하지 않고, 상한이 있는 preview만 렌더링하며 원본 크기 안내 문구를 보여주도록 바꿨다 | 큰 파일을 Hex 화면에 띄우는 순간 UI 레이어가 과도한 메모리와 레이아웃 비용을 쓰는 경로를 줄여서 large-file cluster를 더 좁혔다 | `app/src/main/java/com/kyhsgeekcode/disassembler/ui/components/HexView.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/tabs/HexTab.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ui/components/HexViewLayoutTest.kt` | 완료 |
3940
| 이슈 `#129` `.ar` archive 지원 | 공용 archive extractor를 추가하고, Compose/legacy drawer의 archive 확장 경로를 ZIP 전용 구현에서 generic archive extractor로 교체했다 | archive 판정은 되는데 실제 확장은 ZIP만 되던 불일치를 제거해서 `.ar` 같은 지원 가능한 archive도 실제로 탐색 가능하게 만들었다 | `app/src/main/java/com/kyhsgeekcode/Util.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/FileDrawerTree.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/FileDrawerListItem.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/FileDrawerListAdapter.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ArchiveExtractionTest.kt` | 완료 |
4041
| 프로젝트 경로/파일명 회귀 방지 | 프로젝트 상대경로 계산과 import 파일명 정규화를 pure helper로 분리 | 단위 테스트가 가능하도록 로직을 분리하고 경계 케이스를 줄였다 | `app/src/main/java/com/kyhsgeekcode/disassembler/project/ProjectManager.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/viewmodel/MainViewModel.kt` | 완료 |
4142
| 회귀 테스트 부재 | `ProjectManager`, 저장소 권한, Hex 레이아웃, import 파일명 테스트 추가 | 최소한의 유지보수 안전망을 확보했다 | `app/src/test/java/com/kyhsgeekcode/disassembler/ProjectManagerTest.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/PermissionUtilsTest.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ui/components/HexViewLayoutTest.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/viewmodel/ImportedFileNameTest.kt` | 완료 |
@@ -61,6 +62,7 @@
6162
| string search accumulator 테스트 | 통과 | 문자열 결과 상한과 truncation 동작을 고정 |
6263
| binary detail preview 테스트 | 통과 | 긴 detail 문자열은 preview만 렌더링하고 notice를 붙이는 규칙을 고정 |
6364
| expanded file cache key 테스트 | 통과 | 같은 파일 내용은 같은 streaming hash를 만들고, 전체 `readBytes()` 없이 key를 계산하는 규칙을 고정 |
65+
| hex preview 테스트 | 통과 | Hex 탭이 큰 파일 바이트 배열을 preview 상한으로 잘라 렌더링하는 규칙을 고정 |
6466
| architecture mapping 테스트 | 통과 | `x86_64`, `PPC64`가 64-bit mode로 매핑되는 규칙을 고정 |
6567
| binary manual setup reload 테스트 | 통과 | override autosetup 변경 시 disassembly 재로드 필요 여부를 고정 |
6668
| project export archive 테스트 | 통과 | ZIP 엔트리가 절대경로를 포함하지 않고 `sourceFilePath`, `baseFolder/...`로 묶이는지 확인 |

0 commit comments

Comments
 (0)