Skip to content

Commit 7c5a6a7

Browse files
committed
[TS] Resolve the compare-to-zero truthiness idiom as ToBoolean for ref-like operands
Front ends lower `if (x)` into `x != 0` / `x != false`, byte-identical to a genuine loose comparison in the IR. For reference-like operands the numeric reading diverges from ToBoolean: `undefined != 0` evaluated as ToNumber(undefined) = NaN != 0 = true, making `undefined` truthy (found by the differential oracle: And.andOfUnknown(0, undefined) returned 21 where JS returns 0). A compare of a fake-object or reference operand against literal zero/false now resolves via mkTruthyExpr; numeric and boolean operands keep the literal loose-comparison semantics, matching the convention of the concrete ArkIR interpreter in the hybrid pipeline.
1 parent 3df3c83 commit 7c5a6a7

1 file changed

Lines changed: 32 additions & 0 deletions

File tree

usvm-ts/src/main/kotlin/org/usvm/machine/expr/TsExprResolver.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import org.jacodb.ets.model.EtsYieldExpr
7777
import org.jacodb.ets.utils.ANONYMOUS_METHOD_PREFIX
7878
import org.jacodb.ets.utils.DEFAULT_ARK_METHOD_NAME
7979
import org.jacodb.ets.utils.getDeclaredLocals
80+
import org.usvm.UBoolExpr
8081
import org.usvm.UExpr
8182
import org.usvm.UIteExpr
8283
import org.usvm.USort
@@ -786,13 +787,44 @@ class TsExprResolver(
786787
// region RELATION
787788

788789
override fun visit(expr: EtsEqExpr): UExpr<out USort>? {
790+
truthinessIdiomOrNull(expr.left, expr.right)?.let { truthy ->
791+
return ctx.mkNot(truthy)
792+
}
789793
return resolveBinaryOperator(TsBinaryOperator.Eq, expr)
790794
}
791795

792796
override fun visit(expr: EtsNotEqExpr): UExpr<out USort>? {
797+
truthinessIdiomOrNull(expr.left, expr.right)?.let { truthy ->
798+
return truthy
799+
}
793800
return resolveBinaryOperator(TsBinaryOperator.Neq, expr)
794801
}
795802

803+
/**
804+
* Front ends lower the truthiness test `if (x)` into the compare-to-zero /
805+
* compare-to-false idiom (`x != 0`, `x != false`), which is byte-identical
806+
* to a genuine loose comparison in the IR. For *reference-like* operands the
807+
* numeric reading diverges from ToBoolean (e.g. `undefined != 0` evaluates
808+
* to `NaN != 0 = true`, although `undefined` is falsy). Follow the idiom
809+
* contract: a compare of a fake/ref operand against literal zero/false is
810+
* ToBoolean. Numeric and boolean operands keep the literal loose-comparison
811+
* semantics (this matches the concrete ArkIR interpreter of the hybrid
812+
* pipeline).
813+
*
814+
* @return the truthiness expression, or null when the idiom does not apply.
815+
*/
816+
private fun truthinessIdiomOrNull(left: EtsEntity, right: EtsEntity): UBoolExpr? = with(ctx) {
817+
val isZeroOrFalse = (right is EtsNumberConstant && right.value == 0.0) ||
818+
(right is EtsBooleanConstant && !right.value)
819+
if (!isZeroOrFalse) return null
820+
821+
val lhs = resolve(left) ?: return null
822+
val isRefLike = lhs.isFakeObject() || (lhs.sort == addressSort)
823+
if (!isRefLike) return null
824+
825+
mkTruthyExpr(lhs, scope)
826+
}
827+
796828
override fun visit(expr: EtsStrictEqExpr): UExpr<out USort>? {
797829
return resolveBinaryOperator(TsBinaryOperator.StrictEq, expr)
798830
}

0 commit comments

Comments
 (0)