From 6c3da60871f88083e875de1ed547f2bc30c22321 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:29:59 +0300 Subject: [PATCH] Zend: Fix swapped arguments in MSVC InterlockedCompareExchange CAS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MSVC fallback implementations of zend_atomic_bool_compare_exchange_ex and zend_atomic_int_compare_exchange_ex passed *expected as the Exchange argument and desired as the Comparand argument to InterlockedCompareExchange*. The WinAPI signature is InterlockedCompareExchange(Destination, Exchange, Comparand) — i.e. (new, old) — whereas the C11 / __atomic / __sync CAS intrinsics take (expected, desired) i.e. (old, new). The two arguments were therefore swapped: the function wrote the old value back on success and compared against the new value, so the CAS became a no-op in every case except the degenerate *expected == desired one (where it still reported success without having performed a real exchange). On x86/x64 with HAVE_C11_ATOMICS this code path is not compiled, which is why the bug has gone unnoticed. It is reachable on MSVC builds that do not define HAVE_C11_ATOMICS (e.g. older toolchains or the !C11 branch selected by the preprocessor), where any caller relying on CAS semantics — notably zend_atomic_int_fetch_add-style loops — silently fails to update the value. Swap Exchange and Comparand to match the documented WinAPI order. --- Zend/zend_atomic.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_atomic.h b/Zend/zend_atomic.h index 31558996c3c3..ddd8e7a759cd 100644 --- a/Zend/zend_atomic.h +++ b/Zend/zend_atomic.h @@ -104,7 +104,7 @@ static zend_always_inline int zend_atomic_int_exchange_ex(zend_atomic_int *obj, } static zend_always_inline bool zend_atomic_bool_compare_exchange_ex(zend_atomic_bool *obj, bool *expected, bool desired) { - bool prev = (bool) InterlockedCompareExchange8(&obj->value, *expected, desired); + bool prev = (bool) InterlockedCompareExchange8(&obj->value, desired, *expected); if (prev == *expected) { return true; } else { @@ -114,7 +114,7 @@ static zend_always_inline bool zend_atomic_bool_compare_exchange_ex(zend_atomic_ } static zend_always_inline bool zend_atomic_int_compare_exchange_ex(zend_atomic_int *obj, int *expected, int desired) { - int prev = (int) InterlockedCompareExchange(&obj->value, *expected, desired); + int prev = (int) InterlockedCompareExchange(&obj->value, desired, *expected); if (prev == *expected) { return true; } else {