LGES-OC5-PtoP-Welder/Doc/인수인계/260907_NRE_크래시_위험구간_점검.md
NohDoyeon241104 a3238cc2d6 docs: 인수인계 산출물 일괄 커밋 (260903~260916)
그동안 미추적으로 쌓여 있던 인수인계 문서를 정리해 올린다.

260903 Door 인터락 현장검증 가이드 / 파워캘 3000W 오적용 환산

260907 Arc 출사 파라미터 시각화 / LaserFrequency 단위계통 / NRE 위험구간 점검 / 레시피화면 사용설명서

260915 레시피화면 작업매뉴얼

260916 PTOP 프로그램 사용설명서 / 자동운전 전환 사전점검 현장용

작업 중 임시본(_backup)은 제외했다.
2026-09-16 17:10:50 +09:00

159 lines
7.9 KiB
Markdown

# NRE / 크래시 위험 구간 점검
- 작성 : 2026-09-07
- 계기 : 출사 미지원 객체(Ellipse/Rectangle/Roi) NRE 방어(`ISSUE-260907-01`) 적용 후,
같은 유형이 더 있는지 전수 확인 요청.
- 성격 : **점검 목록 전용. 소스 미수정. 현장 검증 미실시.**
- 표기 : 🟩 소스 확인 / 🟦 현장 기록 / 🟨 미확보·판정 불가
## 점검 범위와 한계
| 구분 | 내용 |
|---|---|
| 포함 | `Machines/ILSOS_PrismaticRD_PTOP/**`, `WeldingComponents/WeldingCommon/Sequences/**` |
| 제외 | AuroraVision, LASA GUI(Canvas·DrawTools), AuroraDevices 하위 대부분, Aurora 프레임워크 |
| 방법 | 패턴 검색(`FirstOrDefault`/`First()`/`[0]`/`as`/인덱서) 후 해당 라인 전후 확인 |
| 한계 | 정적 분석. 런타임 재현·디버깅 없음. 제외 범위에 같은 유형이 더 있을 수 있음 |
---
## 0. 선행 확인 — 예외가 실제로 프로그램을 종료시키는가
🟩 우리 소스에 **전역 예외 핸들러가 없습니다.**
`DispatcherUnhandledException` / `AppDomain.CurrentDomain.UnhandledException` /
`UnobservedTaskException` / `SetUnhandledExceptionMode` 검색 결과 **0건**.
🟩 `ILSOS_PrismaticRD_PTOP.csproj` → `OutputType=library`.
즉 실행 주체는 **Aurora.exe** 이고 저장소에 소스가 없습니다.
🟨 따라서 아래 항목이 **실제로 프로세스를 종료시키는지는 판정할 수 없습니다.**
- Aurora.exe 가 전역 핸들러를 걸어 두었는지 미확인
- Aurora `SequenceBuilder` 가 노드 실행을 try/catch 로 감싸는지 미확인
(`sb.OnForceError(...)` 훅이 존재하므로 **잡을 가능성은 있음**)
> **이 문서는 "NRE 가 발생할 수 있는 지점" 목록입니다.**
> "여기서 프로그램이 죽는다" 로 읽지 마십시오. 종료 여부는 별도 확인 필요.
---
## A. 널 체크 없이 즉시 역참조 — 위험도 최상
### A-1. `SeqComponentTeachingRun.cs:456-459` 🟩
```csharp
var power = Status.GetProcessingWeldingData().UploadProcessDatas
.Where(data => data.DataName == $"Power_{SlotNo}_{_SequenceSetting.LastPageNo}")
.FirstOrDefault();
power.Value = ScannerCanvasUserCtrl.ScannerParams[paramIndex].LaserPower1
+ ScannerCanvasUserCtrl.ScannerParams[paramIndex].LaserPower2; // ← power == null 이면 NRE
var velocity = ...Where(data => data.DataName == $"Velocity_{SlotNo}_{...}").FirstOrDefault();
velocity.Value = ScannerCanvasUserCtrl.ScannerParams[paramIndex].MarkSpeed; // ← 동일
```
- `DataName` 을 `SlotNo` + `LastPageNo` 로 **문자열 조합**해 찾습니다.
- 그 조합이 `UploadProcessDatas` 에 없으면 `FirstOrDefault()` 가 `null` 을 반환합니다.
- **페이지 수가 늘거나 슬롯 번호 체계가 바뀌면 즉시 발생**합니다.
- 실제 운전 경로(Manual 티칭런)라 재현 가능성이 목록 중 가장 높습니다.
### A-2. `SeqEventQTest.cs:142-145` 🟩 — 3중 위험
```csharp
FovInfo _fovInfo = VisionRecipeManager.GetFovInfos().FirstOrDefault();
_PatternAgent = FovPatternAgentManager.GetAgent(_fovInfo.ToSubSysInfo()); // ① _fovInfo null → NRE
List<PatternInfo> patterns = _PatternAgent.GetMainPatterns(_fovInfo); // ② agent null → NRE
_PatternInfo = patterns[0]; // ③ 빈 리스트 → IndexOutOfRange
```
### A-3. `SeqAutoRunPrismatic.cs:1101-1104` 🟩
A-2 와 동일 패턴 + `_Patterns[_InitParam.VisionRecipeNo]` 인덱스 무검증.
> 🟩 **참고 — 올바른 패턴이 같은 파일에 이미 있습니다.**
> `SeqAutoRunPrismatic.cs:743-745` 는 `_PatternResult?.Results?.FirstOrDefault()` 로
> 널 조건 연산자를 쓰고 이후 `if (baseResult != null)` 까지 확인합니다.
> A-2 · A-3 만 이 방어가 빠져 있습니다.
---
## B. `as` 캐스팅 결과를 가드 없이 보관 — 지연 발화형
캐스팅 실패 또는 키 부재 시 조용히 `null` 이 되고, **한참 뒤 그 필드를 사용하는 시점**에 터집니다.
발생 위치와 원인 위치가 달라 추적이 어렵습니다.
| 위치 | 대상 | 🟩 |
|---|---|---|
| `SeqAutoRunPTOP.cs:291` | `_SystemParameter` | 🟩 |
| `Views/Main/UCMainAutoRunViewModel.cs:95-97` | `_SystemParameter`, `_Scanner`, `_Plc` | 🟩 |
| `Views/Main/UCMainAutoRunViewModel.cs:472, 505` | `_SystemParameter` 재대입 | 🟩 |
| `SeqTestRun.cs:472` | `_IOLaser` | 🟩 |
| `SeqComponentTeachingRun.cs:179` | `_SequenceSetting` | 🟩 |
| `Monitoring/SeqStatusMonitor.cs:60` | `_SequenceSetting` | 🟩 |
| `SeqAutoRunPrismatic.cs:1050` | `SystemSetting` | 🟩 |
| `EquipmentSetup.cs:90, 199, 240, 342` | `EquipmentInfo` | 🟩 |
| `EquipmentSetup.cs:129, 133` | `IODevice`, `PLCControllerPTOP` | 🟩 |
| `Recipes/RMSHandlerForPTOP.cs:52-53` | `_plc`, `_RmsSetting` | 🟩 |
대부분 **초기화 구간**이라 정상 구성에서는 성공합니다.
위험은 "설정 키 이름 변경 / 디바이스 등록 누락 / 타입 교체" 시 **즉시 실패하지 않는다**는 점입니다.
🟩 **예외 — 이미 방어된 곳** : `SeqAutoRunPTOP.cs:1140, 1252` 의 `LaserInterlockSetting` 은
`LaserAngleOffsetSelector.Select()` 안에서 `setting != null` 삼항 처리로 방어됩니다.
---
## C. 인덱스 무검증 — NRE 는 아니나 동일하게 종료 요인
| 위치 | 위험 | 🟩 |
|---|---|---|
| `SeqUploadWeldingList.cs:206` | `SelectedDrawObjectList[0]` — 빈 리스트 | 🟩 |
| `SeqUploadWeldingList.cs:207-209` | `ScannerParams[ScannerParamID - 1]` — **ID 가 0 이면 인덱스 -1** | 🟩 |
| `SeqUploadWeldingList.cs:474, 617-631, 719, 1006, 1052, 1152, 1204, 1404, 1605` | `ScannerParams[obj.ScannerParamNo]` 범위 무검증 (배열은 128개 고정 생성) | 🟩 |
| `SeqWaitWeldOrChangePage.cs:194-195` | `ScannerParams[paramIndex]` | 🟩 |
| `SeqTestRun.cs:237-238` | `ScannerParams[paramIndex]` | 🟩 |
| `SeqComponentTeachingRun.cs:457-465` | `ScannerParams[paramIndex]` | 🟩 |
| `SeqAutoRunPrismatic.cs:1104` | `_Patterns[VisionRecipeNo]` | 🟩 |
**`SeqUploadWeldingList.cs:206-209` 주의** — 2026-09-07 적용한 예외처리는
`FuncLoadDrawObjectScannerParamSelected()` 쪽이라 **이 경로는 여전히 무방비**입니다.
---
## D. 예외는 안 나지만 값이 오염되는 것 (참고)
double 나눗셈이라 예외 대신 `Infinity`/`NaN` 이 조용히 흘러갑니다.
| 위치 | 내용 |
|---|---|
| `ScannerRTC6.cs:775` · `ScannerRTC5.cs:773` | `32.0 / periodHz` — `periodHz == 0` 이면 `Infinity` → `(uint)` 캐스팅은 정의되지 않은 동작 |
| `SeqUploadWeldingList.cs:1313-1316` | `ArcRampingInterval == 0` 시 동일 |
상세 : [`260907_LaserFrequency_단위계통_분석.md`](260907_LaserFrequency_단위계통_분석.md)
---
## 우선순위 제안
| 순위 | 항목 | 근거 |
|---|---|---|
| 1 | **A-1** | 실제 운전 경로(Manual 티칭런) + 조합 문자열 탐색이라 재현 가능성 최상 |
| 2 | **C 의 `SeqUploadWeldingList.cs:206-209`** | 이번 예외처리가 커버하지 못한 인접 경로 |
| 3 | **A-2 / A-3** | 비전 경로. Vision 미사용이면 후순위 |
| 4 | **B** | 개별 수정보다 초기화 구간 일괄 널 가드 + 알람이 적절 |
| — | **선행** | Aurora.exe / SequenceBuilder 의 예외 처리 정책 확인. 이게 정해져야 "가드 후 어떻게 할지"(스킵 / 알람 / 시퀀스 중단)가 결정됨 |
## 조치 시 주의
- **조용히 스킵하면 안 되는 경우가 있습니다.** A-1 은 공정 데이터 상신 실패를 뜻하므로,
널이면 로그만 남기고 넘어가면 **데이터 누락이 은폐**됩니다. 알람 여부를 함께 정해야 합니다.
- 반대로 이번 `ISSUE-260907-01` 처럼 **원래도 동작하지 않던 경로**는 스킵이 맞습니다.
- 즉 "널이면 무조건 return" 을 일괄 적용하지 마십시오. 항목마다 판단이 다릅니다.
---
## 변경 이력
| 일자 | 내용 | 비고 |
|---|---|---|
| 2026-09-07 | 최초 작성. PTOP + WeldingCommon Sequences 범위 점검 | 소스 미수정 |