- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다 - 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5 (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를 techviz 로 만들었다 - 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs 돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다. Concept 이 인용한 코드가 SSOT 에 없어 뺐다 - candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
137 lines
11 KiB
Markdown
137 lines
11 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: a14-f002-webproblemsanitizer-alreadysafe
|
|
title: 호출자 없이 들어온 메서드이고, 도달 불가라던 가지는 켜지면 반대로 답한다
|
|
topic: web-inbound-and-http-surface
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: case:a14-f002-webproblemsanitizer-alreadysafe
|
|
evidenceCapturedOn: 2026-09-02
|
|
body: case-a14-f002-webproblemsanitizer-alreadysafe.body.md
|
|
assets:
|
|
- key: a14-f002-webproblemsanitizer-alreadysafe
|
|
file: ../../../final/evidence/rendered/a14-f002-webproblemsanitizer-alreadysafe.svg
|
|
- key: a14-f002-webproblemsanitizer-alreadysafe-branch
|
|
file: ../../../final/evidence/rendered/a14-f002-webproblemsanitizer-alreadysafe-branch.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/a14-f002-webproblemsanitizer-alreadysafe.txt
|
|
- ../../../final/evidence/raw/a14-f002-webproblemsanitizer-alreadysafe-branch.txt
|
|
source:
|
|
- 원본 분석 절은 final/document.md#a14#L380 이다.
|
|
---
|
|
|
|
# 호출자 없이 들어온 메서드이고, 도달 불가라던 가지는 켜지면 반대로 답한다
|
|
|
|
메시지가 이미 안전한지 단언하는 메서드다. 이름이 저장소에 딱 한 번, 자기 선언에만 나온다. 원문은 그 안의 삼항 한 가지도 도달 불가라고 적었는데, 실제로는 코드 점 스물셋이 그 가지를 타고 스물셋 다 안전하다는 답을 받는다.
|
|
|
|
## 관계
|
|
|
|
- **adapter까지 있고 호출자가 없는 교정 경로**
|
|
만들어 두고 아무도 부르지 않는다는 점이 같다.
|
|
- **소비자가 없는 fixture 셋**
|
|
이쪽도 호출자 수를 세어 프로덕션에서 쓰이지 않는다는 것을 확인했다.
|
|
- **runtime_memberships를 먼저 읽고 심각도를 정한다**
|
|
이 사례의 완화가 조립 부재가 아니라는 것을 그 규칙으로 판정했다.
|
|
|
|
## 문제
|
|
|
|
문제 응답 정제기에 안전 확인 메서드가 있고 자바독이 용도를 적는다. 그 용도가 수행되는지, 그리고 안쪽 조건이 정말 죽었는지 확인했다.
|
|
|
|
## 결론
|
|
|
|
호출자가 없다. 저장소가 추적하는 모든 파일에서 이 이름이 잡히는 자리가 선언 하나다.
|
|
|
|
쓰이다 끊긴 것도 아니다. 모든 참조를 뒤져도 이 이름을 건드린 커밋이 하나이고, 그 커밋이 이 파일을 새로 더한다.
|
|
|
|
안쪽 가지는 다르다. 원문은 앞선 검사가 공백만인 입력을 걸렀으니 다듬은 문자열이 빌 수 없고 따라서 삼항의 편집됨 가지는 도달하지 않는다고 적었다.
|
|
|
|
그 논리가 두 판정을 하나로 본다. 공백이냐를 묻는 쪽과 앞뒤를 떼어 내는 쪽이 서로 다른 집합을 쓰고, 그 차집합이 비어 있지 않다.
|
|
|
|
그 조각을 세어 봤다. 기본 다국어 평면 전체에서 스물셋이고, U+0000 부터 U+0008 까지와 U+000E 부터 U+001B 까지 두 구간이다. 널 문자와 백스페이스와 탈출 문자는 그중 셋일 뿐이다.
|
|
|
|
스물셋 모두 참을 받는다. 정제기와 삼항이 같은 편집 문구로 수렴하기 때문이다. 제어 문자 하나짜리 텍스트를 이미 안전하다고 단언하는 셈이다.
|
|
|
|
반대 방향도 나왔다. 삭제 문자는 다듬기가 떼지 않으므로 거짓이 되고, 붙임 공백 한 글자는 어느 규칙에도 걸리지 않아 참이 된다. 다만 그 글자를 상한보다 길게 늘이면 거짓으로 뒤집힌다. 정제기가 상한에서 잘라 낸 앞부분과 삼항이 고른 원문이 달라지기 때문이다.
|
|
|
|
그래서 원문이 적은 것과 다른 결함이 된다. 이 분기는 도달할 수 없는 코드가 아니라 실제로 실행되는 코드이고, 실행되면 원문이 예상한 것과 반대 값을 돌려준다.
|
|
|
|
판정은 P3 다. 호출자가 없으므로 오늘 그 답을 소비하는 코드가 없다.
|
|
|
|
다만 완화의 성격이 배선 부재는 아니다. 모듈 레지스트리에서 이 리프의 런타임 소속을 읽으면 둘이다. 남는 완화는 호출자가 없다는 것 하나뿐이고, 그것은 한 줄이면 사라진다. 그 상태를 붙들어 두는 장치도 없다. 이 리프가 선언한 구조 규칙은 여덟이고 두 목록에 담겨 실제로 도는 것은 일곱이다. 그것들이 보는 것은 컨트롤러와 트랜잭션과 와이어 모델과 엔벌로프와 의존 방향이고, 미호출 메서드를 보는 것은 없다. 목록에 담기지 않은 여덟 번째 자신도 호출자가 없다. 다만 그 여덟 번째 규칙의 자바독은 임포트가 놀지 않게 두는 표식이라고 스스로 밝힌다. 이 사례의 메서드는 하지 않는 일을 하겠다고 적어 두었다는 점이 다르다. 공개 표면 기준선도 이 리프에는 적용되지 않는다.
|
|
|
|
## 검증 환경
|
|
|
|
OpenJDK : 21.0.12
|
|
확인 방식 : 추적 파일 전수 검색과 모든 참조의 이력 확인, 기본 다국어 평면 전체 훑기, 구조 규칙 목록 확인
|
|
소스 수정 : x
|
|
|
|
## 재현 조건
|
|
|
|
이 사례의 프로브는 새로 만든 것이고 원문에는 없다. 원문 근거는 분석 문서의 #L380 절이다.
|
|
|
|
1. 메서드와 그 자바독을 읽는다.
|
|
2. 그 이름을 추적 파일 전부에서 대소문자 없이 찾는다.
|
|
3. 모든 참조를 대상으로 그 이름을 건드린 커밋을 찾고, 그 커밋에서 파일 상태를 본다.
|
|
4. 앞선 검사와 다듬기가 각각 어떤 판정을 쓰는지 읽는다.
|
|
5. 두 판정이 갈리는 코드 점을 기본 다국어 평면 전체에서 센다.
|
|
6. 그중 메서드가 참을 돌려주는 것이 몇 개인지 함께 센다.
|
|
7. 모듈 레지스트리에서 이 리프의 런타임 소속을 읽고, 이 리프의 구조 규칙이 무엇을 보는지 나열한다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
자바독이 용도를 한 줄로 적는다.
|
|
|
|
```java
|
|
// WebProblemSanitizer.java:78-84
|
|
/** Whether text would survive sanitising unchanged, for asserting a message is already safe. */
|
|
public boolean alreadySafe(String input, int maxLength) {
|
|
return input != null
|
|
&& !input.isBlank()
|
|
&& sanitize(input, maxLength)
|
|
.equals(input.trim().toLowerCase(Locale.ROOT).isEmpty() ? REDACTED : input.trim());
|
|
}
|
|
```
|
|
|
|
## 부르는 곳이 없고, 있었던 적도 없다
|
|
|
|
:::evidence key="a14-f002-webproblemsanitizer-alreadysafe" alt="코드베이스 정적 검색 출력 51줄. 메서드와 자바독, 추적 파일 전부에서 그 이름을 찾은 결과 한 줄, 모든 참조에서 그 이름을 건드린 커밋과 그 커밋에서의 파일 상태, 앞선 검사와 다듬기가 쓰는 판정 줄, 이 리프의 런타임 소속, 선언된 구조 규칙 여덟과 그중 두 목록에 담겨 실제로 도는 일곱, 공개 표면 기준선 적용 여부가 차례로 보인다." caption="이름이 나오는 자리와 그 이력, 리프의 소속과 구조 규칙 — 51줄 · exit 0" zoom="true"
|
|
:::
|
|
|
|
`git grep -i` 는 확장자를 가리지 않는다. 그런데도 잡히는 줄이 `WebProblemSanitizer:79` 하나다. `git log --all -S` 도 커밋을 하나만 내고, 그 커밋에서 이 파일의 상태가 `A` 다. 호출자를 잃은 것이 아니라 없이 들어왔다.
|
|
|
|
## 도달 불가라는 근거가 성립하지 않는다
|
|
|
|
원문은 `!input.isBlank()` 가 통과했으니 `input.trim()` 이 빌 수 없고, 따라서 삼항의 `REDACTED` 가지는 도달 불가라고 적는다. 그 추론에는 두 판정이 같은 것으로 놓여 있다.
|
|
|
|
`String.isBlank()` 는 `Character.isWhitespace` 를 쓴다. `String.trim()` 이 떼어 내는 것은 문자열 양끝의 U+0020 이하다. 한쪽이 공백으로 세지 않는 문자를 다른 쪽이 지우면 앞선 검사를 통과한 입력의 다듬은 값이 빌 수 있다.
|
|
|
|
## 갈리는 코드 점을 전부 셌다
|
|
|
|
:::evidence key="a14-f002-webproblemsanitizer-alreadysafe-branch" alt="JVM 프로브 출력 18줄. 열 가지 입력마다 isBlank 결과와 다듬은 문자열이 비는지와 삼항이 타는 가지와 alreadySafe 의 반환을 한 줄에 적고, 이어서 기본 다국어 평면 전체에서 두 판정이 갈리는 코드 점의 수와 구간과 그중 참을 받는 수를 적었다." caption="열 입력의 가지와 갈리는 코드 점 전수 — 18줄 · exit 0" zoom="true"
|
|
:::
|
|
|
|
앞 표가 예시 열 개다. 널 문자 하나, 널 문자 셋, 백스페이스, 탈출 문자가 그 가지를 타고 넷 다 참을 받는다. `sanitize` 도 그 입력에서는 `REDACTED` 를 돌려주므로 등호가 성립한다.
|
|
|
|
예시로는 범위를 말할 수 없어 뒤에서 기본 다국어 평면 전체를 훑었다. 갈리는 코드 점이 스물셋이고 `U+0000..U+0008` 과 `U+000E..U+001B` 두 구간이다. `U+0020` 이하는 서른셋인데 그중 열이 빠진다. `U+0009..U+000D` 와 `U+001C..U+0020` 이 자바에서는 공백이라 앞선 검사에 걸리기 때문이다. 가운데가 끊기는 이유도, 두 번째 구간이 `U+001F` 가 아니라 `U+001B` 에서 멈추는 이유도 그것이다. 정보 구분자 넷이 공백으로 세어진다는 것이 위쪽 경계를 정한다.
|
|
|
|
스물셋 가운데 참을 받는 것이 스물셋이다. 예시 넷은 대표값이고 방향이 갈리는 코드 점은 없다.
|
|
|
|
## 경계 두 개가 더 있다
|
|
|
|
`U+007F` 는 U+0020 보다 크므로 다듬기가 떼지 않는다. 삼항은 원문 가지를 고르고 `sanitize` 는 `CONTROL` 규칙으로 그것을 지우므로 두 값이 갈려 거짓이 된다.
|
|
|
|
`U+00A0` 은 `Character.isWhitespace` 가 거짓이고 다듬기도 떼지 않는다. `REMOVALS` 의 여섯과 `CONTROL`, `WHITESPACE` 어느 것에도 걸리지 않아 입력이 그대로 살아남고 참이 된다. 눈에 보이지 않는 한 글자가 이미 안전한 메시지로 통과한다.
|
|
|
|
길이는 별개다. 같은 글자를 `maxLength` 보다 길게 늘이면 `:70-74` 의 잘림이 개입해 거짓이 된다.
|
|
|
|
정제기에서 값을 바꾸는 것은 여덟 규칙만이 아니다. 규칙을 적용한 뒤 `:66` 이 한 번 더 다듬고, 빈 값은 `:58-60`·`:67-69`·`:75` 에서 편집 문구로 바뀌며, 상한을 넘으면 `:70-74` 가 잘라 낸다. 제어 문자 스물셋에는 앞의 둘이 이어서 걸린다. `CONTROL` 이 그 글자를 공백 하나로 바꾸고, `:66` 이 그것을 다듬어 값을 비우고, 그제서야 `:67-69` 가 편집 문구를 돌려준다. `:66` 을 빼면 값이 공백 하나로 남아 스물셋이 전부 거짓이 된다. 앞뒤 공백이 참을 받는 것은 첫째 장치 하나 때문이고, 길이가 걸리는 것은 마지막 장치다.
|
|
|
|
## 확인하지 못한 것
|
|
|
|
보충 평면은 훑지 않았다. 자바의 문자 하나가 담지 못하는 값이라 이 판정에 걸릴 수 없다고 보았을 뿐 세어 보지는 않았다. 정제기의 다른 메서드에도 같은 판정 불일치가 있는지도 세지 않았다.
|
|
|
|
<!-- body:end -->
|