diff --git a/.jules/bolt.md b/.jules/bolt.md index 19b4c613..c50f975d 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -43,3 +43,7 @@ ## 2025-01-24 - 단일 readAttributes 호출로 파일 속성 조회 최적화 **학습:** `isDirectory`, `!it.isDirectory()`, `isSymbolicLink` 3개의 개별적인 파일 시스템 I/O 호출을 수행하면 성능 저하가 큽니다. 이를 단일 `Files.readAttributes` 호출로 변경하여 메타데이터를 한 번에 조회함으로써 I/O 오버헤드를 대폭 줄일 수 있음을 확인했습니다. **조치:** 디렉토리 순회 시 파일의 여러 속성을 확인할 때는 개별적인 stat 호출보다 `Files.readAttributes`를 사용하여 필요한 모든 속성을 한 번에 가져오는 방식을 우선적으로 고려해야 합니다. + +## 2024-07-13 - [디렉토리 순회 및 I/O 호출 성능 최적화] +**Learning:** 디렉토리 순회 시 `File.list()`나 `File.listFiles()`를 반복 호출하면 불필요한 시스템 I/O 오버헤드가 크게 발생합니다. 또한 `.toPath()` 같은 객체를 반복문 내부에서 조건 검사 시 매번 생성하는 것은 GC 오버헤드를 유발합니다. +**Action:** `dirFilesNames ?: curr_dir.list()` 호출 결과를 변수에 캐싱하여 재사용하고, 반환된 배열이나 콜렉션을 순회할 때는 `Path` 객체 등의 인스턴스를 루프 밖이나 루프 최상단에서 한 번만 할당(`val path = it.toPath()`)하도록 최적화해야 합니다. 특히 패턴 매칭이나 필터링 작업이 비어있을 때는 조기에 종료(Short-circuit)하여 불필요한 반복문을 피합니다. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index b4558624..8984d479 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -178,6 +178,9 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S val files_to_exclude = mutableSetOf() + // ⚡ Bolt Performance Optimization: Cache dirFilesNames to avoid redundant I/O calls + val cachedDirFilesNames = dirFilesNames ?: curr_dir.list() + // 보안 향상: .html4ignore 파일이 일반 파일인지 확인하고, 심볼릭 링크인 경우 무시하여 DoS 및 경로 조작을 방지합니다. // 보안 향상: 파일 크기(1MB 제한) 및 줄 수(1000줄), 정규식 길이(100자)를 제한하여 ReDoS 및 메모리 고갈(OOM) 방지 // 보안 향상: 권한이 없는 파일 접근 시 발생하는 예외(DoS)를 방지하기 위해 canRead() 추가 확인 @@ -198,16 +201,18 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S } } - // ⚡ Bolt Performance Optimization: 디렉토리 목록을 Set에 추가하기 위해 필터링만 할 때는 정렬이 불필요하므로 .sorted()를 제거하여 O(N log N) 오버헤드를 방지합니다. - val list = dirFilesNames ?: curr_dir.list() - list?.forEach { - val current = it - val pathCurrent = java.nio.file.Paths.get(current) - for (matcher in ignored_matchers) { - if (matcher.matches(pathCurrent)) { - files_to_exclude.add(current) - break - } + // ⚡ Bolt Performance Optimization: Avoid iteration if there are no valid patterns + if (ignored_matchers.isNotEmpty()) { + // ⚡ Bolt Performance Optimization: 디렉토리 목록을 Set에 추가하기 위해 필터링만 할 때는 정렬이 불필요하므로 .sorted()를 제거하여 O(N log N) 오버헤드를 방지합니다. + cachedDirFilesNames?.forEach { + val current = it + val pathCurrent = java.nio.file.Paths.get(current) + for (matcher in ignored_matchers) { + if (matcher.matches(pathCurrent)) { + files_to_exclude.add(current) + break + } + } } } } @@ -220,7 +225,8 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S files_to_exclude.addAll(defaultSensitiveFiles) // 보안 향상: .env, .git 등 민감한 정보가 포함될 수 있는 숨김 파일(.으로 시작하는 모든 항목)을 기본적으로 노출하지 않도록 제외 (정보 노출 방지) - (dirFilesNames ?: curr_dir.list())?.forEach { + // ⚡ Bolt Performance Optimization: Iterate over cached directory files instead of querying I/O + cachedDirFilesNames?.forEach { if (it.startsWith(".")) { files_to_exclude.add(it) } diff --git a/src/test/kotlin/html4tree/CoverageTest.kt b/src/test/kotlin/html4tree/CoverageTest.kt index dccf3046..250597a5 100644 --- a/src/test/kotlin/html4tree/CoverageTest.kt +++ b/src/test/kotlin/html4tree/CoverageTest.kt @@ -19,4 +19,14 @@ class CoverageTest { readOnlyDir.setWritable(true, false) } } + + @Test + fun testProcessIgnoreFileEmptyMatchers() { + val tempDir = java.nio.file.Files.createTempDirectory("test_empty_matchers").toFile() + val ignoreFile = File(tempDir, ".html4ignore") + ignoreFile.writeText("\n \n") // Empty or blank lines to trigger empty matchers + val excluded = process_ignore_file(tempDir, null) + assertTrue(excluded.contains("index.html")) + tempDir.deleteRecursively() + } }