CocosBuilder Bug Fix: NSInvalidArgumentException 崩溃修复
日期: 2025-10-17 提交: bcaded65
问题描述
崩溃日志
应用在启动时崩溃,错误信息:
*** -[NSRegularExpression enumerateMatchesInString:options:range:usingBlock:]: nil argument
FAULT: NSInvalidArgumentException: *** -[NSRegularExpression enumerateMatchesInString:options:range:usingBlock:]: nil argument调用栈
0 CoreFoundation __exceptionPreprocess + 176
1 libobjc.A.dylib objc_exception_throw + 88
2 Foundation -[NSRegularExpression enumerateMatchesInString:options:range:usingBlock:] + 1700
3 Foundation -[NSRegularExpression firstMatchInString:options:range:] + 180
4 CocosBuilder -[CocosBuilderAppDelegate checkUpdate] + 624 ← 崩溃点
5 CocosBuilder -[CocosBuilderAppDelegate openProject:] + 1444
6 CocosBuilder __40-[CocosBuilderAppDelegate openDocument:]_block_invoke_2 + 128崩溃发生在:CocosBuilderAppDelegate.m:4897
根本原因分析
代码问题
位置: CocosBuilder/ccBuilder/CocosBuilderAppDelegate.m方法: - (void) checkUpdate行号: 4894-4897
问题代码:
// Line 4892: 检查文件是否存在
if (![[NSFileManager defaultManager] fileExistsAtPath:shellPath]) return;
// Line 4894: 读取文件内容
NSString* file = [NSString stringWithContentsOfFile:shellPath
encoding:NSUTF8StringEncoding
error:nil];
// Line 4897: 直接使用 file,未检查 nil
NSTextCheckingResult* match = [reg firstMatchInString:file
options:0
range:NSMakeRange(0, [file length])];为什么会出现 nil?
虽然代码在第 4892 行检查了文件是否存在,但 stringWithContentsOfFile: 仍然可能返回 nil:
- 权限问题: 文件存在但没有读取权限
- 编码问题: 文件内容无法用 UTF-8 解码
- I/O 错误: 磁盘错误或文件被锁定
- 文件被删除: 在检查存在性后、读取前被删除(竞态条件)
触发场景
在 ARM64 Mac 上运行时,当用户打开项目后触发:
openProject:被调用- 调用
checkUpdate检查插件更新 - 尝试读取
PlugIns/updateCocosBuilder.sh - 文件读取失败返回 nil
- 传递 nil 给
firstMatchInString:导致崩溃
修复方案
代码修改
文件: CocosBuilder/ccBuilder/CocosBuilderAppDelegate.m行号: 4895(新增)
修复后的代码:
// Line 4892: 检查文件是否存在
if (![[NSFileManager defaultManager] fileExistsAtPath:shellPath]) return;
// Line 4894: 读取文件内容
NSString* file = [NSString stringWithContentsOfFile:shellPath
encoding:NSUTF8StringEncoding
error:nil];
// Line 4895: ✅ 新增:检查文件内容是否为 nil
if (!file) return; // Guard against nil file content
// Line 4897-4899: 现在安全使用 file
NSRegularExpression *reg = [NSRegularExpression regularExpressionWithPattern:
@"#[^\\n\\r\\S]*version[^\\n\\r\\S]*:[^\\n\\r\\S]*(\\d+)"
options:NSRegularExpressionCaseInsensitive
error:nil];
NSTextCheckingResult* match = [reg firstMatchInString:file
options:0
range:NSMakeRange(0, [file length])];修复原理
添加了防御性编程的 nil 检查:
- 如果文件内容为 nil,提前返回
- 避免将 nil 传递给 NSRegularExpression
- 优雅地处理文件读取失败的情况
测试验证
构建测试
✅ 编译成功
cd CocosBuilder
xcodebuild -project CocosBuilder.xcodeproj \
-scheme CocosBuilder \
-configuration Debug \
ONLY_ACTIVE_ARCH=NO \
build结果:** BUILD SUCCEEDED **
验证架构
✅ Universal Binary 仍然正常
lipo -info ~/Library/Developer/Xcode/DerivedData/CocosBuilder-*/Build/Products/Debug/CocosBuilder.app/Contents/MacOS/CocosBuilder输出:Architectures in the fat file: x86_64 arm64
运行时测试建议
建议测试以下场景:
- [ ] 正常启动: 应用能正常启动不崩溃
- [ ] 打开项目: 打开包含 PlugIns 目录的项目
- [ ] 打开项目(无 PlugIns): 打开不含 updateCocosBuilder.sh 的项目
- [ ] 权限测试: 修改 updateCocosBuilder.sh 权限为不可读
影响范围
受影响功能
- 自动更新检查: 插件自动更新功能
- 影响范围: 仅在打开项目时触发
用户影响
- 之前: 应用在某些情况下启动崩溃
- 现在: 优雅处理文件读取失败,不再崩溃
功能行为
- ✅ 如果文件读取成功,正常检查更新
- ✅ 如果文件读取失败,静默跳过更新检查
- ✅ 不影响应用的其他功能
相关提交
Bug 修复提交
Commit: bcaded65标题: Fix NSInvalidArgumentException crash in checkUpdate method 文件: CocosBuilder/ccBuilder/CocosBuilderAppDelegate.m更改: +2 行(添加 nil 检查和注释)
相关提交
Commit: 3162420a标题: Enable Universal Binary support (x86_64 + ARM64) 说明: ARM64 迁移时可能触发了这个潜在的 bug
代码质量改进
防御性编程
这次修复体现了良好的防御性编程实践:
- 不信任外部数据: 文件读取可能失败
- 及早检查返回值: 在使用前验证 nil
- 优雅降级: 失败时提前返回,不影响其他功能
建议的后续改进
如果需要进一步改进,可以考虑:
NSError *error = nil;
NSString* file = [NSString stringWithContentsOfFile:shellPath
encoding:NSUTF8StringEncoding
error:&error];
if (!file) {
if (error) {
NSLog(@"Failed to read update script: %@", error);
}
return;
}这样可以记录具体的失败原因,便于调试。
经验教训
1. Objective-C API 的返回值
许多 Objective-C API 可能返回 nil,即使参数合法:
stringWithContentsOfFile:可能返回 nil- 即使文件存在,读取也可能失败
- 总是检查返回值
2. 竞态条件
文件系统操作存在竞态条件:
if (fileExists) { // Time of Check
readFile(); // Time of Use ← 文件可能已被删除
}解决方案: 检查操作结果而不是前置条件
3. ARM64 迁移的副作用
虽然这个 bug 与 ARM64 无关,但在测试 ARM64 版本时被触发。这提醒我们:
- 架构迁移可能暴露潜在的 bug
- 需要全面的回归测试
- 防御性编程很重要
总结
✅ Bug 已修复: 添加 nil 检查防止崩溃 ✅ 构建验证: 编译成功,Universal Binary 正常 ✅ 代码改进: 提高了代码的健壮性 ✅ 影响最小: 仅 2 行代码更改,风险极低
建议: 在实际环境中进行运行时测试,验证修复效果。