3.8.7构建后,安卓包和网页包对同一段代码执行出相反的结果。

  • Creator 版本: 3.8.7

  • 目标平台: web 和原生(安卓、IOS)

  • 重现方式:

Cocos Creator 3.8.7 release 构建 minify 错误

严重性:高。仅 release 包复现,无报错、无日志,条件静默反转。

环境:Cocos Creator 3.8.7 / Windows / android(windows 发布包同样复现)/ debug:false / mangleProperties:false / polyfills.targets: "chrome 80" / 脚本加密开启。

现象

export function repro(p: { findOnMoving?: boolean }, o: { findOnMoving?: boolean } | null | undefined, g: { moving: boolean }): boolean {
    const f = p.findOnMoving ?? o?.findOnMoving ?? false;
    if (!f && g.moving) {
        return false;
    }
    return true;
}

release 产物(script-backup/assets/main/index.js,变量已 mangle):

function i(n){const{p:o,o:i,g:t}=n;return!((o.findOnMoving??(null==i?void 0:i.findOnMoving)??1)&&t.moving)}

期望:return !(!(... ?? false) && t.moving)

两处错误:?? false 变成 ?? 1;!f && moving 变成 !(f && moving),内层 ! 丢失(od -c 已确认字节为 ??1,非 ??!1)。

影响

正确:moving && !findOnMoving 才拒绝。产物:moving && findOnMoving 才拒绝。

findOnMoving moving 正确 产物
未设置 / false true 拒绝 通过
true true 通过 拒绝

触发条件

同一工程同一构建内放 4 个变体:

变体 写法 产物 结果
A ?? 链 + if (!f && moving) !(...(??1) && moving) 错误
B coalesce() 封装 + Boolean(f) !1 !== Boolean(f) \|\| !moving 正确
C coalesce() 封装 + f === false !1 !== coalesce(...) \|\| !moving 正确
D 显式 if/else 赋值 let e=!1; e=null!=...? : ...; return !(moving && !e) 正确

结论:需同时满足 ①?? 链(≥2 层)含可选链 ?.;②结果参与 !x && y;③release 压缩。单层 ??(无 ?.)编译为 ??!1,正常。

最小复现

  1. Cocos 3.8.7 空工程,assets/scripts/repro.ts 放上面的函数,场景组件调用一次(防裁掉);
  2. 构建 android,debug=false;

样例代码

/**
 * 目标代码:把执行结果打印到 Label 上
 */
export function repro(p: any, o: any, g: any): boolean {
    const f = p.findOnMoving ?? o?.findOnMoving ?? false;
    if (!f && g.moving) return false;
    return true;
}

// 参数按需修改
const configA = { findOnMoving: undefined };
const configB = { findOnMoving: true };
const configC = { moving: true };
const result = repro(configA, configB, configC);
// web 平台
// result = true

// release包,如安卓
// result = false

表现:移动中的单位被判为不可索敌,或本应拒绝的目标被放行,无任何日志。

已排除

  • debug 构建输出正确;
  • 构建后的第三方加固与输入逐字节一致(cmp 相同),未参与改写;
  • 稳定落在 Cocos 构建的脚本压缩(terser)阶段。

规避写法(均已验证产物正确)

// 方案 1:显式判断
let f = false;
if (p.findOnMoving != null) f = p.findOnMoving;
else if (o?.findOnMoving != null) f = o.findOnMoving;
if (f === false && g.moving) return false;

期望修复

  1. !(A) && B ?? false 这类写法能正确编译;
  2. 建议提供关闭 compress.conditionals / evaluate 细分规则的构建开关,或升级 terser。

附件

  • 最小工程:dev_wzt_test/NewProject(assets/scripts/CoalescingEquivalenceTest.ts)
  • 明文产物:build/android/script-backup/assets/main/index.js
  • 加密产物:build/android/data/assets/main/index.jsc(构建时 xxtea key 解密后与明文一致)

NewProject_feedback_20261106.zip (857.6 KB)

@wanlai_sud 麻烦帮忙反馈下开发团队

没见过??的写法,长见识了 :rofl:

这是空值合并运算符,本次bug的根本原因是, 构建脚本压缩使用 terser 4.8.0,触发了内联优化

  • 把 a ?? b?.c ?? false 折叠成 a ?? b?.c ?? 1 (默认值由假变真);
  • 把 if (!f && moving) return false 折叠成 !(f && moving) (内层 ! 丢失,条件整体翻转)。

当我排查问题时加了console.log(a,b),因为打破了单一引用没有触发内联优化,bug消失。
这才是最离谱的