af_get_last_error in the unified backend calls a function pointer obtained from LOAD_SYMBOL() without checking it for null. When no backend library is loaded — or the symbol cannot be resolved — this is an indirect call through a null pointer.
This is the only use of LOAD_SYMBOL() in the unified backend, and it is unguarded.
Description
src/api/unified/error.cpp:38-47:
} else {
// If false, the error is coming from active backend.
typedef void (*af_func)(char **, dim_t *);
void *vfn = LOAD_SYMBOL();
af_func func = nullptr;
memcpy(&func, &vfn, sizeof(void *));
func(str, len); // no null check
}
LOAD_SYMBOL() expands to getFunctionPointer(getActiveHandle(), __FUNCTION__), which is GetProcAddress on Windows / dlsym elsewhere. Both return NULL rather than faulting when the handle is NULL or the symbol is absent, so func is NULL and func(str, len) jumps to address 0.
How the else branch is reached
The branch is taken when the unified layer's own error string is empty. That string is thread_local (src/backend/common/err_common.cpp):
std::string &get_global_error_string() noexcept {
thread_local auto *global_error_string = new std::string("");
return *global_error_string;
}
and af_get_last_error clears it after a successful read. So there are several ordinary ways to land in the else branch with no backend loaded:
- Calling
af_get_last_error twice in a row — the first read consumes and clears the string, the second finds it empty.
- Calling it from a different thread than the one that produced the error, since both the string and
getActiveHandle() are thread-local.
- Calling it defensively before any failing call.
Impact
STATUS_ACCESS_VIOLATION (0xC0000005) with faulting address 0x0000000000000000.
This crash occurs inside the error-reporting path. When backend loading fails, the attempt to retrieve the explanatory message is itself what crashes, so the user never sees "ArrayFire couldn't locate any backends." That is very likely why this class of failure has gone undiagnosed for years in the Rust bindings, whose default error handler calls af_get_last_error on every error:
Reproducible Code and/or Steps
see above
System Information
general issue
Checklist
Disclaimer
Found by Claude Opus 5 when prompted to look for potential sources for spurious 0xC0000005 errors on Windows.
af_get_last_errorin the unified backend calls a function pointer obtained fromLOAD_SYMBOL()without checking it for null. When no backend library is loaded — or the symbol cannot be resolved — this is an indirect call through a null pointer.This is the only use of
LOAD_SYMBOL()in the unified backend, and it is unguarded.Description
src/api/unified/error.cpp:38-47:LOAD_SYMBOL()expands togetFunctionPointer(getActiveHandle(), __FUNCTION__), which isGetProcAddresson Windows /dlsymelsewhere. Both return NULL rather than faulting when the handle is NULL or the symbol is absent, sofuncis NULL andfunc(str, len)jumps to address 0.How the
elsebranch is reachedThe branch is taken when the unified layer's own error string is empty. That string is
thread_local(src/backend/common/err_common.cpp):and
af_get_last_errorclears it after a successful read. So there are several ordinary ways to land in theelsebranch with no backend loaded:af_get_last_errortwice in a row — the first read consumes and clears the string, the second finds it empty.getActiveHandle()are thread-local.Impact
STATUS_ACCESS_VIOLATION(0xC0000005) with faulting address0x0000000000000000.This crash occurs inside the error-reporting path. When backend loading fails, the attempt to retrieve the explanatory message is itself what crashes, so the user never sees "ArrayFire couldn't locate any backends." That is very likely why this class of failure has gone undiagnosed for years in the Rust bindings, whose default error handler calls
af_get_last_erroron every error:set_backend()call, both still open and undiagnosed.Reproducible Code and/or Steps
see above
System Information
general issue
Checklist
Disclaimer
Found by Claude Opus 5 when prompted to look for potential sources for spurious 0xC0000005 errors on Windows.