# TestPrintWidget 崩溃风险分析报告 ## 🔴 高风险(必定崩溃) ### 1. `saveFile()` — `m_printSetting` 空指针解引用 **文件**: [testprintwidget.cpp:435](../testprintwidget.cpp#L435) ```cpp PrintSettingModel::saveToXml(m_printSetting->model(), writer); ``` `m_printSetting` 在 `setPrintSettingWidget()` 调用前为 `nullptr`。如果用户在 `setPrintSettingWidget()` 被调用之前就触发了保存,程序会崩溃。虽然 `InkJetPrinter.cpp:104` 在构造时调用了它,但如果构造顺序出现问题或 `m_PrintSetting` 尚未初始化,就会崩溃。 ### 2. `saveFile()` — `qgraphicsitem_cast` 返回 null 后解引用 **文件**: [testprintwidget.cpp:406](../testprintwidget.cpp#L406) ```cpp PicTextItem *picText = qgraphicsitem_cast(item); writer.writeTextElement(..., QString::fromLocal8Bit("%1").arg(picText->pos().x())); ``` `qgraphicsitem_cast` 可能返回 null(当 item 不是 PicTextItem 类型时),紧接着调用 `picText->pos().x()` 就会崩溃。 ### 3. `onFileOpen()` — `sysPara->devOp` 空指针解引用 **文件**: [testprintwidget.cpp:477](../testprintwidget.cpp#L477) ```cpp sysPara->devOp->fpgaWr(0xa4b0, (unsigned char *)&SpecialVarEnable, 1); ``` - `sysPara` 可能为 null(`bindSysPara()` 未被调用时) - `sysPara->devOp` 也可能为 null ### 4. `on_newfileButton_pressed()` — `m_printSetting` 空指针解引用 **文件**: [testprintwidget.cpp:1122](../testprintwidget.cpp#L1122) ```cpp m_printSetting->setModel(defModel); ``` 同样的问题,`m_printSetting` 可能为 null。 ### 5. `getShowSelectItemText()` — `dynamic_cast` 后无空检查 **文件**: [testprintwidget.cpp:312-317](../testprintwidget.cpp#L312-L317) ```cpp PicTextItem* txtItem = dynamic_cast(item); if(txtItem->sourceType() != SourceType::IMAGE && txtItem->sourceType() != SourceType::CODE ...) { lastSelectedTxtItem = txtItem; } ``` 如果场景中包含非 `PicTextItem` 派生类的 `QGraphicsItem`,`dynamic_cast` 返回 null,直接解引用 `txtItem->sourceType()` 崩溃。 ### 6. `onBtnItemConfigClicked()` — `dynamic_cast` 后无空检查 **文件**: [testprintwidget.cpp:1042-1043](../testprintwidget.cpp#L1042-L1043) ```cpp auto item = dynamic_cast(selectedItems[0]); if(item->sourceType() == SourceType::COUNTER) { ``` 如果选中的 item 不是 `PicTextItem` 类型,`dynamic_cast` 返回 null,直接崩溃。 ### 7. `addDateTimeItem()` / `addScheduleShiftsItem()` — `dynamic_cast` 后无空检查 **文件**: [testprintwidget.cpp:1310-1312](../testprintwidget.cpp#L1310-L1312) ```cpp auto item = dynamic_cast(selectItems[0]); if(item->sourceType() == SourceType::DATETIME) { ``` 同样的问题,`testprintwidget.cpp:1338` 也存在。 ### 8. `onEditItemDone()` — C风格强转,无空检查 **文件**: [testprintwidget.cpp:287](../testprintwidget.cpp#L287) ```cpp PicTextItem *pTextItem = (PicTextItem *)item; if(pTextItem->sourceType() == SourceType::TEXT) pTextItem->setSource(content); ``` C风格强转不做任何类型检查,如果 item 不是 PicTextItem,访问 `sourceType()` 是未定义行为。 ### 9. `addCounterItem()` — `dynamic_cast` 后无空检查 **文件**: [testprintwidget.cpp:1380](../testprintwidget.cpp#L1380) ```cpp auto item = dynamic_cast(items[index]); font = f ? *f : item->font(); ``` 如果选中的 item 不是 PicTextItem,`item->font()` 崩溃。 --- ## 🟡 中风险(特定条件下崩溃) ### 10. `onItemEditorAccepted()` — `currentItem()` 可能返回 null **文件**: [testprintwidget.cpp:1095](../testprintwidget.cpp#L1095) ```cpp if(itemEditor->currentItem()->source() == SourceType::UNKNOWN) { ``` `currentItem()` 可能返回 null 导致崩溃。 ### 11. `parseVbfile()` 加载 XML 时 `m_printSetting` 空指针 **文件**: [testprintwidget.cpp:621-622](../testprintwidget.cpp#L621-L622) ```cpp m_printSetting->setModel(model); m_printSetting->applyPrintSettingsToSysPara(false); ``` 当加载带有 PrintSettings 的 XML 文件时,`loadPrintSetting` 为 true 才会执行,但如果 `m_printSetting` 为 null,会崩溃。 ### 12. `on_cbResolution_currentIndexChanged()` — 数组越界后的修复逻辑不完整 **文件**: [testprintwidget.cpp:1274-1280](../testprintwidget.cpp#L1274-L1280) ```cpp if(index < 0 || int(ARRAY_SIZE(IdxToFontFamily)) <= index) { index = std::max(std::min(int(ARRAY_SIZE(IdxToFontFamily) - 1), index), 0); ... } auto selectedItems = pPicScene->selectedItems(); ... qInfo() << "Selected font family: " << IdxToFontFamily[index]; ``` 虽然有边界修正,但 `IdxToFontFamily` 是一个局部 `constexpr` 数组,如果 `ARRAY_SIZE` 宏未正确定义,可能导致越界访问。 --- ## 🟢 低风险(逻辑问题,不太可能崩溃) ### 13. `pasteCopiedLayer()` — 临时对象传入 nullptr ```cpp PicTextItem tempSrc(nullptr, nullptr, m_copiedSourceType); ``` 用 `nullptr` 作为 parent 和 scene 创建临时对象,仅用于类型判断。如果 PicTextItem 构造函数中对 nullptr 做了安全处理,则没问题。 ### 14. `onSceneFocusedItemPosChanged()` — `QString::asprintf` 格式字符串不安全 **文件**: [testprintwidget.cpp:1112-1113](../testprintwidget.cpp#L1112-L1113) ```cpp ui.m_lblX->setText(QString::asprintf("X:%d", p.x())); ui.m_lblY->setText(QString::asprintf("Y:%d", p.y())); ``` 使用 `asprintf` 而非 `QString::number()`,虽然不会崩溃,但不是最佳实践。 --- ## 📋 修复建议汇总 | 优先级 | 位置 | 问题 | 修复方案 | |--------|------|------|----------| | **P0** | `saveFile()` L435 | `m_printSetting` 空指针 | 加 `if(m_printSetting)` 判断 | | **P0** | `saveFile()` L406 | `qgraphicsitem_cast` 返回 null | 加 null 检查,跳过非 PicTextItem | | **P0** | `onFileOpen()` L477 | `sysPara->devOp` 空指针 | 加 `if(sysPara && sysPara->devOp)` | | **P0** | `getShowSelectItemText()` L312 | `dynamic_cast` 无空检查 | 加 `if(txtItem)` | | **P0** | `onBtnItemConfigClicked()` L1042 | `dynamic_cast` 无空检查 | 加 `if(item)` | | **P0** | `addDateTimeItem()` L1310 | `dynamic_cast` 无空检查 | 加 `if(item)` | | **P0** | `addScheduleShiftsItem()` L1338 | `dynamic_cast` 无空检查 | 加 `if(item)` | | **P0** | `onEditItemDone()` L287 | C风格强转无检查 | 改用 `dynamic_cast` + null 检查 | | **P0** | `addCounterItem()` L1380 | `dynamic_cast` 无空检查 | 加 `if(item)` | | **P0** | `on_newfileButton_pressed()` L1122 | `m_printSetting` 空指针 | 加 `if(m_printSetting)` | | **P1** | `parseVbfile()` L621 | `m_printSetting` 空指针 | 加 `if(m_printSetting)` | | **P1** | `onItemEditorAccepted()` L1095 | `currentItem()` 可能 null | 加 null 检查 | --- ## 生成时间 2026-06-29