-26.5.0
+26.6.0
+26.5.0
26.4.0
26.3.1
26.3.0
diff --git a/README.md b/README.md
index dc175eadf5bb6a..45a01ca66fef4a 100644
--- a/README.md
+++ b/README.md
@@ -319,8 +319,6 @@ For information about the governance of the Node.js project, see
**Dario Piotrowicz** <> (he/him)
* [deokjinkim](https://github.com/deokjinkim) -
**Deokjin Kim** <> (he/him)
-* [edsadr](https://github.com/edsadr) -
- **Adrian Estrada** <> (he/him)
* [ErickWendel](https://github.com/ErickWendel) -
**Erick Wendel** <> (he/him)
* [Ethan-Arrowood](https://github.com/Ethan-Arrowood) -
@@ -513,6 +511,8 @@ For information about the governance of the Node.js project, see
**Xu Meng** <> (he/him)
* [dnlup](https://github.com/dnlup) -
**dnlup** <>
+* [edsadr](https://github.com/edsadr) -
+ **Adrian Estrada** <> (he/him)
* [eljefedelrodeodeljefe](https://github.com/eljefedelrodeodeljefe) -
**Robert Jefe Lindstaedt** <>
* [estliberitas](https://github.com/estliberitas) -
diff --git a/SECURITY.md b/SECURITY.md
index 4caa3cc18971af..b4239f5fda4ead 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -221,6 +221,16 @@ then untrusted input must not lead to arbitrary JavaScript code execution.
* The developers and infrastructure that run it.
* The operating system that Node.js is running under and its configuration,
along with anything under the control of the operating system.
+* The deployment network environment for the privacy of traffic and routing
+ decisions, including internal networks through which Node.js traffic passes
+ and configured HTTP(S) proxy servers. Built-in proxy support is intended to
+ route traffic through proxies authorized for the deployment, often because a
+ firewall requires one to access external networks. It is not intended to hide
+ traffic from network operators or authorities governing the deployment.
+ Untrusted or unauthorized proxies, as well as deployment policy or legal
+ compliance controls around proxy use, are the responsibility of the deployment
+ operator and are outside this threat model. This does not change that data
+ parsed from network protocol peers is untrusted as described above.
* The code it is asked to run, including JavaScript, WASM and native code, even
if said code is dynamically loaded, e.g., all dependencies installed from the
npm registry or libraries loaded via `node:ffi`.
@@ -301,6 +311,19 @@ the community they pose.
client consuming unsolicited or misordered responses within the same HTTP/1.1 connection
reuse lifecycle are generally not considered Node.js vulnerabilities.
+#### Unauthorized or untrusted HTTP proxy deployments
+
+* Built-in HTTP proxy support is intended for routing outbound requests through
+ a proxy authorized by the deployment, for example because a firewall requires
+ one to reach external networks. It is not an anonymity, traffic-hiding, or
+ policy-evasion feature.
+* Reports that depend on using an unauthorized proxy, expecting Node.js to
+ provide privacy from a configured proxy or internal network, or expecting
+ Node.js to enforce deployment-specific network policy or legal requirements
+ are not considered Node.js vulnerabilities. Deployment operators are
+ responsible for hardening such environments and controlling which proxy
+ settings are allowed.
+
#### Malicious Third-Party Modules (CWE-1357)
* Code is trusted by Node.js. Therefore any scenario that requires a malicious
diff --git a/benchmark/path/matchesGlob-posix.js b/benchmark/path/matchesGlob-posix.js
new file mode 100644
index 00000000000000..2cc30ca8ace1fb
--- /dev/null
+++ b/benchmark/path/matchesGlob-posix.js
@@ -0,0 +1,28 @@
+'use strict';
+const common = require('../common.js');
+const { posix } = require('path');
+const assert = require('assert');
+
+const bench = common.createBenchmark(main, {
+ path: [
+ 'src/index.ts',
+ 'src/index.test.ts',
+ 'src/foo/bar/biz/baz/index.test.ts',
+ ],
+ pattern: [
+ 'src/**/baz/*.test.ts',
+ 'src/**/baz/*.ts',
+ 'test/**/*.ts',
+ ],
+ n: [1e5],
+});
+
+function main({ path, pattern, n }) {
+ bench.start();
+ let a;
+ for (let i = 0; i < n; i++) {
+ a = posix.matchesGlob(path, pattern);
+ }
+ bench.end(n);
+ assert(a + 'a');
+}
diff --git a/benchmark/path/matchesGlob-win32.js b/benchmark/path/matchesGlob-win32.js
new file mode 100644
index 00000000000000..3ef3c6b4434445
--- /dev/null
+++ b/benchmark/path/matchesGlob-win32.js
@@ -0,0 +1,29 @@
+'use strict';
+const common = require('../common.js');
+const { win32 } = require('path');
+const assert = require('assert');
+
+const bench = common.createBenchmark(main, {
+ path: [
+ 'src/index.ts',
+ 'src\\index.ts',
+ 'src/foo/bar/biz/baz/index.test.ts',
+ 'src\\foo\\bar\\biz\\baz\\index.test.ts',
+ ],
+ pattern: [
+ 'src/**/baz/*.test.ts',
+ 'src/**/baz/*.ts',
+ 'test/**/*.ts',
+ ],
+ n: [1e5],
+});
+
+function main({ path, pattern, n }) {
+ bench.start();
+ let a;
+ for (let i = 0; i < n; i++) {
+ a = win32.matchesGlob(path, pattern);
+ }
+ bench.end(n);
+ assert(a + 'a');
+}
diff --git a/benchmark/webstreams/readable-async-iterator.js b/benchmark/webstreams/readable-async-iterator.js
index 8cdc4785db3dac..73ff3e1ef2b165 100644
--- a/benchmark/webstreams/readable-async-iterator.js
+++ b/benchmark/webstreams/readable-async-iterator.js
@@ -6,23 +6,40 @@ const {
const bench = common.createBenchmark(main, {
n: [1e5],
+ type: ['normal', 'bytes'],
});
-async function main({ n }) {
- const rs = new ReadableStream({
- pull: function(controller) {
- controller.enqueue(1);
- },
- });
+async function main({ n, type }) {
+ const rs = type === 'bytes' ?
+ new ReadableStream({
+ type: 'bytes',
+ pull: function(controller) {
+ controller.enqueue(new Uint8Array(1));
+ },
+ }) :
+ new ReadableStream({
+ pull: function(controller) {
+ controller.enqueue(1);
+ },
+ });
let x = 0;
bench.start();
- for await (const chunk of rs) {
- x += chunk;
- if (x > n) {
- break;
+ if (type === 'bytes') {
+ for await (const chunk of rs) {
+ x += chunk.byteLength;
+ if (x > n) {
+ break;
+ }
+ }
+ } else {
+ for await (const chunk of rs) {
+ x += chunk;
+ if (x > n) {
+ break;
+ }
}
}
// Use x to ensure V8 does not optimize away the loop as a noop.
diff --git a/common.gypi b/common.gypi
index bae09d8a5d6300..2afa1713d567ce 100644
--- a/common.gypi
+++ b/common.gypi
@@ -41,7 +41,7 @@
# Reset this number to 0 on major V8 upgrades.
# Increment by one for each non-official patch applied to deps/v8.
- 'v8_embedder_string': '-node.24',
+ 'v8_embedder_string': '-node.26',
##### V8 defaults for Node.js #####
diff --git a/deps/cares/CMakeLists.txt b/deps/cares/CMakeLists.txt
index a2a9c19329e09b..a9ae8fb952b72a 100644
--- a/deps/cares/CMakeLists.txt
+++ b/deps/cares/CMakeLists.txt
@@ -12,7 +12,7 @@ INCLUDE (CheckCSourceCompiles)
INCLUDE (CheckStructHasMember)
INCLUDE (CheckLibraryExists)
-PROJECT (c-ares LANGUAGES C VERSION "1.34.6" )
+PROJECT (c-ares LANGUAGES C VERSION "1.34.8" )
# Set this version before release
SET (CARES_VERSION "${PROJECT_VERSION}")
@@ -30,7 +30,7 @@ INCLUDE (GNUInstallDirs) # include this *AFTER* PROJECT(), otherwise paths are w
# For example, a version of 4:0:2 would generate output such as:
# libname.so -> libname.so.2
# libname.so.2 -> libname.so.2.2.0
-SET (CARES_LIB_VERSIONINFO "21:5:19")
+SET (CARES_LIB_VERSIONINFO "21:7:19")
OPTION (CARES_STATIC "Build as a static library" OFF)
@@ -43,6 +43,7 @@ OPTION (CARES_BUILD_TOOLS "Build tools"
OPTION (CARES_SYMBOL_HIDING "Hide private symbols in shared libraries" OFF)
OPTION (CARES_THREADS "Build with thread-safety support" ON)
OPTION (CARES_COVERAGE "Build for code coverage" OFF)
+OPTION (CARES_WERROR "Treat compiler warnings on the c-ares library as errors" OFF)
SET (CARES_RANDOM_FILE "/dev/urandom" CACHE STRING "Suitable File / Device Path for entropy, such as /dev/urandom")
# Tests require static to be enabled on Windows to be able to access otherwise hidden symbols
@@ -54,6 +55,30 @@ ENDIF ()
INCLUDE (EnableWarnings)
+# Optionally treat warnings as errors for the c-ares library itself. This is
+# applied per-target (see cares_set_werror() usage) rather than globally so it
+# does not affect the test/fuzz harnesses (which carry their own warnings) or
+# CMake's own compiler feature checks. Enabled in CI only on toolchains known
+# to be free of spurious warnings at our high warning levels.
+FUNCTION (cares_set_werror target)
+ IF (NOT CARES_WERROR)
+ RETURN ()
+ ENDIF ()
+ IF (MSVC)
+ TARGET_COMPILE_OPTIONS (${target} PRIVATE /WX)
+ ELSE ()
+ TARGET_COMPILE_OPTIONS (${target} PRIVATE -Werror)
+ # Modern libc headers (e.g. glibc ) use the C11 _Generic
+ # keyword for const-correctness of strchr()/memchr()/etc. Clang flags
+ # that as a C11 extension under our C90 + -Wpedantic build even though it
+ # originates in a system header at our call sites. It is not something
+ # we can fix in our own code, so warn but do not fail on it.
+ IF (CMAKE_C_COMPILER_ID MATCHES "Clang")
+ TARGET_COMPILE_OPTIONS (${target} PRIVATE -Wno-error=c11-extensions)
+ ENDIF ()
+ ENDIF ()
+ENDFUNCTION ()
+
IF (MSVC)
# allow linking against the static runtime library in msvc
OPTION (CARES_MSVC_STATIC_RUNTIME "Link against the static runtime library" OFF)
diff --git a/deps/cares/README.md b/deps/cares/README.md
index 6566c9fe6aa18e..7e818df8fa5b12 100644
--- a/deps/cares/README.md
+++ b/deps/cares/README.md
@@ -1,7 +1,7 @@
# [](https://c-ares.org/)
-[](https://cirrus-ci.com/github/c-ares/c-ares)
-[](https://ci.appveyor.com/project/c-ares/c-ares/branch/main)
+[](https://github.com/c-ares/c-ares/actions/workflows/ubuntu-latest.yml)
+[](https://github.com/c-ares/c-ares/actions/workflows/windows.yml)
[](https://coveralls.io/github/c-ares/c-ares?branch=main)
[](https://bestpractices.coreinfrastructure.org/projects/291)
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:c-ares)
diff --git a/deps/cares/RELEASE-NOTES.md b/deps/cares/RELEASE-NOTES.md
index ded75001b54fa4..93cc10fc74bd8e 100644
--- a/deps/cares/RELEASE-NOTES.md
+++ b/deps/cares/RELEASE-NOTES.md
@@ -1,3 +1,113 @@
+## c-ares version 1.34.8 - July 7 2026
+
+This is a bugfix release.
+
+Bugfixes:
+* Revert "Mark parameters in callbacks as const" which shipped in 1.34.7.
+ Changing the parameter types of the `ares_callback`, `ares_host_callback`,
+ and `ares_nameinfo_callback` function pointer typedefs was an unintended
+ API break: existing applications with the historical non-const callback
+ signatures no longer compiled, particularly in C++ where function pointer
+ types must match exactly. The read-only nature of the callback parameters
+ is now documented instead.
+ [PR #1244](https://github.com/c-ares/c-ares/pull/1244)
+
+Thanks go to these friendly people for their efforts and contributions for this
+release:
+
+* Brad House (@bradh352)
+
+
+## c-ares version 1.34.7 - July 6 2026
+
+This is a security release.
+
+Security:
+* CVE-2026-33630. Use-after-free / double-free in c-ares' query-completion
+ handling, remotely triggerable via ares_getaddrinfo() over TCP. Please see
+ https://github.com/c-ares/c-ares/security/advisories/GHSA-6wfj-rwm7-3542
+* CPU-exhaustion denial of service via unbounded DNS name compression pointer
+ chains. Please see
+ https://github.com/c-ares/c-ares/security/advisories/GHSA-pjmc-gx33-gc76
+* Memory-amplification denial of service via unvalidated DNS header record
+ counts. Please see
+ https://github.com/c-ares/c-ares/security/advisories/GHSA-jv8r-gqr9-68wj
+
+Changes:
+* `ares_getaddrinfo()`: handle a NULL node per POSIX and implement
+ `ARES_AI_PASSIVE`. [PR #1186](https://github.com/c-ares/c-ares/pull/1186)
+* Mark parameters in callbacks as const.
+ [PR #1060](https://github.com/c-ares/c-ares/pull/1060)
+* Correct `ARES_CLASS_HESOID` misspelling to `ARES_CLASS_HESIOD`.
+ [PR #1092](https://github.com/c-ares/c-ares/pull/1092)
+
+Bugfixes:
+* Fix sticky server recovery when all servers have failures. [PR #1192](https://github.com/c-ares/c-ares/pull/1192)
+* Fix UDP socket exhaustion regression: retire connections per-connection, not via server failure count. [PR #1197](https://github.com/c-ares/c-ares/pull/1197)
+* Guard DNS record binary length overflow. [PR #1168](https://github.com/c-ares/c-ares/pull/1168)
+* Prevent integer overflow in allocation size calculations. [PR #1147](https://github.com/c-ares/c-ares/pull/1147)
+* Prevent overflow in ares_array allocation size calculations. [PR #1117](https://github.com/c-ares/c-ares/pull/1117)
+* Prevent integer overflow in buffer size calculation. [PR #1116](https://github.com/c-ares/c-ares/pull/1116)
+* Add overflow checks to ares_buf_ensure_space(). [PR #1094](https://github.com/c-ares/c-ares/pull/1094)
+* Skip name compression offsets beyond the 14-bit pointer limit. [PR #1159](https://github.com/c-ares/c-ares/pull/1159)
+* ares_dns_parse: reject name compression in RDATA where not permitted (RFC 3597). [PR #1190](https://github.com/c-ares/c-ares/pull/1190)
+* ares_dns_parse: reject responses with more than one OPT record (RFC 6891). [PR #1189](https://github.com/c-ares/c-ares/pull/1189)
+* Discard oversized UDP datagrams instead of truncating the length frame. [PR #1161](https://github.com/c-ares/c-ares/pull/1161)
+* Route numeric config parsing through range-checked ares_str_parse_uint. [PR #1158](https://github.com/c-ares/c-ares/pull/1158)
+* Replace atoi-based port parsing with validated helper. [PR #1097](https://github.com/c-ares/c-ares/pull/1097)
+* Defer TCP connection error handling until DNS responses are parsed. [PR #1138](https://github.com/c-ares/c-ares/pull/1138)
+* Prevent undefined-behavior left shift in ares_calc_query_timeout(). [PR #1151](https://github.com/c-ares/c-ares/pull/1151)
+* Use unsigned type for ares_round_up_pow2_u64 to avoid undefined behavior. [PR #1107](https://github.com/c-ares/c-ares/pull/1107)
+* Fix undefined behavior in ares_buf_replace() pointer arithmetic. [PR #1099](https://github.com/c-ares/c-ares/pull/1099)
+* ares_array: reset offset when array becomes empty. [PR #1165](https://github.com/c-ares/c-ares/pull/1165)
+* ares_iface_ips: add ARES_IFACE_IP_NONE zero enum value. [PR #1187](https://github.com/c-ares/c-ares/pull/1187)
+* ares_sysconfig_files: recognize AIX netsvc.conf bind4/local4 tokens. [PR #1188](https://github.com/c-ares/c-ares/pull/1188)
+* Use safe string construction in Windows sysconfig join path. [PR #1143](https://github.com/c-ares/c-ares/pull/1143)
+* Fix zero-length RAW_RR losing type metadata during parsing. [PR #1129](https://github.com/c-ares/c-ares/pull/1129)
+* Fix RAW_RR type tostr/fromstr roundtrip mismatch. [PR #1123](https://github.com/c-ares/c-ares/pull/1123)
+* Fix NULL dereference for ifa netmask. [PR #1120](https://github.com/c-ares/c-ares/pull/1120)
+* adig: fix negated option prefix parsing. [PR #1135](https://github.com/c-ares/c-ares/pull/1135)
+* Use UnregisterWaitEx to prevent use-after-free on Win32. [PR #1111](https://github.com/c-ares/c-ares/pull/1111)
+* Add NULL check after ares_malloc_zero in Windows UTF8 conversion. [PR #1121](https://github.com/c-ares/c-ares/pull/1121)
+* Fix NULL dereference after ares_malloc_zero in Win32 IOCP event add. [PR #1132](https://github.com/c-ares/c-ares/pull/1132)
+* Fix microsecond overflow in ares_queue_wait_empty timeout. [PR #1122](https://github.com/c-ares/c-ares/pull/1122)
+* Fix NULL dereference in ares_buf_replace() on NULL buf. [PR #1124](https://github.com/c-ares/c-ares/pull/1124)
+* Initialize *read_bytes in ares_socket_recvfrom(). [PR #1125](https://github.com/c-ares/c-ares/pull/1125)
+* Fix memory leak of binbuf in ares_buf_parse_dns_binstr_int. [PR #1126](https://github.com/c-ares/c-ares/pull/1126)
+* Fix memory leak of qcache entry on key allocation failure. [PR #1127](https://github.com/c-ares/c-ares/pull/1127)
+* Fix memory leak of buf in ares_dns_multistring_combined() on OOM. [PR #1110](https://github.com/c-ares/c-ares/pull/1110)
+* Fix memory leaks on error paths in two functions. [PR #1109](https://github.com/c-ares/c-ares/pull/1109)
+* Fix memory leak of buckets in ares_htable_dict_keys() error path. [PR #1108](https://github.com/c-ares/c-ares/pull/1108)
+* Fix memory leak of bucket->key in ares_htable_dict_insert error path. [PR #1105](https://github.com/c-ares/c-ares/pull/1105)
+* Fix additional memory leaks. [PR #1091](https://github.com/c-ares/c-ares/pull/1091) [PR #1078](https://github.com/c-ares/c-ares/pull/1078)
+* Prevent corrupt addrinfo nodes on sockaddr allocation failure. [PR #1112](https://github.com/c-ares/c-ares/pull/1112)
+* Fix two logic bugs in DNS cookie handling. [PR #1103](https://github.com/c-ares/c-ares/pull/1103)
+* Fix linked list INSERT_BEFORE corruption and array insertdata_first ordering. [PR #1101](https://github.com/c-ares/c-ares/pull/1101)
+* Fix HASH_IDX macro missing parentheses. [PR #1128](https://github.com/c-ares/c-ares/pull/1128)
+* Fix malloc(0) in ares_htable_all_buckets on empty table. [PR #1131](https://github.com/c-ares/c-ares/pull/1131)
+* Fix wrong sizeof in QNX confstr() call truncating domain names. [PR #1130](https://github.com/c-ares/c-ares/pull/1130)
+* Clear probe pending flag after timeout. [PR #1059](https://github.com/c-ares/c-ares/pull/1059)
+* Fix incorrect check for empty wide string. [PR #1064](https://github.com/c-ares/c-ares/pull/1064)
+* Handle strdup failure. [PR #1077](https://github.com/c-ares/c-ares/pull/1077)
+* doc: reference ares_free_string(), not ares_free(). [PR #1084](https://github.com/c-ares/c-ares/pull/1084)
+
+Thanks go to these friendly people for their efforts and contributions for this
+release:
+
+* (@Alb3e3)
+* Brad House (@bradh352)
+* (@dankmeme01)
+* David Hotham (@dimbleby)
+* Tom Flynn (@Flynnzaa)
+* (@jmestwa-coder)
+* (@kodareef5)
+* Kaixuan Li (@MarkLee131)
+* (@lifenjoiner)
+* (@metsw24-max)
+* Song Li (@SongTonyLi)
+* (@uwezkhan)
+
+
## c-ares version 1.34.6 - December 8 2025
This is a security release.
diff --git a/deps/cares/aminclude_static.am b/deps/cares/aminclude_static.am
index 07239563e6a6d2..9a00ba789172eb 100644
--- a/deps/cares/aminclude_static.am
+++ b/deps/cares/aminclude_static.am
@@ -1,6 +1,6 @@
# aminclude_static.am generated automatically by Autoconf
-# from AX_AM_MACROS_STATIC on Mon Dec 8 16:21:41 UTC 2025
+# from AX_AM_MACROS_STATIC on Tue Jul 7 16:14:13 UTC 2026
# Code coverage
diff --git a/deps/cares/configure b/deps/cares/configure
index f91e2c956862e7..c2318a1c3bf07a 100755
--- a/deps/cares/configure
+++ b/deps/cares/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.71 for c-ares 1.34.6.
+# Generated by GNU Autoconf 2.71 for c-ares 1.34.8.
#
# Report bugs to .
#
@@ -621,8 +621,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='c-ares'
PACKAGE_TARNAME='c-ares'
-PACKAGE_VERSION='1.34.6'
-PACKAGE_STRING='c-ares 1.34.6'
+PACKAGE_VERSION='1.34.8'
+PACKAGE_STRING='c-ares 1.34.8'
PACKAGE_BUGREPORT='c-ares mailing list: http://lists.haxx.se/listinfo/c-ares'
PACKAGE_URL=''
@@ -1430,7 +1430,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures c-ares 1.34.6 to adapt to many kinds of systems.
+\`configure' configures c-ares 1.34.8 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1501,7 +1501,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of c-ares 1.34.6:";;
+ short | recursive ) echo "Configuration of c-ares 1.34.8:";;
esac
cat <<\_ACEOF
@@ -1647,7 +1647,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-c-ares configure 1.34.6
+c-ares configure 1.34.8
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@@ -2271,7 +2271,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by c-ares $as_me 1.34.6, which was
+It was created by c-ares $as_me 1.34.8, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@@ -3245,7 +3245,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
-CARES_VERSION_INFO="21:5:19"
+CARES_VERSION_INFO="21:7:19"
@@ -6818,7 +6818,7 @@ fi
# Define the identity of the package.
PACKAGE='c-ares'
- VERSION='1.34.6'
+ VERSION='1.34.8'
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -28238,7 +28238,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by c-ares $as_me 1.34.6, which was
+This file was extended by c-ares $as_me 1.34.8, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -28306,7 +28306,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
-c-ares config.status 1.34.6
+c-ares config.status 1.34.8
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"
diff --git a/deps/cares/configure.ac b/deps/cares/configure.ac
index 744a99be56ab2f..637dfb153b1f72 100644
--- a/deps/cares/configure.ac
+++ b/deps/cares/configure.ac
@@ -2,10 +2,10 @@ dnl Copyright (C) The c-ares project and its contributors
dnl SPDX-License-Identifier: MIT
AC_PREREQ([2.69])
-AC_INIT([c-ares], [1.34.6],
+AC_INIT([c-ares], [1.34.8],
[c-ares mailing list: http://lists.haxx.se/listinfo/c-ares])
-CARES_VERSION_INFO="21:5:19"
+CARES_VERSION_INFO="21:7:19"
dnl This flag accepts an argument of the form current[:revision[:age]]. So,
dnl passing -version-info 3:12:1 sets current to 3, revision to 12, and age to
dnl 1.
diff --git a/deps/cares/docs/ares_dns_record.3 b/deps/cares/docs/ares_dns_record.3
index 47ca95b057a246..723f9ab4b98c6a 100644
--- a/deps/cares/docs/ares_dns_record.3
+++ b/deps/cares/docs/ares_dns_record.3
@@ -140,7 +140,7 @@ DNS Classes for requests and responses:
.B ARES_CLASS_CHAOS
- CHAOS
.br
-.B ARES_CLASS_HESOID
+.B ARES_CLASS_HESIOD
- Hesoid [Dyer 87]
.br
.B ARES_CLASS_NONE
diff --git a/deps/cares/docs/ares_getaddrinfo.3 b/deps/cares/docs/ares_getaddrinfo.3
index 5544ce20224eba..661381e0926e8c 100644
--- a/deps/cares/docs/ares_getaddrinfo.3
+++ b/deps/cares/docs/ares_getaddrinfo.3
@@ -27,6 +27,19 @@ The
and
.I service
parameters give the hostname and service as NULL-terminated C strings.
+Either
+.I name
+or
+.I service
+may be NULL, but not both. If
+.I name
+is NULL, the returned addresses are synthesized from
+.IR service :
+the wildcard address (0.0.0.0 or ::) is returned when
+.B ARES_AI_PASSIVE
+is set in
+.IR ai_flags ,
+otherwise the loopback address (127.0.0.1 or ::1) is returned.
The
.I hints
parameter is an
@@ -63,6 +76,16 @@ If this option is set
.I service
field will be treated as a numeric value.
.TP 19
+.B ARES_AI_PASSIVE
+If this option is set and
+.I name
+is NULL, the returned addresses will use the wildcard address (0.0.0.0 or ::),
+suitable for
+.BR bind (2)ing
+a socket that will accept connections. If not set and
+.I name
+is NULL, the loopback address (127.0.0.1 or ::1) is returned instead.
+.TP 19
.B ARES_AI_CANONNAME
The ares_addrinfo structure will return a canonical names list.
.TP 19
diff --git a/deps/cares/docs/ares_gethostbyaddr.3 b/deps/cares/docs/ares_gethostbyaddr.3
index cc4092285c0168..f5b5f0864fcff1 100644
--- a/deps/cares/docs/ares_gethostbyaddr.3
+++ b/deps/cares/docs/ares_gethostbyaddr.3
@@ -80,7 +80,16 @@ points to a
containing the name of the host returned by the query. The callback
need not and should not attempt to free the memory pointed to by
.IR hostent ;
-the ares library will free it when the callback returns. If the query
+the ares library will free it when the callback returns. The callback
+.B must not
+modify the
+.B struct hostent
+or retain a reference to it after returning; it is owned by c-ares and is only
+valid for the duration of the callback. The parameter is intentionally not
+declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
+If the query
did not complete successfully,
.I hostent
will be
diff --git a/deps/cares/docs/ares_gethostbyname.3 b/deps/cares/docs/ares_gethostbyname.3
index 06d075ca6c5136..40d340343114f1 100644
--- a/deps/cares/docs/ares_gethostbyname.3
+++ b/deps/cares/docs/ares_gethostbyname.3
@@ -88,7 +88,16 @@ points to a
containing the name of the host returned by the query. The callback
need not and should not attempt to free the memory pointed to by
.IR hostent ;
-the ares library will free it when the callback returns. If the query
+the ares library will free it when the callback returns. The callback
+.B must not
+modify the
+.B struct hostent
+or retain a reference to it after returning; it is owned by c-ares and is only
+valid for the duration of the callback. The parameter is intentionally not
+declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
+If the query
did not complete successfully,
.I hostent
will be
diff --git a/deps/cares/docs/ares_getnameinfo.3 b/deps/cares/docs/ares_getnameinfo.3
index 66b04f9efc11a7..800b57b31f3f4c 100644
--- a/deps/cares/docs/ares_getnameinfo.3
+++ b/deps/cares/docs/ares_getnameinfo.3
@@ -24,7 +24,7 @@ function is defined for protocol-independent address translation. The function
is a combination of \fIares_gethostbyaddr(3)\fP and \fIgetservbyport(3)\fP. The function will
translate the address either by executing a host query on the name service channel
identified by
-.IR channel
+.IR channel
or it will attempt to resolve it locally if possible.
The parameters
.I sa
@@ -68,8 +68,8 @@ A hostname lookup is being requested.
A service name lookup is being requested.
.PP
When the query
-is complete or has
-failed, the ares library will invoke \fIcallback\fP. Completion or failure of
+is complete or has
+failed, the ares library will invoke \fIcallback\fP. Completion or failure of
the query may happen immediately, or may happen during a later call to
\fIares_process(3)\fP, \fIares_destroy(3)\fP or \fIares_cancel(3)\fP.
.PP
@@ -129,19 +129,31 @@ given request.
.PP
On successful completion of the query, the callback argument
.I node
-contains a string representing the hostname (assuming
+contains a string representing the hostname (assuming
.B ARES_NI_LOOKUPHOST
-was specified). Additionally,
+was specified). Additionally,
.I service
contains a string representing the service name (assuming
.B ARES_NI_LOOKUPSERVICE
was specified).
If the query did not complete successfully, or one of the values
-was not requested,
+was not requested,
.I node
or
.I service
-will be
+will be
.BR NULL .
+.PP
+The
+.I node
+and
+.I service
+strings are owned by the c-ares library and are only valid for the duration of
+the callback. The callback
+.B must not
+modify or free them, and must not retain references to them after returning;
+treat them as read-only. The parameters are intentionally not declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
.SH SEE ALSO
.BR ares_process (3),
diff --git a/deps/cares/docs/ares_query.3 b/deps/cares/docs/ares_query.3
index 3aa428b00bb813..401d700a44ccd9 100644
--- a/deps/cares/docs/ares_query.3
+++ b/deps/cares/docs/ares_query.3
@@ -146,6 +146,16 @@ or
.I abuf
will be non-NULL, otherwise they will be NULL.
+The
+.I abuf
+buffer is owned by the c-ares library and is only valid for the duration of
+the callback. The callback
+.B must not
+modify or free it, and must not retain a reference to it after returning; treat
+it as read-only. The parameter is intentionally not declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
+
.SH AVAILABILITY
\fBares_query_dnsrec(3)\fP was introduced in c-ares 1.28.0.
diff --git a/deps/cares/docs/ares_search.3 b/deps/cares/docs/ares_search.3
index 66791b47e908fb..12d1dede09e874 100644
--- a/deps/cares/docs/ares_search.3
+++ b/deps/cares/docs/ares_search.3
@@ -152,6 +152,15 @@ will usually be NULL and
will usually be 0, but in some cases an unsuccessful query result may
be placed in
.IR abuf .
+The
+.I abuf
+buffer is owned by the c-ares library and is only valid for the duration of
+the callback. The callback
+.B must not
+modify or free it, and must not retain a reference to it after returning; treat
+it as read-only. The parameter is intentionally not declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
The \fIares_search_dnsrec(3)\fP function behaves identically to
\fIares_search(3)\fP, but takes an initialized and filled DNS record object to
diff --git a/deps/cares/docs/ares_send.3 b/deps/cares/docs/ares_send.3
index df3e3bbe4136b0..cf669a001db295 100644
--- a/deps/cares/docs/ares_send.3
+++ b/deps/cares/docs/ares_send.3
@@ -131,6 +131,16 @@ and
.IR alen
for \fIares_send(3)\fP will be non-NULL.
+The
+.I abuf
+buffer is owned by the c-ares library and is only valid for the duration of
+the callback. The callback
+.B must not
+modify or free it, and must not retain a reference to it after returning; treat
+it as read-only. The parameter is intentionally not declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
+
Unless the flag
.B ARES_FLAG_NOCHECKRESP
was set at channel initialization time, \fIares_send_dnsrec(3)\fP and
diff --git a/deps/cares/include/ares.h b/deps/cares/include/ares.h
index 7fe3ec78f4e651..1e38463a044a22 100644
--- a/deps/cares/include/ares.h
+++ b/deps/cares/include/ares.h
@@ -434,6 +434,14 @@ struct ares_addr {
/* DNS record parser, writer, and helpers */
#include "ares_dns_record.h"
+/* IMPORTANT: the pointer parameters below (abuf, hostent, node, service) are
+ * intentionally NOT const. Marking them const changes the function-pointer
+ * type and breaks API/ABI compatibility for existing C and (especially) C++
+ * applications that declare their callbacks with the historical non-const
+ * signature. This was tried in PR #1060 and reverted for that reason. These
+ * buffers are owned by c-ares and MUST be treated as read-only by the callback
+ * (do not modify or free them); that contract is documented in the man pages,
+ * not enforced via const, to preserve compatibility. Do not re-add const. */
typedef void (*ares_callback)(void *arg, int status, int timeouts,
unsigned char *abuf, int alen);
diff --git a/deps/cares/include/ares_dns_record.h b/deps/cares/include/ares_dns_record.h
index cec9f47f63d8f5..1e47a78e143c4a 100644
--- a/deps/cares/include/ares_dns_record.h
+++ b/deps/cares/include/ares_dns_record.h
@@ -76,7 +76,8 @@ typedef enum {
typedef enum {
ARES_CLASS_IN = 1, /*!< Internet */
ARES_CLASS_CHAOS = 3, /*!< CHAOS */
- ARES_CLASS_HESOID = 4, /*!< Hesoid [Dyer 87] */
+ ARES_CLASS_HESIOD = 4, /*!< Hesiod [Dyer 87] */
+ ARES_CLASS_HESOID = 4, /*!< typo from older c-ares version for Hesiod */
ARES_CLASS_NONE = 254, /*!< RFC 2136 */
ARES_CLASS_ANY = 255 /*!< Any class (requests only) */
} ares_dns_class_t;
@@ -1092,7 +1093,7 @@ CARES_EXTERN ares_status_t ares_dns_parse(const unsigned char *buf,
*
* \param[in] dnsrec Pointer to initialized and filled DNS record object.
* \param[out] buf Pointer passed by reference to be filled in with with
- * DNS message. Must be ares_free()'d by caller.
+ * DNS message. Must be ares_free_string()'d by caller.
* \param[out] buf_len Length of returned buffer containing DNS message.
* \return ARES_SUCCESS on success
*/
diff --git a/deps/cares/include/ares_version.h b/deps/cares/include/ares_version.h
index 006029c12dfe91..f836d5f9186d1c 100644
--- a/deps/cares/include/ares_version.h
+++ b/deps/cares/include/ares_version.h
@@ -32,8 +32,8 @@
#define ARES_VERSION_MAJOR 1
#define ARES_VERSION_MINOR 34
-#define ARES_VERSION_PATCH 6
-#define ARES_VERSION_STR "1.34.6"
+#define ARES_VERSION_PATCH 8
+#define ARES_VERSION_STR "1.34.8"
/* NOTE: We cannot make the version string a C preprocessor stringify operation
* due to assumptions made by integrators that aren't properly using
diff --git a/deps/cares/src/lib/CMakeLists.txt b/deps/cares/src/lib/CMakeLists.txt
index bdb2690fb673f9..a4c1e73f1bd5ad 100644
--- a/deps/cares/src/lib/CMakeLists.txt
+++ b/deps/cares/src/lib/CMakeLists.txt
@@ -66,6 +66,8 @@ IF (CARES_SHARED)
TARGET_COMPILE_DEFINITIONS (${PROJECT_NAME} PRIVATE HAVE_CONFIG_H=1 CARES_BUILDING_LIBRARY)
+ cares_set_werror (${PROJECT_NAME})
+
TARGET_LINK_LIBRARIES (${PROJECT_NAME}
PUBLIC ${CARES_DEPENDENT_LIBS}
PRIVATE ${CMAKE_THREAD_LIBS_INIT}
@@ -136,6 +138,8 @@ IF (CARES_STATIC)
TARGET_COMPILE_DEFINITIONS (${LIBNAME} PRIVATE HAVE_CONFIG_H=1 CARES_BUILDING_LIBRARY)
+ cares_set_werror (${LIBNAME})
+
# Only matters on Windows
IF (WIN32 OR CYGWIN)
TARGET_COMPILE_DEFINITIONS (${LIBNAME} PUBLIC CARES_STATICLIB)
diff --git a/deps/cares/src/lib/Makefile.in b/deps/cares/src/lib/Makefile.in
index e92732eaf72c8d..a9630300c0848b 100644
--- a/deps/cares/src/lib/Makefile.in
+++ b/deps/cares/src/lib/Makefile.in
@@ -15,7 +15,7 @@
@SET_MAKE@
# aminclude_static.am generated automatically by Autoconf
-# from AX_AM_MACROS_STATIC on Mon Dec 8 16:21:41 UTC 2025
+# from AX_AM_MACROS_STATIC on Tue Jul 7 16:14:13 UTC 2026
# Copyright (C) The c-ares project and its contributors
# SPDX-License-Identifier: MIT
diff --git a/deps/cares/src/lib/ares_addrinfo_localhost.c b/deps/cares/src/lib/ares_addrinfo_localhost.c
index 2abb0c48a6f601..b65cf48a2f6a75 100644
--- a/deps/cares/src/lib/ares_addrinfo_localhost.c
+++ b/deps/cares/src/lib/ares_addrinfo_localhost.c
@@ -67,13 +67,8 @@ ares_status_t ares_append_ai_node(int aftype, unsigned short port,
struct ares_addrinfo_node **nodes)
{
struct ares_addrinfo_node *node;
-
- node = ares_append_addrinfo_node(nodes);
- if (!node) {
- return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
- }
-
- memset(node, 0, sizeof(*node));
+ struct sockaddr *sa;
+ socklen_t salen;
if (aftype == AF_INET) {
struct sockaddr_in *sin = ares_malloc(sizeof(*sin));
@@ -86,14 +81,9 @@ ares_status_t ares_append_ai_node(int aftype, unsigned short port,
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
- node->ai_addr = (struct sockaddr *)sin;
- node->ai_family = AF_INET;
- node->ai_addrlen = sizeof(*sin);
- node->ai_addr = (struct sockaddr *)sin;
- node->ai_ttl = (int)ttl;
- }
-
- if (aftype == AF_INET6) {
+ sa = (struct sockaddr *)sin;
+ salen = sizeof(*sin);
+ } else if (aftype == AF_INET6) {
struct sockaddr_in6 *sin6 = ares_malloc(sizeof(*sin6));
if (!sin6) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
@@ -104,13 +94,23 @@ ares_status_t ares_append_ai_node(int aftype, unsigned short port,
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons(port);
- node->ai_addr = (struct sockaddr *)sin6;
- node->ai_family = AF_INET6;
- node->ai_addrlen = sizeof(*sin6);
- node->ai_addr = (struct sockaddr *)sin6;
- node->ai_ttl = (int)ttl;
+ sa = (struct sockaddr *)sin6;
+ salen = sizeof(*sin6);
+ } else {
+ return ARES_EFORMERR;
+ }
+
+ node = ares_append_addrinfo_node(nodes);
+ if (!node) {
+ ares_free(sa); /* LCOV_EXCL_LINE: OutOfMemory */
+ return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
+ node->ai_addr = sa;
+ node->ai_family = aftype;
+ node->ai_addrlen = salen;
+ node->ai_ttl = (int)ttl;
+
return ARES_SUCCESS;
}
diff --git a/deps/cares/src/lib/ares_close_sockets.c b/deps/cares/src/lib/ares_close_sockets.c
index 347f43e6fcd88d..7bb72c31ec1b2a 100644
--- a/deps/cares/src/lib/ares_close_sockets.c
+++ b/deps/cares/src/lib/ares_close_sockets.c
@@ -112,11 +112,11 @@ void ares_check_cleanup_conns(const ares_channel_t *channel)
do_cleanup = ARES_TRUE;
}
- /* If the associated server has failures, close it out. Resetting the
- * connection (and specifically the source port number) can help resolve
- * situations where packets are being dropped.
+ /* If the connection has been retired for new queries, close it out once
+ * idle. Resetting the connection (and specifically the source port
+ * number) can help resolve situations where packets are being dropped.
*/
- if (conn->server->consec_failures > 0) {
+ if (conn->flags & ARES_CONN_FLAG_NONEW) {
do_cleanup = ARES_TRUE;
}
diff --git a/deps/cares/src/lib/ares_conn.h b/deps/cares/src/lib/ares_conn.h
index 16ecefdd19fa67..c332de14b135d4 100644
--- a/deps/cares/src/lib/ares_conn.h
+++ b/deps/cares/src/lib/ares_conn.h
@@ -43,7 +43,13 @@ typedef enum {
ARES_CONN_FLAG_TFO = 1 << 1,
/*! TCP Fast Open has not yet sent its first packet. Gets unset on first
* write to a connection */
- ARES_CONN_FLAG_TFO_INITIAL = 1 << 2
+ ARES_CONN_FLAG_TFO_INITIAL = 1 << 2,
+ /*! Connection has been retired: it must not be handed any NEW queries (e.g.
+ * it experienced a query timeout, suggesting packets are being dropped on
+ * it). Its in-flight queries continue to drain and it is cleaned up once
+ * idle. This is a per-connection signal so that a transient failure does
+ * not evict otherwise-healthy connections to the same server. */
+ ARES_CONN_FLAG_NONEW = 1 << 3
} ares_conn_flags_t;
typedef enum {
diff --git a/deps/cares/src/lib/ares_cookie.c b/deps/cares/src/lib/ares_cookie.c
index 509e12050e0c00..25960db6022fcc 100644
--- a/deps/cares/src/lib/ares_cookie.c
+++ b/deps/cares/src/lib/ares_cookie.c
@@ -217,7 +217,7 @@ static const unsigned char *
static ares_bool_t timeval_is_set(const ares_timeval_t *tv)
{
- if (tv->sec != 0 && tv->usec != 0) {
+ if (tv->sec != 0 || tv->usec != 0) {
return ARES_TRUE;
}
return ARES_FALSE;
@@ -324,7 +324,7 @@ ares_status_t ares_cookie_apply(ares_dns_record_t *dnsrec, ares_conn_t *conn,
if (cookie->state == ARES_COOKIE_UNSUPPORTED) {
/* If timer hasn't expired, just delete any possible cookie and return */
if (!timeval_expired(&cookie->unsupported_ts, now,
- COOKIE_REGRESSION_TIMEOUT_MS)) {
+ COOKIE_UNSUPPORTED_TIMEOUT_MS)) {
ares_dns_rr_del_opt_byid(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE);
return ARES_SUCCESS;
}
diff --git a/deps/cares/src/lib/ares_getaddrinfo.c b/deps/cares/src/lib/ares_getaddrinfo.c
index 6009de36a3d02d..6d9e21af4bb9a1 100644
--- a/deps/cares/src/lib/ares_getaddrinfo.c
+++ b/deps/cares/src/lib/ares_getaddrinfo.c
@@ -242,6 +242,11 @@ static ares_bool_t fake_addrinfo(const char *name, unsigned short port,
ares_status_t status = ARES_SUCCESS;
ares_bool_t result = ARES_FALSE;
int family = hints->ai_family;
+
+ if (name == NULL) {
+ return ARES_FALSE; /* LCOV_EXCL_LINE: DefensiveCoding */
+ }
+
if (family == AF_INET || family == AF_INET6 || family == AF_UNSPEC) {
/* It only looks like an IP address if it's all numbers and dots. */
size_t numdots = 0;
@@ -268,6 +273,7 @@ static ares_bool_t fake_addrinfo(const char *name, unsigned short port,
if (result) {
status = ares_append_ai_node(AF_INET, port, 0, &addr4, &ai->nodes);
if (status != ARES_SUCCESS) {
+ ares_freeaddrinfo(ai);
callback(arg, (int)status, 0, NULL); /* LCOV_EXCL_LINE: OutOfMemory */
return ARES_TRUE; /* LCOV_EXCL_LINE: OutOfMemory */
}
@@ -282,6 +288,7 @@ static ares_bool_t fake_addrinfo(const char *name, unsigned short port,
if (result) {
status = ares_append_ai_node(AF_INET6, port, 0, &addr6, &ai->nodes);
if (status != ARES_SUCCESS) {
+ ares_freeaddrinfo(ai);
callback(arg, (int)status, 0, NULL); /* LCOV_EXCL_LINE: OutOfMemory */
return ARES_TRUE; /* LCOV_EXCL_LINE: OutOfMemory */
}
@@ -575,21 +582,55 @@ static void host_callback(void *arg, ares_status_t status, size_t timeouts,
/* at this point we keep on waiting for the next query to finish */
}
-static ares_bool_t numeric_service_to_port(const char *service,
- unsigned short *port)
+/* Per POSIX getaddrinfo(), when no node/hostname is provided the returned
+ * addresses are synthesized from the service: the wildcard address when
+ * ARES_AI_PASSIVE is set (suitable for bind()ing a listening socket),
+ * otherwise the loopback address (suitable for connect()ing to a peer on the
+ * local host). */
+static ares_status_t
+ ares_append_nulladdr(unsigned short port,
+ const struct ares_addrinfo_hints *hints,
+ struct ares_addrinfo *ai)
{
- char *end;
- unsigned long val;
+ int family = hints->ai_family;
+ ares_bool_t passive = ARES_FALSE;
+ ares_status_t status;
+ struct ares_addrinfo_node *node;
- errno = 0;
- val = strtoul(service, &end, 10);
+ if (hints->ai_flags & ARES_AI_PASSIVE) {
+ passive = ARES_TRUE;
+ }
- if (errno == 0 && *end == '\0' && val <= 65535) {
- *port = (unsigned short)val;
- return ARES_TRUE;
+ if (family == AF_INET6 || family == AF_UNSPEC) {
+ struct ares_in6_addr addr6;
+ memset(&addr6, 0, sizeof(addr6)); /* wildcard "::" */
+ if (!passive) {
+ ares_inet_pton(AF_INET6, "::1", &addr6);
+ }
+ status = ares_append_ai_node(AF_INET6, port, 0, &addr6, &ai->nodes);
+ if (status != ARES_SUCCESS) {
+ return status; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
}
- return ARES_FALSE;
+ if (family == AF_INET || family == AF_UNSPEC) {
+ struct in_addr addr4;
+ memset(&addr4, 0, sizeof(addr4)); /* wildcard "0.0.0.0" */
+ if (!passive) {
+ ares_inet_pton(AF_INET, "127.0.0.1", &addr4);
+ }
+ status = ares_append_ai_node(AF_INET, port, 0, &addr4, &ai->nodes);
+ if (status != ARES_SUCCESS) {
+ return status; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+ }
+
+ for (node = ai->nodes; node != NULL; node = node->ai_next) {
+ node->ai_socktype = hints->ai_socktype;
+ node->ai_protocol = hints->ai_protocol;
+ }
+
+ return ARES_SUCCESS;
}
static void ares_getaddrinfo_int(ares_channel_t *channel, const char *name,
@@ -616,6 +657,13 @@ static void ares_getaddrinfo_int(ares_channel_t *channel, const char *name,
return;
}
+ /* POSIX getaddrinfo() requires that at least one of node (name) or service
+ * be provided. */
+ if (name == NULL && service == NULL) {
+ callback(arg, ARES_ENOTFOUND, 0, NULL);
+ return;
+ }
+
if (ares_is_onion_domain(name)) {
callback(arg, ARES_ENOTFOUND, 0, NULL);
return;
@@ -623,14 +671,14 @@ static void ares_getaddrinfo_int(ares_channel_t *channel, const char *name,
if (service) {
if (hints->ai_flags & ARES_AI_NUMERICSERV) {
- if (!numeric_service_to_port(service, &port)) {
+ if (!ares_parse_port(service, &port, ARES_TRUE)) {
callback(arg, ARES_ESERVICE, 0, NULL);
return;
}
} else {
port = lookup_service(service, 0);
if (!port) {
- if (!numeric_service_to_port(service, &port)) {
+ if (!ares_parse_port(service, &port, ARES_TRUE)) {
callback(arg, ARES_ESERVICE, 0, NULL);
return;
}
@@ -644,6 +692,19 @@ static void ares_getaddrinfo_int(ares_channel_t *channel, const char *name,
return;
}
+ /* No node/hostname was provided (service-only lookup): synthesize wildcard
+ * or loopback addresses from the resolved port instead of issuing a query. */
+ if (name == NULL) {
+ status = ares_append_nulladdr(port, hints, ai);
+ if (status != ARES_SUCCESS) {
+ ares_freeaddrinfo(ai); /* LCOV_EXCL_LINE: OutOfMemory */
+ callback(arg, (int)status, 0, NULL); /* LCOV_EXCL_LINE: OutOfMemory */
+ return; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+ callback(arg, ARES_SUCCESS, 0, ai);
+ return;
+ }
+
if (fake_addrinfo(name, port, hints, ai, callback, arg)) {
return;
}
diff --git a/deps/cares/src/lib/ares_gethostbyname.c b/deps/cares/src/lib/ares_gethostbyname.c
index d451b4685110ec..640df74cfc36c3 100644
--- a/deps/cares/src/lib/ares_gethostbyname.c
+++ b/deps/cares/src/lib/ares_gethostbyname.c
@@ -102,6 +102,10 @@ void ares_gethostbyname(ares_channel_t *channel, const char *name, int family,
struct ares_addrinfo_hints hints;
struct host_query *ghbn_arg;
+ if (channel == NULL) {
+ return;
+ }
+
if (!callback) {
return;
}
diff --git a/deps/cares/src/lib/ares_hosts_file.c b/deps/cares/src/lib/ares_hosts_file.c
index c0fb80766a7e9b..0c8aebaf6bad9d 100644
--- a/deps/cares/src/lib/ares_hosts_file.c
+++ b/deps/cares/src/lib/ares_hosts_file.c
@@ -674,6 +674,9 @@ static ares_status_t ares_hosts_path(const ares_channel_t *channel,
&dwLength);
ExpandEnvironmentStringsA(tmp, PATH_HOSTS, MAX_PATH);
RegCloseKey(hkeyHosts);
+ if (strlen(PATH_HOSTS)+strlen(WIN_PATH_HOSTS) >= MAX_PATH) {
+ return ARES_ENOTFOUND;
+ }
strcat(PATH_HOSTS, WIN_PATH_HOSTS);
#elif defined(WATT32)
const char *PATH_HOSTS = _w32_GetHostsFile();
diff --git a/deps/cares/src/lib/ares_init.c b/deps/cares/src/lib/ares_init.c
index dc094c4df6eb69..dc3c1e4f537e16 100644
--- a/deps/cares/src/lib/ares_init.c
+++ b/deps/cares/src/lib/ares_init.c
@@ -102,6 +102,20 @@ static int server_sort_cb(const void *data1, const void *data2)
if (s1->consec_failures > s2->consec_failures) {
return 1;
}
+
+ /* Among servers with the same failure count, prefer the one that failed
+ * least recently so that a server that went down long ago (and may have
+ * since recovered) is tried before one that just failed. This also keeps
+ * retries rotating across servers once the failure counts saturate at
+ * SERVER_CONSEC_FAILURES_CAP. Healthy servers all have a zeroed retry
+ * time and fall through to configuration order. */
+ if (s1->next_retry_time.sec != s2->next_retry_time.sec) {
+ return s1->next_retry_time.sec < s2->next_retry_time.sec ? -1 : 1;
+ }
+ if (s1->next_retry_time.usec != s2->next_retry_time.usec) {
+ return s1->next_retry_time.usec < s2->next_retry_time.usec ? -1 : 1;
+ }
+
if (s1->idx < s2->idx) {
return -1;
}
diff --git a/deps/cares/src/lib/ares_library_init.c b/deps/cares/src/lib/ares_library_init.c
index 2b91692baec6d5..10c88de0095793 100644
--- a/deps/cares/src/lib/ares_library_init.c
+++ b/deps/cares/src/lib/ares_library_init.c
@@ -105,6 +105,27 @@ void *ares_realloc_zero(void *ptr, size_t orig_size, size_t new_size)
return p;
}
+void *ares_malloc_zero_array(size_t num, size_t size)
+{
+ size_t total;
+ if (ares_size_t_mul_overflow(num, size, &total)) {
+ return NULL;
+ }
+ return ares_malloc_zero(total);
+}
+
+void *ares_realloc_zero_array(void *ptr, size_t orig_num, size_t new_num,
+ size_t size)
+{
+ size_t orig_total;
+ size_t new_total;
+ if (ares_size_t_mul_overflow(orig_num, size, &orig_total) ||
+ ares_size_t_mul_overflow(new_num, size, &new_total)) {
+ return NULL;
+ }
+ return ares_realloc_zero(ptr, orig_total, new_total);
+}
+
int ares_library_init(int flags)
{
if (ares_initialized) {
diff --git a/deps/cares/src/lib/ares_private.h b/deps/cares/src/lib/ares_private.h
index d6bd426d39c180..04d4576ed203f1 100644
--- a/deps/cares/src/lib/ares_private.h
+++ b/deps/cares/src/lib/ares_private.h
@@ -131,6 +131,13 @@ W32_FUNC const char *_w32_GetHostsFile(void);
#define DEFAULT_SERVER_RETRY_CHANCE 10
#define DEFAULT_SERVER_RETRY_DELAY 5000
+/* Upper bound on the consecutive failure count tracked per server. Only the
+ * relative order of the counts is used for server selection, so magnitude
+ * beyond "clearly down" carries no additional signal. Capping it bounds how
+ * long a server that failed for an extended period sorts behind other failed
+ * servers once it comes back online. */
+#define SERVER_CONSEC_FAILURES_CAP 16
+
typedef void (*ares_query_enqueue_cb)(void *data);
struct ares_query;
@@ -418,6 +425,9 @@ ares_status_t ares_parse_sortlist(struct apattern **sortlist, size_t *nsort,
ares_status_t ares_lookup_hostaliases(const ares_channel_t *channel,
const char *name, char **alias);
+ares_bool_t ares_parse_port(const char *str, unsigned short *port,
+ ares_bool_t allow_zero);
+
ares_status_t ares_cat_domain(const char *name, const char *domain, char **s);
ares_status_t ares_sortaddrinfo(ares_channel_t *channel,
struct ares_addrinfo_node *ai_node);
@@ -539,10 +549,15 @@ void ares_gethostbyaddr_nolock(ares_channel_t *channel, const void *addr,
* ares_free()'d by the caller.
* \param[in] is_hostname if ARES_TRUE, will validate the character set for
* a valid hostname or will return error.
+ * \param[in] allow_compression if ARES_FALSE, a compression pointer
+ * encountered while parsing the name will be rejected
+ * with ARES_EBADNAME. Used for RR types (e.g. SRV)
+ * whose RDATA names must not use name compression.
* \return ARES_SUCCESS on success
*/
ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
- ares_bool_t is_hostname);
+ ares_bool_t is_hostname,
+ ares_bool_t allow_compression);
/*! Write the DNS name to the buffer in the DNS domain-name syntax as a
* series of labels. The maximum domain name length is 255 characters with
diff --git a/deps/cares/src/lib/ares_process.c b/deps/cares/src/lib/ares_process.c
index 9352968419ef8e..b2316c4b333cab 100644
--- a/deps/cares/src/lib/ares_process.c
+++ b/deps/cares/src/lib/ares_process.c
@@ -45,7 +45,6 @@
#include
-static void timeadd(ares_timeval_t *now, size_t millisecs);
static ares_status_t process_write(ares_channel_t *channel,
ares_socket_t write_fd);
static ares_status_t process_read(ares_channel_t *channel,
@@ -66,6 +65,11 @@ static void end_query(ares_channel_t *channel, ares_server_t *server,
ares_query_t *query, ares_status_t status,
ares_dns_record_t *dnsrec,
ares_array_t **requeue);
+static ares_status_t ares_send_query_int(ares_server_t *requested_server,
+ ares_query_t *query,
+ const ares_timeval_t *now,
+ ares_array_t **requeue);
+static void ares_detach_query(ares_query_t *query);
static void ares_query_remove_from_conn(ares_query_t *query)
{
@@ -124,13 +128,22 @@ static void server_increment_failures(ares_server_t *server,
return; /* LCOV_EXCL_LINE: DefensiveCoding */
}
- server->consec_failures++;
- ares_slist_node_reinsert(node);
+ /* Cap the failure count. Only the relative order matters for server
+ * selection, and an uncapped count would require an unbounded number of
+ * failures on other servers before a server that failed during an
+ * extended outage could be selected again. */
+ if (server->consec_failures < SERVER_CONSEC_FAILURES_CAP) {
+ server->consec_failures++;
+ }
+ /* Must update the retry time before reinserting since the sort uses it to
+ * order servers with the same failure count */
ares_tvnow(&next_retry_time);
- timeadd(&next_retry_time, channel->server_retry_delay);
+ ares_timeval_add(&next_retry_time, channel->server_retry_delay);
server->next_retry_time = next_retry_time;
+ ares_slist_node_reinsert(node);
+
invoke_server_state_cb(server, ARES_FALSE,
used_tcp == ARES_TRUE ? ARES_SERV_STATE_TCP
: ARES_SERV_STATE_UDP);
@@ -148,12 +161,13 @@ static void server_set_good(ares_server_t *server, ares_bool_t used_tcp)
if (server->consec_failures > 0) {
server->consec_failures = 0;
+ /* Must update the retry time before reinserting since the sort uses it
+ * to order servers with the same failure count */
+ server->next_retry_time.sec = 0;
+ server->next_retry_time.usec = 0;
ares_slist_node_reinsert(node);
}
- server->next_retry_time.sec = 0;
- server->next_retry_time.usec = 0;
-
invoke_server_state_cb(server, ARES_TRUE,
used_tcp == ARES_TRUE ? ARES_SERV_STATE_TCP
: ARES_SERV_STATE_UDP);
@@ -178,18 +192,6 @@ ares_bool_t ares_timedout(const ares_timeval_t *now,
: ARES_FALSE;
}
-/* add the specific number of milliseconds to the time in the first argument */
-static void timeadd(ares_timeval_t *now, size_t millisecs)
-{
- now->sec += (ares_int64_t)millisecs / 1000;
- now->usec += (unsigned int)((millisecs % 1000) * 1000);
-
- if (now->usec >= 1000000) {
- now->sec += now->usec / 1000000;
- now->usec %= 1000000;
- }
-}
-
static ares_status_t ares_process_fds_nolock(ares_channel_t *channel,
const ares_fd_events_t *events,
size_t nevents, unsigned int flags)
@@ -445,12 +447,19 @@ void ares_process_pending_write(ares_channel_t *channel)
ares_channel_unlock(channel);
}
-static ares_status_t read_conn_packets(ares_conn_t *conn)
+static ares_status_t read_conn_packets(ares_conn_t *conn,
+ ares_bool_t *conn_error)
{
ares_bool_t read_again;
ares_conn_err_t err;
const ares_channel_t *channel = conn->server->channel;
+ if (conn_error == NULL) {
+ return ARES_EFORMERR;
+ }
+
+ *conn_error = ARES_FALSE;
+
do {
size_t count;
size_t len = 65535;
@@ -498,6 +507,18 @@ static ares_status_t read_conn_packets(ares_conn_t *conn)
/* If UDP, overwrite length */
if (!(conn->flags & ARES_CONN_FLAG_TCP)) {
+ /* The read buffer is grown in powers of two, so a single recvfrom() can
+ * return more than the 2-byte length prefix is able to represent. A
+ * datagram that large can't be a valid DNS message, and writing the
+ * truncated (unsigned short)count would desync the framing of everything
+ * buffered after it, so discard it. Standard UDP can't actually deliver
+ * a payload this large (max is 65507 IPv4 / 65527 IPv6); the only vector
+ * is IPv6 jumbograms (RFC 2675), which DNS never uses. This is purely
+ * defense-in-depth. */
+ if (count > 65535) {
+ ares_buf_set_length(conn->in_buf, start_len);
+ break;
+ }
len = ares_buf_len(conn->in_buf);
ares_buf_set_length(conn->in_buf, start_len);
ares_buf_append_be16(conn->in_buf, (unsigned short)count);
@@ -508,8 +529,15 @@ static ares_status_t read_conn_packets(ares_conn_t *conn)
} while (read_again);
if (err != ARES_CONN_ERR_SUCCESS && err != ARES_CONN_ERR_WOULDBLOCK) {
- handle_conn_error(conn, ARES_TRUE, ARES_ECONNREFUSED);
- return ARES_ECONNREFUSED;
+ /* If there is no packet data buffered, preserve the historical
+ * immediate connection-failure behavior so retries happen promptly.
+ * Only defer if there is buffered data to parse first. */
+ if (ares_buf_len(conn->in_buf) == 0) {
+ handle_conn_error(conn, ARES_TRUE, ARES_ECONNREFUSED);
+ return ARES_ECONNREFUSED;
+ }
+
+ *conn_error = ARES_TRUE;
}
return ARES_SUCCESS;
@@ -573,6 +601,79 @@ static ares_status_t ares_append_endqueue(ares_array_t **requeue,
dnsrec);
}
+/* Drain the deferred requeue/endqueue list iteratively. All flush sites
+ * (read_answers(), process_timeouts(), and ares_send_query()) funnel through
+ * here so that:
+ * 1. Retries are re-dispatched by appending to this same list and looping,
+ * rather than recursing ares_requeue_query() -> ares_send_query() until
+ * the stack is exhausted (issue #1043).
+ * 2. A query is fully detached from all lookup lists before its callback is
+ * invoked, so a reentrant ares_cancel() from within that callback cannot
+ * find and free the same query, which would otherwise double-free it
+ * (CVE-2026-33630 / GHSA-6wfj-rwm7-3542).
+ *
+ * On return the list has been fully processed, an empty-queue notification has
+ * been sent if appropriate, and *requeue has been destroyed and set to NULL. */
+static ares_status_t ares_flush_requeue(ares_channel_t *channel,
+ const ares_timeval_t *now,
+ ares_array_t **requeue)
+{
+ ares_status_t status = ARES_SUCCESS;
+
+ if (requeue == NULL) {
+ return status;
+ }
+
+ while (*requeue != NULL && ares_array_len(*requeue) > 0) {
+ ares_query_t *query;
+ ares_requeue_t entry;
+ ares_status_t internal_status;
+
+ internal_status = ares_array_claim_at(&entry, sizeof(entry), *requeue, 0);
+ if (internal_status != ARES_SUCCESS) {
+ break; /* LCOV_EXCL_LINE: DefensiveCoding */
+ }
+
+ query = ares_htable_szvp_get_direct(channel->queries_by_qid, entry.qid);
+
+ if (entry.type == REQUEUE_REQUEUE) {
+ /* Query disappeared (e.g. a prior callback in this drain cancelled it) */
+ if (query == NULL) {
+ continue;
+ }
+ /* Re-dispatch via the internal entrypoint so any further requeues are
+ * appended back onto this same list and drained by the loop above,
+ * rather than recursing. */
+ internal_status = ares_send_query_int(entry.server, query, now, requeue);
+ /* We only care about ARES_ENOMEM */
+ if (internal_status == ARES_ENOMEM) {
+ status = ARES_ENOMEM;
+ }
+ } else { /* REQUEUE_ENDQUERY */
+ if (query != NULL) {
+ /* Detach the query from all lookup lists BEFORE invoking the callback.
+ * Otherwise a reentrant ares_cancel() from within the callback would
+ * find this query still linked in all_queries/queries_by_qid, free it,
+ * and the ares_free_query() below would then double-free it. */
+ ares_detach_query(query);
+ query->callback(query->arg, entry.status, query->timeouts,
+ entry.dnsrec);
+ ares_free_query(query);
+ }
+ ares_dns_record_destroy(entry.dnsrec);
+ }
+ }
+
+ /* Don't forget to send notification if queue emptied */
+ if (*requeue != NULL) {
+ ares_queue_notify_empty(channel);
+ }
+ ares_array_destroy(*requeue);
+ *requeue = NULL;
+
+ return status;
+}
+
static ares_status_t read_answers(ares_conn_t *conn, const ares_timeval_t *now)
{
ares_status_t status;
@@ -625,43 +726,11 @@ static ares_status_t read_answers(ares_conn_t *conn, const ares_timeval_t *now)
}
cleanup:
-
- /* Flush requeue */
- while (ares_array_len(requeue) > 0) {
- ares_query_t *query;
- ares_requeue_t entry;
- ares_status_t internal_status;
-
- internal_status = ares_array_claim_at(&entry, sizeof(entry), requeue, 0);
- if (internal_status != ARES_SUCCESS) {
- break;
- }
-
- query = ares_htable_szvp_get_direct(channel->queries_by_qid, entry.qid);
-
- if (entry.type == REQUEUE_REQUEUE) {
- /* query disappeared */
- if (query == NULL) {
- continue;
- }
- internal_status = ares_send_query(entry.server, query, now);
- /* We only care about ARES_ENOMEM */
- if (internal_status == ARES_ENOMEM) {
- status = ARES_ENOMEM;
- }
- } else { /* REQUEUE_ENDQUERY */
- if (query != NULL) {
- query->callback(query->arg, entry.status, query->timeouts, entry.dnsrec);
- ares_free_query(query);
- }
- ares_dns_record_destroy(entry.dnsrec);
- }
- }
- /* Don't forget to send notification if queue emptied */
- if (requeue != NULL) {
- ares_queue_notify_empty(channel);
+ /* Flush requeue - re-dispatch retries and invoke deferred callbacks
+ * iteratively and safely */
+ if (ares_flush_requeue(channel, now, &requeue) == ARES_ENOMEM) {
+ status = ARES_ENOMEM;
}
- ares_array_destroy(requeue);
return status;
}
@@ -671,24 +740,32 @@ static ares_status_t process_read(ares_channel_t *channel,
const ares_timeval_t *now)
{
ares_conn_t *conn = ares_conn_from_fd(channel, read_fd);
+ ares_bool_t conn_error;
ares_status_t status;
if (conn == NULL) {
return ARES_SUCCESS;
}
- /* TODO: There might be a potential issue here where there was a read that
- * read some data, then looped and read again and got a disconnect.
- * Right now, that would cause a resend instead of processing the data
- * we have. This is fairly unlikely to occur due to only looping if
- * a full buffer of 65535 bytes was read. */
- status = read_conn_packets(conn);
+ status = read_conn_packets(conn, &conn_error);
if (status != ARES_SUCCESS) {
return status;
}
- return read_answers(conn, now);
+ status = read_answers(conn, now);
+ if (status != ARES_SUCCESS) {
+ return status;
+ }
+
+ if (conn_error) {
+ conn = ares_conn_from_fd(channel, read_fd);
+ if (conn != NULL) {
+ handle_conn_error(conn, ARES_TRUE, ARES_ECONNREFUSED);
+ }
+ }
+
+ return ARES_SUCCESS;
}
/* If any queries have timed out, note the timeout and move them on. */
@@ -696,7 +773,8 @@ static ares_status_t process_timeouts(ares_channel_t *channel,
const ares_timeval_t *now)
{
ares_slist_node_t *node;
- ares_status_t status = ARES_SUCCESS;
+ ares_status_t status = ARES_SUCCESS;
+ ares_array_t *requeue = NULL;
/* Just keep popping off the first as this list will re-sort as things come
* and go. We don't want to try to rely on 'next' as some operation might
@@ -714,14 +792,26 @@ static ares_status_t process_timeouts(ares_channel_t *channel,
query->timeouts++;
conn = query->conn;
+ /* Retire this connection for NEW queries. A timeout suggests packets are
+ * being dropped on it, so route new queries to a fresh source port while
+ * the in-flight queries here drain (it is cleaned up once idle). This is
+ * per-connection so a transient failure doesn't stop reuse of healthy
+ * connections to the same server. */
+ conn->flags |= ARES_CONN_FLAG_NONEW;
server_increment_failures(conn->server, query->using_tcp);
- status = ares_requeue_query(query, now, ARES_ETIMEOUT, ARES_TRUE, NULL,
- NULL);
+ status =
+ ares_requeue_query(query, now, ARES_ETIMEOUT, ARES_TRUE, NULL, &requeue);
if (status == ARES_ENOMEM) {
goto done;
}
}
done:
+ /* Flush requeue - re-dispatch retries and invoke deferred callbacks
+ * iteratively and safely */
+ if (ares_flush_requeue(channel, now, &requeue) == ARES_ENOMEM) {
+ status = ARES_ENOMEM;
+ }
+
if (status == ARES_ENOMEM) {
return ARES_ENOMEM;
}
@@ -871,7 +961,7 @@ static ares_status_t process_answer(ares_channel_t *channel,
if (issue_might_be_edns(query->query, rdnsrec)) {
status = rewrite_without_edns(query);
if (status != ARES_SUCCESS) {
- end_query(channel, server, query, status, NULL, NULL);
+ end_query(channel, server, query, status, NULL, requeue);
goto cleanup;
}
@@ -975,6 +1065,11 @@ ares_status_t ares_requeue_query(ares_query_t *query, const ares_timeval_t *now,
{
ares_channel_t *channel = query->channel;
size_t max_tries = ares_slist_len(channel->servers) * channel->tries;
+ ares_server_t *server = NULL;
+
+ if (query->conn != NULL) {
+ server = query->conn->server;
+ }
ares_query_remove_from_conn(query);
@@ -999,7 +1094,7 @@ ares_status_t ares_requeue_query(ares_query_t *query, const ares_timeval_t *now,
query->error_status = ARES_ETIMEOUT;
}
- end_query(channel, NULL, query, query->error_status, dnsrec, requeue);
+ end_query(channel, server, query, query->error_status, dnsrec, requeue);
return ARES_ETIMEOUT;
}
@@ -1102,22 +1197,23 @@ static void ares_probe_failed_server(ares_channel_t *channel,
}
/* Select the first server with failures to retry that has passed the retry
- * timeout and doesn't already have a pending probe */
+ * timeout and doesn't already have a pending probe. Skip the server the
+ * triggering query was just sent to since that query is already exercising
+ * it. */
ares_tvnow(&now);
for (node = ares_slist_node_first(channel->servers); node != NULL;
node = ares_slist_node_next(node)) {
ares_server_t *node_val = ares_slist_node_val(node);
- if (node_val != NULL && node_val->consec_failures > 0 &&
- !node_val->probe_pending &&
+ if (node_val != NULL && node_val != server &&
+ node_val->consec_failures > 0 && !node_val->probe_pending &&
ares_timedout(&now, &node_val->next_retry_time)) {
probe_server = node_val;
break;
}
}
- /* Either nothing to probe or the query was enqueud to the same server
- * we were going to probe. Do nothing. */
- if (probe_server == NULL || server == probe_server) {
+ /* Nothing to probe */
+ if (probe_server == NULL) {
return;
}
@@ -1148,7 +1244,12 @@ static size_t ares_calc_query_timeout(const ares_query_t *query,
* retry from the last retry */
rounds = (query->try_count / num_servers);
if (rounds > 0) {
- timeplus <<= rounds;
+ if (rounds >= sizeof(timeplus) * CHAR_BIT ||
+ timeplus > (SIZE_MAX >> rounds)) {
+ timeplus = SIZE_MAX;
+ } else {
+ timeplus <<= rounds;
+ }
}
if (channel->maxtimeout && timeplus > channel->maxtimeout) {
@@ -1205,9 +1306,13 @@ static ares_conn_t *ares_fetch_connection(const ares_channel_t *channel,
return NULL;
}
- /* If the associated server has failures, don't use it. It should be cleaned
- * up later. */
- if (conn->server->consec_failures > 0) {
+ /* Don't hand new queries to a connection that has been retired (e.g. it saw
+ * a timeout). It keeps servicing its in-flight queries and is cleaned up
+ * once idle. Note this is a per-connection check: a server-wide failure
+ * counter must not be used here or a single transient failure would evict
+ * every (including healthy) connection to the server and spawn a new socket
+ * per query. */
+ if (conn->flags & ARES_CONN_FLAG_NONEW) {
return NULL;
}
@@ -1262,8 +1367,44 @@ static ares_status_t ares_conn_query_write(ares_conn_t *conn,
return ares_conn_flush(conn);
}
+/* Public entrypoint. Establishes a requeue list and drives
+ * ares_send_query_int() plus any retries/deferred callbacks it produces
+ * iteratively, so a chain of retryable failures can never recurse until the
+ * stack is exhausted (#1043). */
ares_status_t ares_send_query(ares_server_t *requested_server,
ares_query_t *query, const ares_timeval_t *now)
+{
+ ares_channel_t *channel = query->channel;
+ ares_array_t *requeue = NULL;
+ unsigned short qid = query->qid;
+ ares_status_t status;
+
+ status = ares_send_query_int(requested_server, query, now, &requeue);
+
+ /* Drain any retries/deferred callbacks this send produced.
+ * ares_flush_requeue() always fully processes and destroys the list (even on
+ * ENOMEM), and sends the empty-queue notification if needed. */
+ if (ares_flush_requeue(channel, now, &requeue) == ARES_ENOMEM) {
+ status = ARES_ENOMEM;
+ }
+
+ /* A retry may have been deferred (returning ARES_SUCCESS from the append)
+ * and then terminally failed while draining, in which case the query has
+ * been freed. Do not dereference 'query' here. If it is no longer tracked
+ * it ended, so don't report success to the caller (which would, e.g., cause
+ * ares_send_nolock() to write to a now-freed *qid). */
+ if (status == ARES_SUCCESS &&
+ ares_htable_szvp_get_direct(channel->queries_by_qid, qid) == NULL) {
+ status = ARES_ETIMEOUT;
+ }
+
+ return status;
+}
+
+static ares_status_t ares_send_query_int(ares_server_t *requested_server,
+ ares_query_t *query,
+ const ares_timeval_t *now,
+ ares_array_t **requeue)
{
ares_channel_t *channel = query->channel;
ares_server_t *server;
@@ -1286,14 +1427,16 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
}
if (server == NULL) {
- end_query(channel, server, query, ARES_ENOSERVER /* ? */, NULL, NULL);
+ end_query(channel, server, query, ARES_ENOSERVER /* ? */, NULL, requeue);
return ARES_ENOSERVER;
}
- /* If a query is directed to a specific query, or the server chosen has
- * failures, or the query is being retried, don't probe for downed servers */
- if (requested_server != NULL || server->consec_failures > 0 ||
- query->try_count != 0) {
+ /* If a query is directed to a specific server, or the query is being
+ * retried, don't probe for downed servers. Note that the chosen server
+ * having failures itself does NOT disable probing: when every server has
+ * failures (e.g. during an extended outage), probes are the only way a
+ * recovered server that sorts behind the current best can be noticed. */
+ if (requested_server != NULL || query->try_count != 0) {
probe_downed_server = ARES_FALSE;
}
@@ -1310,11 +1453,11 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
case ARES_ECONNREFUSED:
case ARES_EBADFAMILY:
server_increment_failures(server, query->using_tcp);
- return ares_requeue_query(query, now, status, ARES_TRUE, NULL, NULL);
+ return ares_requeue_query(query, now, status, ARES_TRUE, NULL, requeue);
/* Anything else is not retryable, likely ENOMEM */
default:
- end_query(channel, server, query, status, NULL, NULL);
+ end_query(channel, server, query, status, NULL, requeue);
return status;
}
}
@@ -1328,7 +1471,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
case ARES_ENOMEM:
/* Not retryable */
- end_query(channel, server, query, status, NULL, NULL);
+ end_query(channel, server, query, status, NULL, requeue);
return status;
/* These conditions are retryable as they are server-specific
@@ -1336,7 +1479,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
case ARES_ECONNREFUSED:
case ARES_EBADFAMILY:
handle_conn_error(conn, ARES_TRUE, status);
- status = ares_requeue_query(query, now, status, ARES_TRUE, NULL, NULL);
+ status = ares_requeue_query(query, now, status, ARES_TRUE, NULL, requeue);
if (status == ARES_ETIMEOUT) {
status = ARES_ECONNREFUSED;
}
@@ -1344,7 +1487,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
default:
server_increment_failures(server, query->using_tcp);
- status = ares_requeue_query(query, now, status, ARES_TRUE, NULL, NULL);
+ status = ares_requeue_query(query, now, status, ARES_TRUE, NULL, requeue);
return status;
}
@@ -1355,12 +1498,12 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
ares_slist_node_destroy(query->node_queries_by_timeout);
query->ts = *now;
query->timeout = *now;
- timeadd(&query->timeout, timeplus);
+ ares_timeval_add(&query->timeout, timeplus);
query->node_queries_by_timeout =
ares_slist_insert(channel->queries_by_timeout, query);
if (!query->node_queries_by_timeout) {
/* LCOV_EXCL_START: OutOfMemory */
- end_query(channel, server, query, ARES_ENOMEM, NULL, NULL);
+ end_query(channel, server, query, ARES_ENOMEM, NULL, requeue);
return ARES_ENOMEM;
/* LCOV_EXCL_STOP */
}
@@ -1373,7 +1516,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
if (query->node_queries_to_conn == NULL) {
/* LCOV_EXCL_START: OutOfMemory */
- end_query(channel, server, query, ARES_ENOMEM, NULL, NULL);
+ end_query(channel, server, query, ARES_ENOMEM, NULL, requeue);
return ARES_ENOMEM;
/* LCOV_EXCL_STOP */
}
diff --git a/deps/cares/src/lib/ares_qcache.c b/deps/cares/src/lib/ares_qcache.c
index 4050ff54bf1667..ad3bc83505af07 100644
--- a/deps/cares/src/lib/ares_qcache.c
+++ b/deps/cares/src/lib/ares_qcache.c
@@ -371,9 +371,11 @@ static ares_status_t ares_qcache_insert_int(ares_qcache_t *qcache,
/* LCOV_EXCL_START: OutOfMemory */
fail:
- if (entry != NULL && entry->key != NULL) {
- ares_htable_strvp_remove(qcache->cache, entry->key);
- ares_free(entry->key);
+ if (entry != NULL) {
+ if (entry->key != NULL) {
+ ares_htable_strvp_remove(qcache->cache, entry->key);
+ ares_free(entry->key);
+ }
ares_free(entry);
}
return ARES_ENOMEM;
diff --git a/deps/cares/src/lib/ares_socket.c b/deps/cares/src/lib/ares_socket.c
index 516852a84abfb8..4ad3034021a0f7 100644
--- a/deps/cares/src/lib/ares_socket.c
+++ b/deps/cares/src/lib/ares_socket.c
@@ -188,6 +188,8 @@ ares_conn_err_t ares_socket_recvfrom(ares_channel_t *channel, ares_socket_t s,
{
ares_ssize_t rv;
+ *read_bytes = 0;
+
rv = channel->sock_funcs.arecvfrom(s, data, data_len, flags, from, from_len,
channel->sock_func_cb_data);
diff --git a/deps/cares/src/lib/ares_sysconfig.c b/deps/cares/src/lib/ares_sysconfig.c
index 286db60328f45b..9e389c17672a1f 100644
--- a/deps/cares/src/lib/ares_sysconfig.c
+++ b/deps/cares/src/lib/ares_sysconfig.c
@@ -218,13 +218,16 @@ static ares_status_t ares_init_sysconfig_android(const ares_channel_t *channel,
status = ares_sconfig_append_fromstr(channel, &sysconfig->sconfig,
dns_servers[i], ARES_TRUE);
if (status != ARES_SUCCESS) {
- return status;
+ break;
}
}
for (i = 0; i < num_servers; i++) {
ares_free(dns_servers[i]);
}
ares_free(dns_servers);
+ if (status != ARES_SUCCESS) {
+ return status;
+ }
}
domains = ares_get_android_search_domains_list();
@@ -273,7 +276,7 @@ static ares_status_t
* 3. if confstr(_CS_DOMAIN, ...) this is the domain name. Use this as
* preference over anything else found.
*/
- ares_buf_t *buf = ares_buf_create();
+ ares_buf_t *buf = NULL;
unsigned char *data = NULL;
size_t data_size = 0;
ares_bool_t process_resolvconf = ARES_TRUE;
@@ -330,7 +333,7 @@ static ares_status_t
char domain[256];
size_t domain_len;
- domain_len = confstr(_CS_DOMAIN, domain, sizeof(domain_len));
+ domain_len = confstr(_CS_DOMAIN, domain, sizeof(domain));
if (domain_len != 0) {
ares_strsplit_free(sysconfig->domains, sysconfig->ndomains);
sysconfig->domains = ares_strsplit(domain, ", ", &sysconfig->ndomains);
diff --git a/deps/cares/src/lib/ares_sysconfig_files.c b/deps/cares/src/lib/ares_sysconfig_files.c
index a6c2a8e62bb34f..2caa3b200075b8 100644
--- a/deps/cares/src/lib/ares_sysconfig_files.c
+++ b/deps/cares/src/lib/ares_sysconfig_files.c
@@ -27,6 +27,8 @@
#include "ares_private.h"
+#include
+
#ifdef HAVE_SYS_PARAM_H
# include
#endif
@@ -170,8 +172,8 @@ static ares_status_t parse_sort(ares_buf_t *buf, struct apattern *pat)
if (ares_str_isnum(maskstr)) {
/* Numeric mask */
- int mask = atoi(maskstr);
- if (mask < 0 || mask > 128) {
+ unsigned int mask;
+ if (!ares_str_parse_uint(maskstr, 128, &mask)) {
return ARES_EBADSTR;
}
if (pat->addr.family == AF_INET && mask > 32) {
@@ -340,12 +342,17 @@ static ares_status_t config_lookup(ares_sysconfig_t *sysconfig, ares_buf_t *buf,
const char *value = lookups[i];
char ch;
+ /* AIX /etc/netsvc.conf uses address-family-suffixed tokens such as
+ * "bind4"/"bind6" and "local4"/"local6" in addition to the bare forms. */
if (ares_strcaseeq(value, "dns") || ares_strcaseeq(value, "bind") ||
+ ares_strcaseeq(value, "bind4") || ares_strcaseeq(value, "bind6") ||
ares_strcaseeq(value, "resolv") || ares_strcaseeq(value, "resolve")) {
ch = 'b';
} else if (ares_strcaseeq(value, "files") ||
ares_strcaseeq(value, "file") ||
- ares_strcaseeq(value, "local")) {
+ ares_strcaseeq(value, "local") ||
+ ares_strcaseeq(value, "local4") ||
+ ares_strcaseeq(value, "local6")) {
ch = 'f';
} else {
continue;
@@ -401,20 +408,25 @@ static ares_status_t process_option(ares_sysconfig_t *sysconfig,
key = kv[0];
if (num == 2) {
- val = kv[1];
- valint = (unsigned int)strtoul(val, NULL, 10);
+ val = kv[1];
+ if (!ares_str_parse_uint(val, UINT_MAX, &valint)) {
+ status = ARES_EBADSTR;
+ goto done;
+ }
}
if (ares_streq(key, "ndots")) {
sysconfig->ndots = valint;
} else if (ares_streq(key, "retrans") || ares_streq(key, "timeout")) {
if (valint == 0) {
- return ARES_EFORMERR;
+ status = ARES_EFORMERR;
+ goto done;
}
sysconfig->timeout_ms = valint * 1000;
} else if (ares_streq(key, "retry") || ares_streq(key, "attempts")) {
if (valint == 0) {
- return ARES_EFORMERR;
+ status = ARES_EFORMERR;
+ goto done;
}
sysconfig->tries = valint;
} else if (ares_streq(key, "rotate")) {
diff --git a/deps/cares/src/lib/ares_sysconfig_win.c b/deps/cares/src/lib/ares_sysconfig_win.c
index 94a3817de348bb..e0daf1103e2cec 100644
--- a/deps/cares/src/lib/ares_sysconfig_win.c
+++ b/deps/cares/src/lib/ares_sysconfig_win.c
@@ -114,56 +114,69 @@ static ares_bool_t get_REG_SZ(HKEY hKey, const WCHAR *leafKeyName, char **outptr
/* Convert to UTF8 */
len = WideCharToMultiByte(CP_UTF8, 0, val, -1, NULL, 0, NULL, NULL);
if (len == 0) {
+ ares_free(val);
return ARES_FALSE;
}
*outptr = ares_malloc_zero((size_t)len + 1);
+ if (*outptr == NULL) {
+ ares_free(val);
+ return ARES_FALSE;
+ }
if (WideCharToMultiByte(CP_UTF8, 0, val, -1, *outptr, len, NULL, NULL)
== 0) {
ares_free(*outptr);
*outptr = NULL;
+ ares_free(val);
return ARES_FALSE;
}
+ ares_free(val);
return ARES_TRUE;
}
-static void commanjoin(char **dst, const char * const src, const size_t len)
+static ares_status_t sysconfig_commajoin(ares_buf_t *buf, const char *src,
+ ares_bool_t asciionly)
{
- char *newbuf;
- size_t newsize;
+ ares_status_t status;
- /* 1 for terminating 0 and 2 for , and terminating 0 */
- newsize = len + (*dst ? (ares_strlen(*dst) + 2) : 1);
- newbuf = ares_realloc(*dst, newsize);
- if (!newbuf) {
- return;
+ if (buf == NULL) {
+ return ARES_ENOMEM;
}
- if (*dst == NULL) {
- *newbuf = '\0';
+
+ if (src == NULL || *src == '\0') {
+ return ARES_SUCCESS;
}
- *dst = newbuf;
- if (ares_strlen(*dst) != 0) {
- strcat(*dst, ",");
+
+ if (asciionly && !ares_str_isprint(src, ares_strlen(src))) {
+ return ARES_SUCCESS;
}
- strncat(*dst, src, len);
-}
-/*
- * commajoin()
- *
- * RTF code.
- */
-static void commajoin(char **dst, const char *src)
-{
- commanjoin(dst, src, ares_strlen(src));
+ if (ares_buf_len(buf) > 0) {
+ status = ares_buf_append_byte(buf, ',');
+ if (status != ARES_SUCCESS) {
+ return status;
+ }
+ }
+
+ return ares_buf_append_str(buf, src);
}
-static void commajoin_asciionly(char **dst, const char *src)
+/* Read a REG_SZ value and comma-append it (ASCII-only) to *buf.
+ * On allocation failure, destroys *buf and sets it to NULL. */
+static void reg_commajoin(ares_buf_t **buf, HKEY key, const WCHAR *leaf)
{
- if (!ares_str_isprint(src, ares_strlen(src))) {
+ char *p = NULL;
+
+ if (*buf == NULL || !get_REG_SZ(key, leaf, &p)) {
return;
}
- commanjoin(dst, src, ares_strlen(src));
+
+ if (sysconfig_commajoin(*buf, p, ARES_TRUE) != ARES_SUCCESS) {
+ ares_buf_destroy(*buf);
+ *buf = NULL;
+ }
+
+ ares_free(p);
}
/* A structure to hold the string form of IPv4 and IPv6 addresses so we can
@@ -494,8 +507,10 @@ static ares_bool_t get_DNS_Windows(char **outptr)
/* Join them all into a single string, removing duplicates. */
{
- size_t i;
- for (i = 0; i < addressesIndex; ++i) {
+ size_t i;
+ ares_buf_t *buf = ares_buf_create();
+
+ for (i = 0; buf != NULL && i < addressesIndex; ++i) {
size_t j;
/* Look for this address text appearing previously in the results. */
for (j = 0; j < i; ++j) {
@@ -505,10 +520,22 @@ static ares_bool_t get_DNS_Windows(char **outptr)
}
/* Iff we didn't emit this address already, emit it now. */
if (j == i) {
- /* Add that to outptr (if we can). */
- commajoin(outptr, addresses[i].text);
+ /* Add that to buf (if we can). */
+ if (sysconfig_commajoin(buf, addresses[i].text, ARES_FALSE) !=
+ ARES_SUCCESS) {
+ ares_buf_destroy(buf);
+ buf = NULL;
+ break;
+ }
}
}
+
+ if (buf != NULL && ares_buf_len(buf) == 0) {
+ ares_buf_destroy(buf);
+ buf = NULL;
+ }
+
+ *outptr = ares_buf_finish_str(buf, NULL);
}
done:
@@ -540,51 +567,46 @@ static ares_bool_t get_DNS_Windows(char **outptr)
*/
static ares_bool_t get_SuffixList_Windows(char **outptr)
{
- HKEY hKey;
- HKEY hKeyEnum;
- char keyName[256];
- DWORD keyNameBuffSize;
- DWORD keyIdx = 0;
- char *p = NULL;
+ HKEY hKey;
+ HKEY hKeyEnum;
+ char keyName[256];
+ DWORD keyNameBuffSize;
+ DWORD keyIdx = 0;
+ ares_buf_t *buf = ares_buf_create();
*outptr = NULL;
+ if (buf == NULL) {
+ return ARES_FALSE;
+ }
+
/* 1. Global DNS Suffix Search List */
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ, &hKey) ==
ERROR_SUCCESS) {
- get_REG_SZ(hKey, SEARCHLIST_KEY, outptr);
- if (get_REG_SZ(hKey, DOMAIN_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
+ reg_commajoin(&buf, hKey, SEARCHLIST_KEY);
+ reg_commajoin(&buf, hKey, DOMAIN_KEY);
RegCloseKey(hKey);
}
- if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NT_DNSCLIENT, 0, KEY_READ, &hKey) ==
- ERROR_SUCCESS) {
- if (get_REG_SZ(hKey, SEARCHLIST_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
+ if (buf != NULL &&
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NT_DNSCLIENT, 0, KEY_READ, &hKey) ==
+ ERROR_SUCCESS) {
+ reg_commajoin(&buf, hKey, SEARCHLIST_KEY);
RegCloseKey(hKey);
}
/* 2. Connection Specific Search List composed of:
* a. Primary DNS Suffix */
- if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_DNSCLIENT, 0, KEY_READ, &hKey) ==
- ERROR_SUCCESS) {
- if (get_REG_SZ(hKey, PRIMARYDNSSUFFIX_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
+ if (buf != NULL &&
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_DNSCLIENT, 0, KEY_READ, &hKey) ==
+ ERROR_SUCCESS) {
+ reg_commajoin(&buf, hKey, PRIMARYDNSSUFFIX_KEY);
RegCloseKey(hKey);
}
/* b. Interface SearchList, Domain, DhcpDomain */
- if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY "\\" INTERFACES_KEY, 0,
+ if (buf != NULL &&
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY "\\" INTERFACES_KEY, 0,
KEY_READ, &hKey) == ERROR_SUCCESS) {
for (;;) {
keyNameBuffSize = sizeof(keyName);
@@ -596,27 +618,26 @@ static ares_bool_t get_SuffixList_Windows(char **outptr)
ERROR_SUCCESS) {
continue;
}
- /* p can be comma separated (SearchList) */
- if (get_REG_SZ(hKeyEnum, SEARCHLIST_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
- if (get_REG_SZ(hKeyEnum, DOMAIN_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
- if (get_REG_SZ(hKeyEnum, DHCPDOMAIN_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
+ /* SearchList can be comma separated */
+ reg_commajoin(&buf, hKeyEnum, SEARCHLIST_KEY);
+ reg_commajoin(&buf, hKeyEnum, DOMAIN_KEY);
+ reg_commajoin(&buf, hKeyEnum, DHCPDOMAIN_KEY);
RegCloseKey(hKeyEnum);
+
+ if (buf == NULL) {
+ break;
+ }
}
RegCloseKey(hKey);
}
+ if (ares_buf_len(buf) == 0) {
+ ares_buf_destroy(buf);
+ buf = NULL;
+ }
+
+ *outptr = ares_buf_finish_str(buf, NULL);
+
return *outptr != NULL ? ARES_TRUE : ARES_FALSE;
}
diff --git a/deps/cares/src/lib/ares_timeout.c b/deps/cares/src/lib/ares_timeout.c
index 0d2fdcff21f657..dbd5cf443d68f2 100644
--- a/deps/cares/src/lib/ares_timeout.c
+++ b/deps/cares/src/lib/ares_timeout.c
@@ -32,6 +32,17 @@
#endif
+void ares_timeval_add(ares_timeval_t *now, size_t millisecs)
+{
+ now->sec += (ares_int64_t)millisecs / 1000;
+ now->usec += (unsigned int)((millisecs % 1000) * 1000);
+
+ if (now->usec >= 1000000) {
+ now->sec += now->usec / 1000000;
+ now->usec %= 1000000;
+ }
+}
+
void ares_timeval_remaining(ares_timeval_t *remaining,
const ares_timeval_t *now,
const ares_timeval_t *tout)
diff --git a/deps/cares/src/lib/ares_update_servers.c b/deps/cares/src/lib/ares_update_servers.c
index 70a9381499f8c2..f8d441accf23e1 100644
--- a/deps/cares/src/lib/ares_update_servers.c
+++ b/deps/cares/src/lib/ares_update_servers.c
@@ -27,6 +27,8 @@
*/
#include "ares_private.h"
+#include
+
#ifdef HAVE_ARPA_INET_H
# include
#endif
@@ -234,7 +236,10 @@ static ares_status_t parse_nameserver_uri(ares_buf_t *buf,
sconfig->tcp_port = sconfig->udp_port;
port = ares_uri_get_query_key(uri, "tcpport");
if (port != NULL) {
- sconfig->tcp_port = (unsigned short)atoi(port);
+ if (!ares_parse_port(port, &sconfig->tcp_port, ARES_TRUE)) {
+ status = ARES_EBADSTR;
+ goto done;
+ }
}
done:
@@ -352,7 +357,9 @@ static ares_status_t parse_nameserver(ares_buf_t *buf, ares_sconfig_t *sconfig)
return status;
}
- sconfig->udp_port = (unsigned short)atoi(portstr);
+ if (!ares_parse_port(portstr, &sconfig->udp_port, ARES_TRUE)) {
+ return ARES_EBADSTR;
+ }
sconfig->tcp_port = sconfig->udp_port;
}
@@ -398,7 +405,13 @@ static ares_status_t ares_sconfig_linklocal(const ares_channel_t *channel,
if (ares_str_isnum(ll_iface)) {
char ifname[IF_NAMESIZE] = "";
- ll_scope = (unsigned int)atoi(ll_iface);
+
+ /* The interface identifier is all digits but may not fit in an
+ * unsigned int; parse with the range-checked helper rather than atoi(),
+ * whose behavior on overflow is undefined. */
+ if (!ares_str_parse_uint(ll_iface, UINT_MAX, &ll_scope)) {
+ return ARES_ENOTFOUND;
+ }
if (channel->sock_funcs.aif_indextoname == NULL ||
channel->sock_funcs.aif_indextoname(ll_scope, ifname, sizeof(ifname),
channel->sock_func_cb_data) ==
@@ -967,7 +980,8 @@ ares_status_t ares_in_addr_to_sconfig_llist(const struct in_addr *servers,
sizeof(sconfig->addr.addr.addr4));
if (ares_llist_insert_last(s, sconfig) == NULL) {
- goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
+ ares_free(sconfig); /* LCOV_EXCL_LINE: OutOfMemory */
+ goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
diff --git a/deps/cares/src/lib/dsa/ares_array.c b/deps/cares/src/lib/dsa/ares_array.c
index c421d5c5f670bd..e6bce6e41db91e 100644
--- a/deps/cares/src/lib/dsa/ares_array.c
+++ b/deps/cares/src/lib/dsa/ares_array.c
@@ -171,14 +171,19 @@ void *ares_array_finish(ares_array_t *arr, size_t *num_members)
ares_status_t ares_array_set_size(ares_array_t *arr, size_t size)
{
- void *temp;
+ void *temp;
+ size_t rounded_size;
if (arr == NULL || size == 0 || size < arr->cnt) {
return ARES_EFORMERR;
}
/* Always operate on powers of 2 */
- size = ares_round_up_pow2(size);
+ rounded_size = ares_round_up_pow2(size);
+ if (rounded_size < size) {
+ return ARES_ENOMEM; /* rounding wrapped around */
+ }
+ size = rounded_size;
if (size < ARES__ARRAY_MIN) {
size = ARES__ARRAY_MIN;
@@ -189,8 +194,8 @@ ares_status_t ares_array_set_size(ares_array_t *arr, size_t size)
return ARES_SUCCESS;
}
- temp = ares_realloc_zero(arr->arr, arr->alloc_cnt * arr->member_size,
- size * arr->member_size);
+ temp =
+ ares_realloc_zero_array(arr->arr, arr->alloc_cnt, size, arr->member_size);
if (temp == NULL) {
return ARES_ENOMEM;
}
@@ -295,7 +300,7 @@ ares_status_t ares_array_insertdata_first(ares_array_t *arr,
ares_status_t status;
void *ptr = NULL;
- status = ares_array_insert_last(&ptr, arr);
+ status = ares_array_insert_first(&ptr, arr);
if (status != ARES_SUCCESS) {
return status;
}
@@ -362,6 +367,16 @@ ares_status_t ares_array_claim_at(void *dest, size_t dest_size,
}
arr->cnt--;
+
+ /* When empty, reset offset so a later insert doesn't compact from an
+ * out-of-range index (offset can reach alloc_cnt after front claims).
+ * This also protects ares_array_finish(), which has the same
+ * move(0, offset) and would otherwise fail on an offset == alloc_cnt
+ * array. */
+ if (arr->cnt == 0) {
+ arr->offset = 0;
+ }
+
return ARES_SUCCESS;
}
diff --git a/deps/cares/src/lib/dsa/ares_htable.c b/deps/cares/src/lib/dsa/ares_htable.c
index f76b67cae9a668..d6ac7a065a6b02 100644
--- a/deps/cares/src/lib/dsa/ares_htable.c
+++ b/deps/cares/src/lib/dsa/ares_htable.c
@@ -150,6 +150,10 @@ const void **ares_htable_all_buckets(const ares_htable_t *htable, size_t *num)
*num = 0;
+ if (htable->num_keys == 0) {
+ return NULL;
+ }
+
out = ares_malloc_zero(sizeof(*out) * htable->num_keys);
if (out == NULL) {
return NULL; /* LCOV_EXCL_LINE */
@@ -172,7 +176,7 @@ const void **ares_htable_all_buckets(const ares_htable_t *htable, size_t *num)
* We are doing "hash & (size - 1)" since we are guaranteeing a power of
* 2 for size. This is equivalent to "hash % size", but should be more
* efficient */
-#define HASH_IDX(h, key) h->hash(key, h->seed) & (h->size - 1)
+#define HASH_IDX(h, key) ((h)->hash((key), (h)->seed) & ((h)->size - 1))
static ares_llist_node_t *ares_htable_find(const ares_htable_t *htable,
unsigned int idx, const void *key)
diff --git a/deps/cares/src/lib/dsa/ares_htable_dict.c b/deps/cares/src/lib/dsa/ares_htable_dict.c
index 93d7a20137c8db..cda7a49c79d05d 100644
--- a/deps/cares/src/lib/dsa/ares_htable_dict.c
+++ b/deps/cares/src/lib/dsa/ares_htable_dict.c
@@ -132,6 +132,7 @@ ares_bool_t ares_htable_dict_insert(ares_htable_dict_t *htable, const char *key,
fail:
if (bucket) {
+ ares_free(bucket->key);
ares_free(bucket->val);
ares_free(bucket);
}
@@ -223,6 +224,7 @@ char **ares_htable_dict_keys(const ares_htable_dict_t *htable, size_t *num)
fail:
*num = 0;
+ ares_free(buckets);
ares_free_array(out, cnt, ares_free);
return NULL;
}
diff --git a/deps/cares/src/lib/dsa/ares_llist.c b/deps/cares/src/lib/dsa/ares_llist.c
index 6bd7de269a43fb..ce1a31149401f1 100644
--- a/deps/cares/src/lib/dsa/ares_llist.c
+++ b/deps/cares/src/lib/dsa/ares_llist.c
@@ -103,7 +103,10 @@ static void ares_llist_attach_at(ares_llist_t *list,
case ARES__LLIST_INSERT_BEFORE:
node->next = at;
node->prev = at->prev;
- at->prev = node;
+ if (at->prev) {
+ at->prev->next = node;
+ }
+ at->prev = node;
break;
}
if (list->tail == NULL) {
diff --git a/deps/cares/src/lib/event/ares_event_configchg.c b/deps/cares/src/lib/event/ares_event_configchg.c
index add729574e46c3..d73c4862058c97 100644
--- a/deps/cares/src/lib/event/ares_event_configchg.c
+++ b/deps/cares/src/lib/event/ares_event_configchg.c
@@ -207,12 +207,14 @@ void ares_event_configchg_destroy(ares_event_configchg_t *configchg)
# ifdef HAVE_REGISTERWAITFORSINGLEOBJECT
if (configchg->regip4_wait != NULL) {
- UnregisterWait(configchg->regip4_wait);
+ /* Use INVALID_HANDLE_VALUE to wait for any running callback to complete
+ * before returning, preventing use-after-free of configchg */
+ UnregisterWaitEx(configchg->regip4_wait, INVALID_HANDLE_VALUE);
configchg->regip4_wait = NULL;
}
if (configchg->regip6_wait != NULL) {
- UnregisterWait(configchg->regip6_wait);
+ UnregisterWaitEx(configchg->regip6_wait, INVALID_HANDLE_VALUE);
configchg->regip6_wait = NULL;
}
diff --git a/deps/cares/src/lib/event/ares_event_wake_pipe.c b/deps/cares/src/lib/event/ares_event_wake_pipe.c
index cd1534bbbd586c..2ea72c29415230 100644
--- a/deps/cares/src/lib/event/ares_event_wake_pipe.c
+++ b/deps/cares/src/lib/event/ares_event_wake_pipe.c
@@ -117,7 +117,13 @@ static void ares_pipeevent_signal(const ares_event_t *e)
}
p = e->data;
- (void)write(p->filedes[1], "1", 1);
+ /* Best-effort wakeup. A failed or short write is harmless: it can only
+ * happen when the pipe buffer is already full, which means a wakeup is
+ * already pending. The result is consumed to satisfy warn_unused_result
+ * (a plain (void) cast does not silence it under _FORTIFY_SOURCE). */
+ if (write(p->filedes[1], "1", 1) < 0) {
+ return;
+ }
}
static void ares_pipeevent_cb(ares_event_thread_t *e, ares_socket_t fd,
diff --git a/deps/cares/src/lib/event/ares_event_win32.c b/deps/cares/src/lib/event/ares_event_win32.c
index d7d1d65735082d..d558b6930a66f2 100644
--- a/deps/cares/src/lib/event/ares_event_win32.c
+++ b/deps/cares/src/lib/event/ares_event_win32.c
@@ -707,6 +707,9 @@ static ares_bool_t ares_evsys_win32_event_add(ares_event_t *event)
ares_bool_t rc = ARES_FALSE;
ed = ares_malloc_zero(sizeof(*ed));
+ if (ed == NULL) {
+ return ARES_FALSE; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
ed->event = event;
ed->socket = event->fd;
ed->base_socket = ARES_SOCKET_BAD;
diff --git a/deps/cares/src/lib/include/ares_mem.h b/deps/cares/src/lib/include/ares_mem.h
index 371cd4266dc720..d980923e5817a2 100644
--- a/deps/cares/src/lib/include/ares_mem.h
+++ b/deps/cares/src/lib/include/ares_mem.h
@@ -34,5 +34,8 @@ CARES_EXTERN void ares_free(void *ptr);
CARES_EXTERN void *ares_malloc_zero(size_t size);
CARES_EXTERN void *ares_realloc_zero(void *ptr, size_t orig_size,
size_t new_size);
+CARES_EXTERN void *ares_malloc_zero_array(size_t num, size_t size);
+CARES_EXTERN void *ares_realloc_zero_array(void *ptr, size_t orig_num,
+ size_t new_num, size_t size);
#endif
diff --git a/deps/cares/src/lib/include/ares_str.h b/deps/cares/src/lib/include/ares_str.h
index 4ee339510bf026..c7fdf4fc4c8059 100644
--- a/deps/cares/src/lib/include/ares_str.h
+++ b/deps/cares/src/lib/include/ares_str.h
@@ -60,6 +60,23 @@ CARES_EXTERN size_t ares_strcpy(char *dest, const char *src, size_t dest_size);
CARES_EXTERN ares_bool_t ares_str_isnum(const char *str);
CARES_EXTERN ares_bool_t ares_str_isalnum(const char *str);
+/*! Parse a base-10 unsigned integer from a NULL-terminated string. Unlike
+ * atoi(), this rejects overflow, leading or trailing garbage (a sign,
+ * whitespace, or non-digit characters), and any value that does not fit
+ * within the caller-provided maximum.
+ *
+ * \param[in] str String to parse. May be NULL or empty, in which case
+ * ARES_FALSE is returned.
+ * \param[in] max Largest value considered valid. Values above UINT_MAX
+ * are capped to UINT_MAX since out is an unsigned int.
+ * \param[out] out On success, receives the parsed value. Untouched on
+ * failure.
+ * \return ARES_TRUE if a valid in-range integer was parsed, ARES_FALSE
+ * otherwise.
+ */
+CARES_EXTERN ares_bool_t ares_str_parse_uint(const char *str, unsigned long max,
+ unsigned int *out);
+
CARES_EXTERN void ares_str_ltrim(char *str);
CARES_EXTERN void ares_str_rtrim(char *str);
CARES_EXTERN void ares_str_trim(char *str);
diff --git a/deps/cares/src/lib/legacy/ares_expand_name.c b/deps/cares/src/lib/legacy/ares_expand_name.c
index 72668f4cb60a07..ee1d70076be815 100644
--- a/deps/cares/src/lib/legacy/ares_expand_name.c
+++ b/deps/cares/src/lib/legacy/ares_expand_name.c
@@ -69,7 +69,7 @@ ares_status_t ares_expand_name_validated(const unsigned char *encoded,
}
start_len = ares_buf_len(buf);
- status = ares_dns_name_parse(buf, s, is_hostname);
+ status = ares_dns_name_parse(buf, s, is_hostname, ARES_TRUE);
if (status != ARES_SUCCESS) {
goto done;
}
diff --git a/deps/cares/src/lib/legacy/ares_parse_ns_reply.c b/deps/cares/src/lib/legacy/ares_parse_ns_reply.c
index fc9ab9219d399c..7013ce28b90131 100644
--- a/deps/cares/src/lib/legacy/ares_parse_ns_reply.c
+++ b/deps/cares/src/lib/legacy/ares_parse_ns_reply.c
@@ -97,12 +97,12 @@ int ares_parse_ns_reply(const unsigned char *abuf, int alen_int,
}
/* Preallocate the maximum number + 1 */
- hostent->h_aliases = ares_malloc((ancount + 1) * sizeof(*hostent->h_aliases));
+ hostent->h_aliases =
+ ares_malloc_zero_array(ancount + 1, sizeof(*hostent->h_aliases));
if (hostent->h_aliases == NULL) {
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
- memset(hostent->h_aliases, 0, (ancount + 1) * sizeof(*hostent->h_aliases));
for (i = 0; i < ancount; i++) {
const ares_dns_rr_t *rr =
diff --git a/deps/cares/src/lib/legacy/ares_parse_ptr_reply.c b/deps/cares/src/lib/legacy/ares_parse_ptr_reply.c
index 0e52f9db09bbc7..a275f3802ffc08 100644
--- a/deps/cares/src/lib/legacy/ares_parse_ptr_reply.c
+++ b/deps/cares/src/lib/legacy/ares_parse_ptr_reply.c
@@ -88,12 +88,12 @@ ares_status_t ares_parse_ptr_reply_dnsrec(const ares_dns_record_t *dnsrec,
hostent->h_length = (HOSTENT_LENGTH_TYPE)addrlen;
/* Preallocate the maximum number + 1 */
- hostent->h_aliases = ares_malloc((ancount + 1) * sizeof(*hostent->h_aliases));
+ hostent->h_aliases =
+ ares_malloc_zero_array(ancount + 1, sizeof(*hostent->h_aliases));
if (hostent->h_aliases == NULL) {
status = ARES_ENOMEM;
goto done;
}
- memset(hostent->h_aliases, 0, (ancount + 1) * sizeof(*hostent->h_aliases));
/* Cycle through answers */
diff --git a/deps/cares/src/lib/record/ares_dns_mapping.c b/deps/cares/src/lib/record/ares_dns_mapping.c
index 5a3ec28abf130b..38dedd438c6dd5 100644
--- a/deps/cares/src/lib/record/ares_dns_mapping.c
+++ b/deps/cares/src/lib/record/ares_dns_mapping.c
@@ -147,7 +147,7 @@ ares_bool_t ares_dns_class_isvalid(ares_dns_class_t qclass,
switch (qclass) {
case ARES_CLASS_IN:
case ARES_CLASS_CHAOS:
- case ARES_CLASS_HESOID:
+ case ARES_CLASS_HESIOD:
case ARES_CLASS_NONE:
return ARES_TRUE;
case ARES_CLASS_ANY:
@@ -228,7 +228,7 @@ const char *ares_dns_rec_type_tostr(ares_dns_rec_type_t type)
case ARES_REC_TYPE_CAA:
return "CAA";
case ARES_REC_TYPE_RAW_RR:
- return "RAWRR";
+ return "RAW_RR";
}
return "UNKNOWN";
}
@@ -240,7 +240,7 @@ const char *ares_dns_class_tostr(ares_dns_class_t qclass)
return "IN";
case ARES_CLASS_CHAOS:
return "CH";
- case ARES_CLASS_HESOID:
+ case ARES_CLASS_HESIOD:
return "HS";
case ARES_CLASS_ANY:
return "ANY";
@@ -670,7 +670,7 @@ ares_bool_t ares_dns_class_fromstr(ares_dns_class_t *qclass, const char *str)
} list[] = {
{ "IN", ARES_CLASS_IN },
{ "CH", ARES_CLASS_CHAOS },
- { "HS", ARES_CLASS_HESOID },
+ { "HS", ARES_CLASS_HESIOD },
{ "NONE", ARES_CLASS_NONE },
{ "ANY", ARES_CLASS_ANY },
{ NULL, 0 }
diff --git a/deps/cares/src/lib/record/ares_dns_multistring.c b/deps/cares/src/lib/record/ares_dns_multistring.c
index 44fcaccd65bb6a..a2a7adc4e4527f 100644
--- a/deps/cares/src/lib/record/ares_dns_multistring.c
+++ b/deps/cares/src/lib/record/ares_dns_multistring.c
@@ -227,7 +227,9 @@ const unsigned char *ares_dns_multistring_combined(ares_dns_multistring_t *strs,
strs->cache_str =
(unsigned char *)ares_buf_finish_str(buf, &strs->cache_str_len);
- if (strs->cache_str != NULL) {
+ if (strs->cache_str == NULL) {
+ ares_buf_destroy(buf);
+ } else {
strs->cache_invalidated = ARES_FALSE;
}
*len = strs->cache_str_len;
diff --git a/deps/cares/src/lib/record/ares_dns_name.c b/deps/cares/src/lib/record/ares_dns_name.c
index a9b92e03ca9353..ffcaf5e110d547 100644
--- a/deps/cares/src/lib/record/ares_dns_name.c
+++ b/deps/cares/src/lib/record/ares_dns_name.c
@@ -25,6 +25,18 @@
*/
#include "ares_private.h"
+/* RFC 1035 3.1 limits a name to 255 octets. We track presentation length
+ * (label octets plus one separator before each label after the first), which is
+ * up to ~2 octets looser than the strict wire limit and so never rejects a
+ * compliant name. Shared by the read and write paths. */
+#define ARES_MAX_NAME_PRESENTATION_LEN 255
+
+/* A name of <= 255 octets holds at most 128 labels, so it can never legitimately
+ * require more than that many compression-pointer jumps. Bounding the number of
+ * indirections stops a name built purely from pointers (which never adds label
+ * bytes, so the length cap never fires) from being walked without limit. */
+#define ARES_MAX_INDIRS 128
+
typedef struct {
char *name;
size_t name_len;
@@ -48,10 +60,18 @@ static ares_status_t ares_nameoffset_create(ares_llist_t **list,
ares_nameoffset_t *off = NULL;
if (list == NULL || name == NULL || ares_strlen(name) == 0 ||
- ares_strlen(name) > 255) {
+ ares_strlen(name) > ARES_MAX_NAME_PRESENTATION_LEN) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
+ /* Don't record a name as a compression target if it can't be referenced by
+ * a pointer. DNS name compression offsets are only 14 bits (RFC 1035
+ * 4.1.4), so a name positioned beyond 0x3FFF would have its offset
+ * truncated when written, producing a pointer to the wrong location. */
+ if (idx > 0x3FFF) {
+ return ARES_SUCCESS;
+ }
+
if (*list == NULL) {
*list = ares_llist_create(ares_nameoffset_free);
}
@@ -65,7 +85,12 @@ static ares_status_t ares_nameoffset_create(ares_llist_t **list,
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
- off->name = ares_strdup(name);
+ off->name = ares_strdup(name);
+ if (off->name == NULL) {
+ status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
+ goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+
off->name_len = ares_strlen(off->name);
off->idx = idx;
@@ -335,7 +360,8 @@ static ares_status_t ares_split_dns_name(ares_array_t *labels,
}
/* Can't exceed maximum (unescaped) length */
- if (ares_array_len(labels) && total_len + ares_array_len(labels) - 1 > 255) {
+ if (ares_array_len(labels) &&
+ total_len + ares_array_len(labels) - 1 > ARES_MAX_NAME_PRESENTATION_LEN) {
status = ARES_EBADNAME;
goto done;
}
@@ -419,6 +445,9 @@ ares_status_t ares_dns_name_write(ares_buf_t *buf, ares_llist_t **list,
/* Output name compression offset jump */
if (off != NULL) {
+ /* off->idx is guaranteed to fit in 14 bits: ares_nameoffset_create()
+ * refuses to record offsets beyond 0x3FFF, so the mask below never
+ * actually truncates. */
unsigned short u16 =
(unsigned short)0xC000 | (unsigned short)(off->idx & 0x3FFF);
status = ares_buf_append_be16(buf, u16);
@@ -531,13 +560,16 @@ static ares_status_t ares_fetch_dnsname_into_buf(ares_buf_t *buf,
}
ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
- ares_bool_t is_hostname)
+ ares_bool_t is_hostname,
+ ares_bool_t allow_compression)
{
size_t save_offset = 0;
unsigned char c;
ares_status_t status;
ares_buf_t *namebuf = NULL;
size_t label_start = ares_buf_get_position(buf);
+ size_t name_len = 0;
+ size_t indir = 0;
if (buf == NULL) {
return ARES_EFORMERR;
@@ -588,6 +620,13 @@ ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
*/
size_t offset = (size_t)((c & 0x3F) << 8);
+ /* Some RR types (e.g. SRV) do not permit name compression within their
+ * RDATA. Reject a compression pointer when it is not allowed. */
+ if (!allow_compression) {
+ status = ARES_EBADNAME;
+ goto fail;
+ }
+
/* Fetch second byte of the redirect length */
status = ares_buf_fetch_bytes(buf, &c, 1);
if (status != ARES_SUCCESS) {
@@ -609,6 +648,16 @@ ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
goto fail;
}
+ /* Bound the number of indirections. A name made purely of pointers never
+ * adds label bytes, so the length cap below can't stop it; the
+ * strictly-decreasing rule alone still allows thousands of jumps per name.
+ * No legitimate <= 255 octet name needs more than 128 labels/jumps. */
+ indir++;
+ if (indir > ARES_MAX_INDIRS) {
+ status = ARES_EBADNAME;
+ goto fail;
+ }
+
/* First time we make a jump, save the current position */
if (save_offset == 0) {
save_offset = ares_buf_get_position(buf);
@@ -632,6 +681,20 @@ ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
/* New label */
+ /* RFC 1035 3.1 limits a name to 255 octets. Enforce it during
+ * decompression so labels reached through a chain of pointers can't expand a
+ * single name without bound. Track the presentation length (label data plus
+ * the separator that precedes each label after the first), matching
+ * ares_split_dns_name() on the write side. */
+ if (name_len) {
+ name_len++;
+ }
+ name_len += c;
+ if (name_len > ARES_MAX_NAME_PRESENTATION_LEN) {
+ status = ARES_EBADNAME;
+ goto fail;
+ }
+
/* Labels are separated by periods */
if (ares_buf_len(namebuf) != 0 && name != NULL) {
status = ares_buf_append_byte(namebuf, '.');
diff --git a/deps/cares/src/lib/record/ares_dns_parse.c b/deps/cares/src/lib/record/ares_dns_parse.c
index 0c545d7aa18ada..9dc28e92d84247 100644
--- a/deps/cares/src/lib/record/ares_dns_parse.c
+++ b/deps/cares/src/lib/record/ares_dns_parse.c
@@ -46,8 +46,14 @@ static ares_status_t ares_dns_parse_and_set_dns_name(ares_buf_t *buf,
{
ares_status_t status;
char *name = NULL;
+ /* Only RR types defined in RFC1035 may use name compression within their
+ * RDATA (RFC3597). Reject compression pointers for any other type (e.g.
+ * SRV per RFC2782) to match the write-side policy and avoid following
+ * pointers that a non-understanding nameserver could not have rewritten. */
+ ares_bool_t allow_compression =
+ ares_dns_rec_allow_name_comp(ares_dns_rr_get_type(rr));
- status = ares_dns_name_parse(buf, &name, is_hostname);
+ status = ares_dns_name_parse(buf, &name, is_hostname, allow_compression);
if (status != ARES_SUCCESS) {
return status;
}
@@ -514,6 +520,7 @@ static ares_status_t ares_dns_parse_rr_opt(ares_buf_t *buf, ares_dns_rr_t *rr,
status = ares_dns_rr_set_opt_own(rr, ARES_RR_OPT_OPTIONS, opt, val, len);
if (status != ARES_SUCCESS) {
+ ares_free(val);
return status;
}
}
@@ -607,6 +614,7 @@ static ares_status_t ares_dns_parse_rr_svcb(ares_buf_t *buf, ares_dns_rr_t *rr,
status = ares_dns_rr_set_opt_own(rr, ARES_RR_SVCB_PARAMS, opt, val, len);
if (status != ARES_SUCCESS) {
+ ares_free(val);
return status;
}
}
@@ -658,6 +666,7 @@ static ares_status_t ares_dns_parse_rr_https(ares_buf_t *buf, ares_dns_rr_t *rr,
status = ares_dns_rr_set_opt_own(rr, ARES_RR_HTTPS_PARAMS, opt, val, len);
if (status != ARES_SUCCESS) {
+ ares_free(val);
return status;
}
}
@@ -765,6 +774,12 @@ static ares_status_t ares_dns_parse_rr_raw_rr(ares_buf_t *buf,
ares_status_t status;
unsigned char *bytes = NULL;
+ /* Can't fail */
+ status = ares_dns_rr_set_u16(rr, ARES_RR_RAW_RR_TYPE, raw_type);
+ if (status != ARES_SUCCESS) {
+ return status;
+ }
+
if (rdlength == 0) {
return ARES_SUCCESS;
}
@@ -774,13 +789,6 @@ static ares_status_t ares_dns_parse_rr_raw_rr(ares_buf_t *buf,
return status;
}
- /* Can't fail */
- status = ares_dns_rr_set_u16(rr, ARES_RR_RAW_RR_TYPE, raw_type);
- if (status != ARES_SUCCESS) {
- ares_free(bytes);
- return status;
- }
-
status = ares_dns_rr_set_bin_own(rr, ARES_RR_RAW_RR_DATA, bytes, rdlength);
if (status != ARES_SUCCESS) {
ares_free(bytes);
@@ -920,30 +928,6 @@ static ares_status_t ares_dns_parse_header(ares_buf_t *buf, unsigned int flags,
(*dnsrec)->raw_rcode = rcode;
- if (*ancount > 0) {
- status =
- ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_ANSWER, *ancount);
- if (status != ARES_SUCCESS) {
- goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
- }
- }
-
- if (*nscount > 0) {
- status =
- ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_AUTHORITY, *nscount);
- if (status != ARES_SUCCESS) {
- goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
- }
- }
-
- if (*arcount > 0) {
- status =
- ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_ADDITIONAL, *arcount);
- if (status != ARES_SUCCESS) {
- goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
- }
- }
-
return ARES_SUCCESS;
fail:
@@ -1032,7 +1016,7 @@ static ares_status_t ares_dns_parse_qd(ares_buf_t *buf,
*/
/* Name */
- status = ares_dns_name_parse(buf, &name, ARES_FALSE);
+ status = ares_dns_name_parse(buf, &name, ARES_FALSE, ARES_TRUE);
if (status != ARES_SUCCESS) {
goto done;
}
@@ -1103,7 +1087,7 @@ static ares_status_t ares_dns_parse_rr(ares_buf_t *buf, unsigned int flags,
*/
/* Name */
- status = ares_dns_name_parse(buf, &name, ARES_FALSE);
+ status = ares_dns_name_parse(buf, &name, ARES_FALSE, ARES_TRUE);
if (status != ARES_SUCCESS) {
goto done;
}
@@ -1208,6 +1192,8 @@ static ares_status_t ares_dns_parse_buf(ares_buf_t *buf, unsigned int flags,
ares_dns_record_t **dnsrec)
{
ares_status_t status;
+ size_t total_rr_count;
+ const size_t min_rr_wire_len = 11;
unsigned short qdcount;
unsigned short ancount;
unsigned short nscount;
@@ -1268,6 +1254,35 @@ static ares_status_t ares_dns_parse_buf(ares_buf_t *buf, unsigned int flags,
}
}
+ total_rr_count = (size_t)ancount + (size_t)nscount + (size_t)arcount;
+ if (total_rr_count > ares_buf_len(buf) / min_rr_wire_len) {
+ status = ARES_EBADRESP;
+ goto fail;
+ }
+
+ if (ancount > 0) {
+ status = ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_ANSWER, ancount);
+ if (status != ARES_SUCCESS) {
+ goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+ }
+
+ if (nscount > 0) {
+ status =
+ ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_AUTHORITY, nscount);
+ if (status != ARES_SUCCESS) {
+ goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+ }
+
+ if (arcount > 0) {
+ status =
+ ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_ADDITIONAL, arcount);
+ if (status != ARES_SUCCESS) {
+ goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+ }
+
/* Parse Answers */
for (i = 0; i < ancount; i++) {
status = ares_dns_parse_rr(buf, flags, ARES_SECTION_ANSWER, *dnsrec);
@@ -1292,6 +1307,22 @@ static ares_status_t ares_dns_parse_buf(ares_buf_t *buf, unsigned int flags,
}
}
+ /* RFC 6891 6.1.1: a message MUST NOT contain more than one OPT RR. Reject
+ * any response carrying multiple OPT records in the additional section. */
+ {
+ size_t rr_cnt = ares_dns_record_rr_cnt(*dnsrec, ARES_SECTION_ADDITIONAL);
+ size_t opt_cnt = 0;
+ size_t j;
+ for (j = 0; j < rr_cnt; j++) {
+ const ares_dns_rr_t *rr =
+ ares_dns_record_rr_get_const(*dnsrec, ARES_SECTION_ADDITIONAL, j);
+ if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT && ++opt_cnt > 1) {
+ status = ARES_EBADRESP;
+ goto fail;
+ }
+ }
+ }
+
/* Finalize rcode now that if we have OPT it is processed */
if (!ares_dns_rcode_isvalid((*dnsrec)->raw_rcode)) {
(*dnsrec)->rcode = ARES_RCODE_SERVFAIL;
diff --git a/deps/cares/src/lib/record/ares_dns_record.c b/deps/cares/src/lib/record/ares_dns_record.c
index ec0dfbd13c49f3..2c0dadd084ca66 100644
--- a/deps/cares/src/lib/record/ares_dns_record.c
+++ b/deps/cares/src/lib/record/ares_dns_record.c
@@ -929,7 +929,7 @@ ares_status_t ares_dns_rr_add_abin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
ares_dns_datatype_t datatype = ares_dns_rr_key_datatype(key);
ares_bool_t is_nullterm =
(datatype == ARES_DATATYPE_ABINP) ? ARES_TRUE : ARES_FALSE;
- size_t alloclen = is_nullterm ? len + 1 : len;
+ size_t alloclen;
unsigned char *temp;
ares_dns_multistring_t **strs;
@@ -937,6 +937,15 @@ ares_status_t ares_dns_rr_add_abin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
return ARES_EFORMERR;
}
+ if (val == NULL && len != 0) {
+ return ARES_EFORMERR;
+ }
+
+ if (is_nullterm && len == SIZE_MAX) {
+ return ARES_ENOMEM;
+ }
+ alloclen = is_nullterm ? len + 1 : len;
+
strs = ares_dns_rr_data_ptr(dns_rr, key, NULL);
if (strs == NULL) {
return ARES_EFORMERR;
@@ -954,7 +963,9 @@ ares_status_t ares_dns_rr_add_abin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
return ARES_ENOMEM;
}
- memcpy(temp, val, len);
+ if (len != 0) {
+ memcpy(temp, val, len);
+ }
/* NULL-term ABINP */
if (is_nullterm) {
@@ -1237,14 +1248,32 @@ ares_status_t ares_dns_rr_set_bin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
(datatype == ARES_DATATYPE_BINP || datatype == ARES_DATATYPE_ABINP)
? ARES_TRUE
: ARES_FALSE;
- size_t alloclen = is_nullterm ? len + 1 : len;
- unsigned char *temp = ares_malloc(alloclen);
+ size_t alloclen;
+ unsigned char *temp;
+
+ if (datatype != ARES_DATATYPE_BIN && datatype != ARES_DATATYPE_BINP &&
+ datatype != ARES_DATATYPE_ABINP) {
+ return ARES_EFORMERR;
+ }
+
+ if (val == NULL && len != 0) {
+ return ARES_EFORMERR;
+ }
+
+ if (is_nullterm && len == SIZE_MAX) {
+ return ARES_ENOMEM;
+ }
+ alloclen = is_nullterm ? len + 1 : len;
+
+ temp = ares_malloc(alloclen);
if (temp == NULL) {
return ARES_ENOMEM;
}
- memcpy(temp, val, len);
+ if (len != 0) {
+ memcpy(temp, val, len);
+ }
/* NULL-term BINP */
if (is_nullterm) {
@@ -1400,12 +1429,23 @@ ares_status_t ares_dns_rr_set_opt(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
ares_status_t status;
if (val != NULL) {
- temp = ares_malloc(val_len + 1);
+ size_t alloclen;
+
+ if (val_len == SIZE_MAX) {
+ return ARES_ENOMEM;
+ }
+ alloclen = val_len + 1;
+
+ temp = ares_malloc(alloclen);
if (temp == NULL) {
return ARES_ENOMEM;
}
- memcpy(temp, val, val_len);
+ if (val_len != 0) {
+ memcpy(temp, val, val_len);
+ }
temp[val_len] = 0;
+ } else if (val_len != 0) {
+ return ARES_EFORMERR;
}
status = ares_dns_rr_set_opt_own(dns_rr, key, opt, temp, val_len);
diff --git a/deps/cares/src/lib/str/ares_buf.c b/deps/cares/src/lib/str/ares_buf.c
index 63acc6cf7714d3..25f0372e4dbdcc 100644
--- a/deps/cares/src/lib/str/ares_buf.c
+++ b/deps/cares/src/lib/str/ares_buf.c
@@ -139,6 +139,7 @@ static ares_status_t ares_buf_ensure_space(ares_buf_t *buf, size_t needed_size)
{
size_t remaining_size;
size_t alloc_size;
+ size_t total_required;
unsigned char *ptr;
if (buf == NULL) {
@@ -151,9 +152,20 @@ static ares_status_t ares_buf_ensure_space(ares_buf_t *buf, size_t needed_size)
/* When calling ares_buf_finish_str() we end up adding a null terminator,
* so we want to ensure the size is always sufficient for this as we don't
- * want an ARES_ENOMEM at that point */
+ * want an ARES_ENOMEM at that point.
+ */
+ if (buf->data_len >= SIZE_MAX - 1) {
+ return ARES_ENOMEM;
+ }
+
needed_size++;
+ if (needed_size > SIZE_MAX - buf->data_len) {
+ return ARES_ENOMEM;
+ }
+
+ total_required = buf->data_len + needed_size;
+
/* No need to do an expensive move operation, we have enough to just append */
remaining_size = buf->alloc_buf_len - buf->data_len;
if (remaining_size >= needed_size) {
@@ -177,9 +189,11 @@ static ares_status_t ares_buf_ensure_space(ares_buf_t *buf, size_t needed_size)
/* Increase allocation by powers of 2 */
do {
- alloc_size <<= 1;
- remaining_size = alloc_size - buf->data_len;
- } while (remaining_size < needed_size);
+ if (alloc_size > SIZE_MAX >> 1) {
+ return ARES_ENOMEM;
+ }
+ alloc_size <<= 1;
+ } while (alloc_size < total_required);
ptr = ares_realloc(buf->alloc_buf, alloc_size);
if (ptr == NULL) {
@@ -1111,7 +1125,7 @@ ares_status_t ares_buf_replace(ares_buf_t *buf, const unsigned char *srch,
size_t processed_len = 0;
ares_status_t status;
- if (buf->alloc_buf == NULL || srch == NULL || srch_size == 0 ||
+ if (buf == NULL || buf->alloc_buf == NULL || srch == NULL || srch_size == 0 ||
(rplc == NULL && rplc_size != 0)) {
return ARES_EFORMERR;
}
@@ -1131,7 +1145,7 @@ ares_status_t ares_buf_replace(ares_buf_t *buf, const unsigned char *srch,
/* Store the offset this was found because our actual pointer might be
* switched out from under us by the call to ensure_space() if the
* replacement pattern is larger than the search pattern */
- found_offset = (size_t)(ptr - (size_t)(buf->alloc_buf + buf->offset));
+ found_offset = (size_t)(ptr - buf->alloc_buf) - buf->offset;
if (rplc_size > srch_size) {
status = ares_buf_ensure_space(buf, rplc_size - srch_size);
if (status != ARES_SUCCESS) {
@@ -1262,17 +1276,15 @@ static ares_status_t
}
done:
- if (status != ARES_SUCCESS) {
- ares_buf_destroy(binbuf);
+ if (status == ARES_SUCCESS && bin != NULL) {
+ size_t mylen = 0;
+ /* NOTE: we use ares_buf_finish_str() here as we guarantee NULL
+ * Termination even though we are technically returning binary data.
+ */
+ *bin = (unsigned char *)ares_buf_finish_str(binbuf, &mylen);
+ *bin_len = mylen;
} else {
- if (bin != NULL) {
- size_t mylen = 0;
- /* NOTE: we use ares_buf_finish_str() here as we guarantee NULL
- * Termination even though we are technically returning binary data.
- */
- *bin = (unsigned char *)ares_buf_finish_str(binbuf, &mylen);
- *bin_len = mylen;
- }
+ ares_buf_destroy(binbuf);
}
return status;
diff --git a/deps/cares/src/lib/str/ares_str.c b/deps/cares/src/lib/str/ares_str.c
index 0eda1ab9f15783..0819dc57849049 100644
--- a/deps/cares/src/lib/str/ares_str.c
+++ b/deps/cares/src/lib/str/ares_str.c
@@ -28,6 +28,9 @@
#include "ares_private.h"
#include "ares_str.h"
+#include
+#include
+
#ifdef HAVE_STDINT_H
# include
#endif
@@ -125,6 +128,57 @@ ares_bool_t ares_str_isnum(const char *str)
return ARES_TRUE;
}
+ares_bool_t ares_str_parse_uint(const char *str, unsigned long max,
+ unsigned int *out)
+{
+ char *end = NULL;
+ unsigned long val;
+
+ /* Require a leading digit so strtoul()'s tolerance of a sign or leading
+ * whitespace (e.g. "-1" wrapping to UINT_MAX) can't slip through here. This
+ * also rejects NULL and empty strings. */
+ if (str == NULL || out == NULL || !ares_isdigit(*str)) {
+ return ARES_FALSE;
+ }
+
+ /* out is unsigned int, so a max above UINT_MAX would let strtoul's result
+ * truncate silently on assignment. Cap it. */
+ if (max > UINT_MAX) {
+ max = UINT_MAX;
+ }
+
+ errno = 0;
+ val = strtoul(str, &end, 10);
+ if (errno == ERANGE || *end != '\0' || val > max) {
+ return ARES_FALSE;
+ }
+
+ *out = (unsigned int)val;
+ return ARES_TRUE;
+}
+
+ares_bool_t ares_parse_port(const char *str, unsigned short *port,
+ ares_bool_t allow_zero)
+{
+ unsigned int val;
+
+ if (port == NULL) {
+ return ARES_FALSE;
+ }
+
+ if (!ares_str_parse_uint(str, 65535UL, &val)) {
+ return ARES_FALSE;
+ }
+
+ if (!allow_zero && val == 0) {
+ return ARES_FALSE;
+ }
+
+ *port = (unsigned short)val;
+
+ return ARES_TRUE;
+}
+
ares_bool_t ares_str_isalnum(const char *str)
{
size_t i;
diff --git a/deps/cares/src/lib/util/ares_iface_ips.c b/deps/cares/src/lib/util/ares_iface_ips.c
index c5f507f87e1476..66d1fba84d4bce 100644
--- a/deps/cares/src/lib/util/ares_iface_ips.c
+++ b/deps/cares/src/lib/util/ares_iface_ips.c
@@ -25,6 +25,8 @@
*/
#include "ares_private.h"
+#include
+
#ifdef USE_WINSOCK
# include
# include
@@ -256,12 +258,12 @@ ares_iface_ip_flags_t ares_iface_ips_get_flags(const ares_iface_ips_t *ips,
const ares_iface_ip_t *ip;
if (ips == NULL) {
- return 0;
+ return ARES_IFACE_IP_NONE;
}
ip = ares_array_at_const(ips->ips, idx);
if (ip == NULL) {
- return 0;
+ return ARES_IFACE_IP_NONE;
}
return ip->flags;
@@ -329,6 +331,8 @@ static char *wcharp_to_charp(const wchar_t *in)
static ares_bool_t name_match(const char *name, const char *adapter_name,
unsigned int ll_scope)
{
+ unsigned int scope;
+
if (name == NULL || *name == 0) {
return ARES_TRUE;
}
@@ -337,7 +341,7 @@ static ares_bool_t name_match(const char *name, const char *adapter_name,
return ARES_TRUE;
}
- if (ares_str_isnum(name) && (unsigned int)atoi(name) == ll_scope) {
+ if (ares_str_parse_uint(name, UINT_MAX, &scope) && scope == ll_scope) {
return ARES_TRUE;
}
@@ -376,7 +380,7 @@ static ares_status_t ares_iface_ips_enumerate(ares_iface_ips_t *ips,
for (address = addresses; address != NULL; address = address->Next) {
IP_ADAPTER_UNICAST_ADDRESS *ipaddr = NULL;
- ares_iface_ip_flags_t addrflag = 0;
+ ares_iface_ip_flags_t addrflag = ARES_IFACE_IP_NONE;
char ifname[64] = "";
# if defined(HAVE_CONVERTINTERFACEINDEXTOLUID) && \
@@ -477,7 +481,7 @@ static ares_status_t ares_iface_ips_enumerate(ares_iface_ips_t *ips,
}
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
- ares_iface_ip_flags_t addrflag = 0;
+ ares_iface_ip_flags_t addrflag = ARES_IFACE_IP_NONE;
struct ares_addr addr;
unsigned char netmask = 0;
unsigned int ll_scope = 0;
@@ -500,20 +504,28 @@ static ares_status_t ares_iface_ips_enumerate(ares_iface_ips_t *ips,
addr.family = AF_INET;
memcpy(&addr.addr.addr4, &sockaddr_in->sin_addr, sizeof(addr.addr.addr4));
/* netmask */
- sockaddr_in = (struct sockaddr_in *)((void *)ifa->ifa_netmask);
- netmask = count_addr_bits((const void *)&sockaddr_in->sin_addr, 4);
+ if (ifa->ifa_netmask != NULL) {
+ sockaddr_in = (struct sockaddr_in *)((void *)ifa->ifa_netmask);
+ netmask = count_addr_bits((const void *)&sockaddr_in->sin_addr, 4);
+ } else {
+ netmask = 32;
+ }
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
const struct sockaddr_in6 *sockaddr_in6 =
(const struct sockaddr_in6 *)((void *)ifa->ifa_addr);
addr.family = AF_INET6;
memcpy(&addr.addr.addr6, &sockaddr_in6->sin6_addr,
sizeof(addr.addr.addr6));
- /* netmask */
- sockaddr_in6 = (struct sockaddr_in6 *)((void *)ifa->ifa_netmask);
- netmask = count_addr_bits((const void *)&sockaddr_in6->sin6_addr, 16);
# ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
ll_scope = sockaddr_in6->sin6_scope_id;
# endif
+ /* netmask */
+ if (ifa->ifa_netmask != NULL) {
+ sockaddr_in6 = (struct sockaddr_in6 *)((void *)ifa->ifa_netmask);
+ netmask = count_addr_bits((const void *)&sockaddr_in6->sin6_addr, 16);
+ } else {
+ netmask = 128;
+ }
} else {
/* unknown */
continue;
diff --git a/deps/cares/src/lib/util/ares_iface_ips.h b/deps/cares/src/lib/util/ares_iface_ips.h
index f22e09046a065b..ec2353448fb0f5 100644
--- a/deps/cares/src/lib/util/ares_iface_ips.h
+++ b/deps/cares/src/lib/util/ares_iface_ips.h
@@ -28,6 +28,7 @@
/*! Flags for interface ip addresses. */
typedef enum {
+ ARES_IFACE_IP_NONE = 0, /*!< No flags set / unspecified */
ARES_IFACE_IP_V4 = 1 << 0, /*!< IPv4 address. During enumeration if
* this flag is set ARES_IFACE_IP_V6
* is not, will only enumerate v4
diff --git a/deps/cares/src/lib/util/ares_math.c b/deps/cares/src/lib/util/ares_math.c
index 1106bf6bf151f8..3513146c3ddc4a 100644
--- a/deps/cares/src/lib/util/ares_math.c
+++ b/deps/cares/src/lib/util/ares_math.c
@@ -42,7 +42,7 @@ static unsigned int ares_round_up_pow2_u32(unsigned int n)
return n;
}
-static ares_int64_t ares_round_up_pow2_u64(ares_int64_t n)
+static ares_uint64_t ares_round_up_pow2_u64(ares_uint64_t n)
{
/* NOTE: if already a power of 2, will return itself, not the next */
n--;
@@ -73,7 +73,7 @@ ares_bool_t ares_is_64bit(void)
size_t ares_round_up_pow2(size_t n)
{
if (ares_is_64bit()) {
- return (size_t)ares_round_up_pow2_u64((ares_int64_t)n);
+ return (size_t)ares_round_up_pow2_u64((ares_uint64_t)n);
}
return (size_t)ares_round_up_pow2_u32((unsigned int)n);
@@ -156,3 +156,12 @@ unsigned char ares_count_bits_u8(unsigned char x)
static const unsigned char lookup[256] = { B6(0), B6(1), B6(1), B6(2) };
return lookup[x];
}
+
+ares_bool_t ares_size_t_mul_overflow(size_t a, size_t b, size_t *res)
+{
+ if (a > 0 && b > SIZE_MAX / a) {
+ return ARES_TRUE;
+ }
+ *res = a * b;
+ return ARES_FALSE;
+}
diff --git a/deps/cares/src/lib/util/ares_math.h b/deps/cares/src/lib/util/ares_math.h
index 3b60b00bdbb6ce..76e1e488370984 100644
--- a/deps/cares/src/lib/util/ares_math.h
+++ b/deps/cares/src/lib/util/ares_math.h
@@ -50,4 +50,9 @@ size_t ares_count_digits(size_t n);
size_t ares_count_hexdigits(size_t n);
unsigned char ares_count_bits_u8(unsigned char x);
+/*! Multiply two size_t values, checking for overflow. On success writes the
+ * product to *res and returns ARES_FALSE. On overflow returns ARES_TRUE and
+ * leaves *res untouched. */
+ares_bool_t ares_size_t_mul_overflow(size_t a, size_t b, size_t *res);
+
#endif
diff --git a/deps/cares/src/lib/util/ares_threads.c b/deps/cares/src/lib/util/ares_threads.c
index ab0b51afb70577..b27b7ebd70005f 100644
--- a/deps/cares/src/lib/util/ares_threads.c
+++ b/deps/cares/src/lib/util/ares_threads.c
@@ -563,8 +563,7 @@ ares_status_t ares_queue_wait_empty(ares_channel_t *channel, int timeout_ms)
if (timeout_ms >= 0) {
ares_tvnow(&tout);
- tout.sec += (ares_int64_t)(timeout_ms / 1000);
- tout.usec += (unsigned int)(timeout_ms % 1000) * 1000;
+ ares_timeval_add(&tout, (size_t)timeout_ms);
}
ares_thread_mutex_lock(channel->lock);
diff --git a/deps/cares/src/lib/util/ares_time.h b/deps/cares/src/lib/util/ares_time.h
index c6eaf97366379e..ecb721de188719 100644
--- a/deps/cares/src/lib/util/ares_time.h
+++ b/deps/cares/src/lib/util/ares_time.h
@@ -39,6 +39,7 @@ ares_bool_t ares_timedout(const ares_timeval_t *now,
const ares_timeval_t *check);
void ares_tvnow(ares_timeval_t *now);
+void ares_timeval_add(ares_timeval_t *now, size_t millisecs);
void ares_timeval_remaining(ares_timeval_t *remaining,
const ares_timeval_t *now,
const ares_timeval_t *tout);
diff --git a/deps/cares/src/lib/util/ares_uri.c b/deps/cares/src/lib/util/ares_uri.c
index 04bad0074a79e2..97f3f3f542a7cf 100644
--- a/deps/cares/src/lib/util/ares_uri.c
+++ b/deps/cares/src/lib/util/ares_uri.c
@@ -1174,6 +1174,7 @@ static ares_status_t ares_uri_parse_hostport(ares_uri_t *uri, ares_buf_t *buf)
unsigned char b;
char host[256];
char port[6];
+ unsigned short parsed_port;
size_t len;
ares_status_t status;
@@ -1242,11 +1243,11 @@ static ares_status_t ares_uri_parse_hostport(ares_uri_t *uri, ares_buf_t *buf)
}
port[len] = 0;
- if (!ares_str_isnum(port)) {
+ if (!ares_parse_port(port, &parsed_port, ARES_TRUE)) {
return ARES_EBADSTR;
}
- status = ares_uri_set_port(uri, (unsigned short)atoi(port));
+ status = ares_uri_set_port(uri, parsed_port);
if (status != ARES_SUCCESS) {
return status;
}
diff --git a/deps/cares/src/tools/adig.c b/deps/cares/src/tools/adig.c
index fce210a8053578..29f359a39287c1 100644
--- a/deps/cares/src/tools/adig.c
+++ b/deps/cares/src/tools/adig.c
@@ -1162,12 +1162,12 @@ static ares_bool_t read_cmdline(int argc, const char * const *argv,
/* skip prefix */
if (dig_options[opt].prefix != 0) {
nameptr++;
- }
-
- /* Negated option if it has a 'no' prefix */
- if (ares_streq_max(nameptr, "no", 2)) {
- is_true = ARES_FALSE;
- nameptr += 2;
+ /* Negated option if it has a 'no' prefix */
+ if (dig_options[opt].prefix == '+' &&
+ ares_streq_max(nameptr, "no", 2)) {
+ is_true = ARES_FALSE;
+ nameptr += 2;
+ }
}
if (dig_options[opt].separator != 0) {
diff --git a/deps/cares/src/tools/ahost.c b/deps/cares/src/tools/ahost.c
index 7d1d4a86dc7a2d..67b5677cad6e25 100644
--- a/deps/cares/src/tools/ahost.c
+++ b/deps/cares/src/tools/ahost.c
@@ -44,6 +44,13 @@
#include "ares_str.h"
+#define RV_OK 0 /* Success */
+#define RV_SYSERR 1 /* Internal system failure */
+#define RV_MISUSE 2 /* Misuse (command line) */
+#define RV_FAIL 3 /* Resolution failure */
+static int final_rv = RV_OK;
+
+
static void callback(void *arg, int status, int timeouts, struct hostent *host);
static void ai_callback(void *arg, int status, int timeouts,
struct ares_addrinfo *result);
diff --git a/deps/crates/crates.gyp b/deps/crates/crates.gyp
index 5c1dc9d782cbfe..ede5bf788bc760 100644
--- a/deps/crates/crates.gyp
+++ b/deps/crates/crates.gyp
@@ -2,6 +2,7 @@
'variables': {
'cargo%': 'cargo',
'cargo_vendor_dir': './vendor',
+ 'temporal_capi_dir': 'temporal_capi',
'cargo_rust_target%': '',
},
'conditions': [
@@ -120,7 +121,7 @@
],
'direct_dependent_settings': {
'include_dirs': [
- '<(cargo_vendor_dir)/temporal_capi/bindings/cpp',
+ '<(cargo_vendor_dir)/<(temporal_capi_dir)/bindings/cpp',
],
},
},
diff --git a/deps/googletest/include/gtest/internal/gtest-port.h b/deps/googletest/include/gtest/internal/gtest-port.h
index b607e0adee74f4..650a04558c03ba 100644
--- a/deps/googletest/include/gtest/internal/gtest-port.h
+++ b/deps/googletest/include/gtest/internal/gtest-port.h
@@ -2411,9 +2411,7 @@ using StringView = ::std::string_view;
#define GTEST_INTERNAL_HAS_STRING_VIEW 0
#endif
-#if (defined(__cpp_lib_three_way_comparison) || \
- (GTEST_INTERNAL_HAS_INCLUDE() && \
- GTEST_INTERNAL_CPLUSPLUS_LANG >= 201907L))
+#if defined(__cpp_lib_three_way_comparison)
#define GTEST_INTERNAL_HAS_COMPARE_LIB 1
#else
#define GTEST_INTERNAL_HAS_COMPARE_LIB 0
diff --git a/deps/googletest/src/gtest.cc b/deps/googletest/src/gtest.cc
index ac90786a091b51..b38f551e036f76 100644
--- a/deps/googletest/src/gtest.cc
+++ b/deps/googletest/src/gtest.cc
@@ -6732,6 +6732,12 @@ static const char kColorEncodedHelpMessage[] =
"recreate_environments_when_repeating@D\n"
" Sets up and tears down the global test environment on each repeat\n"
" of the test.\n"
+ " @G--" GTEST_FLAG_PREFIX_
+ "fail_fast@D\n"
+ " Stop running tests after the first failure.\n"
+ " @G--" GTEST_FLAG_PREFIX_
+ "fail_if_no_test_linked@D\n"
+ " Fail if no test is linked into the test program.\n"
"\n"
"Test Output:\n"
" @G--" GTEST_FLAG_PREFIX_
@@ -6744,6 +6750,9 @@ static const char kColorEncodedHelpMessage[] =
"print_time=0@D\n"
" Don't print the elapsed time of each test.\n"
" @G--" GTEST_FLAG_PREFIX_
+ "print_utf8=0@D\n"
+ " Don't print UTF-8 characters as text.\n"
+ " @G--" GTEST_FLAG_PREFIX_
"output=@Y(@Gjson@Y|@Gxml@Y)[@G:@YDIRECTORY_PATH@G" GTEST_PATH_SEP_
"@Y|@G:@YFILE_PATH]@D\n"
" Generate a JSON or XML report in the given directory or with the "
@@ -6760,6 +6769,9 @@ static const char kColorEncodedHelpMessage[] =
" @G--" GTEST_FLAG_PREFIX_
"death_test_style=@Y(@Gfast@Y|@Gthreadsafe@Y)@D\n"
" Set the default death test style.\n"
+ " @G--" GTEST_FLAG_PREFIX_
+ "death_test_use_fork@D\n"
+ " Use fork() instead of clone() to spawn death test child processes.\n"
#endif // GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS
" @G--" GTEST_FLAG_PREFIX_
"break_on_failure@D\n"
@@ -6772,6 +6784,9 @@ static const char kColorEncodedHelpMessage[] =
"catch_exceptions=0@D\n"
" Do not report exceptions as test failures. Instead, allow them\n"
" to crash the program or throw a pop-up (on Windows).\n"
+ " @G--" GTEST_FLAG_PREFIX_
+ "stack_trace_depth=@Y[NUMBER]@D\n"
+ " Maximum number of stack frames to print when an assertion fails.\n"
"\n"
"Except for @G--" GTEST_FLAG_PREFIX_
"list_tests@D, you can alternatively set "
diff --git a/deps/icu-small/source/data/in/icudt78l.dat.bz2 b/deps/icu-small/source/data/in/icudt78l.dat.bz2
index 100dcc6f6776a9..7ee362b0bb175b 100644
Binary files a/deps/icu-small/source/data/in/icudt78l.dat.bz2 and b/deps/icu-small/source/data/in/icudt78l.dat.bz2 differ
diff --git a/deps/libffi/ChangeLog b/deps/libffi/ChangeLog
index f2d63988dcf70a..0dde93e6adad9e 100644
--- a/deps/libffi/ChangeLog
+++ b/deps/libffi/ChangeLog
@@ -1,3 +1,953 @@
+commit 5c1c43091ed611fdea774374355eb938c73a9157
+Author: Anthony Green
+Date: Fri Jul 10 09:50:53 2026 -0400
+
+ Prepare for 3.7.1
+
+ Bump version to 3.7.1 and libtool -version-info to 12:1:4 (revision
+ bump; no ABI change). See the README for the change list.
+
+ Co-Authored-By: Claude Fable 5
+
+commit aaeaafb04a8335bea93b2873ca9a2d01107737eb
+Author: Anthony Green
+Date: Fri Jul 10 09:24:36 2026 -0400
+
+ testsuite: Make closure_thiscall_fastcall_pop portable across compilers
+
+ The i386 esp-balance shim faulted on Android i686 (clang, -O2): it read
+ the closure code pointer through an esp-relative memory operand after
+ manually moving esp, and it did not 16-byte align the stack for the
+ call, so the -O2-built closure body faulted on aligned SSE.
+
+ Read every operand into a register while esp is still at its incoming
+ value, 16-byte align the stack at the call per the i386 psABI, avoid ebx
+ so it works under -fPIC, and return the delta via memory. Verified with
+ gcc and clang at -O0 and -O2: passes with the fix, still catches the
+ under-pop without it.
+
+ Co-Authored-By: Claude Fable 5
+
+commit ebe306becb167937ead4332290868e041d0c08a7
+Author: Anthony Green
+Date: Fri Jul 10 08:54:39 2026 -0400
+
+ x86: Fix i386 THISCALL/FASTCALL closure stack-pop accounting
+
+ For THISCALL and FASTCALL, ffi_closure_inner force-bumps narg_reg to 2
+ when an argument is a 64-bit integer or a struct, so that following
+ integer arguments are placed on the stack (the Issue #434 rule). The
+ closure return path then computed the callee stack pop as
+ cif->bytes - narg_reg * 4, which discounts register slots that were
+ never used once narg_reg has been forced, under-popping the stack.
+
+ For example FASTCALL void(uint64_t) has cif->bytes == 8 with the uint64
+ placed on the stack, but the formula yields a pop of 0 instead of 8; a
+ callee-clean call site that does not re-sync ESP from a frame pointer is
+ then left with the argument bytes in place of its return address.
+
+ Encode the pop as the number of bytes actually consumed from the
+ incoming stack (argp - stack, dir == 1 for these ABIs), which equals the
+ old formula in the non-forced cases and is correct in the forced ones.
+
+ Adds closure_thiscall_fastcall_pop.c (i386/GNU), which invokes the
+ generated closure through a minimal callee-clean call site and checks
+ ESP is balanced; without this fix the imbalance is 8 (FASTCALL uint64)
+ or 4 (THISCALL this+uint64).
+
+ Co-Authored-By: Claude Fable 5
+
+commit a2ef91c79f4f193dd1e828f615c818fb6bcaff0d
+Author: Anthony Green
+Date: Fri Jul 10 08:20:39 2026 -0400
+
+ aarch64: Fix argument slab under-budget for large by-value structs
+
+ On AArch64, composites larger than 16 bytes (that are not HFAs) are
+ passed by invisible reference. ffi_call copies each payload into the
+ argument slab from the top (next_struct_area, growing down) and, once
+ X0-X7 are exhausted, spills the by-ref pointer into the same slab from
+ the bottom (the NSAA, growing up). The generic prep_cif accounting in
+ cif->bytes only charges the payload copy, not the extra 8-byte pointer
+ slot, so with enough large structs the two regions collide: a later
+ payload copy overwrites an already-spilled pointer, and the callee then
+ receives a corrupt pointer for a by-value argument.
+
+ Reserve an additional 8 bytes in ffi_prep_cif_machdep for each large
+ by-reference struct argument so the copy and spill regions can never
+ overlap.
+
+ Adds many_large_structs.c, which passes sixteen 32-byte structs by
+ value; it crashes without this fix and passes with it.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 25283b0d812b6729299a21a1c02433bc291174e9
+Merge: e87b59f5 665c5c00
+Author: Anthony Green
+Date: Fri Jul 10 08:22:39 2026 -0400
+
+ Merge pull request #997 from libffi/windows-arm64-ci
+
+ Add Windows ARM64 (MSVC) CI; fix latent msvcc.sh and testsuite bugs it exposed
+
+commit 665c5c00f2f492e199a1c9e01bda57a82f4440ef
+Author: Anthony Green
+Date: Fri Jul 10 07:46:24 2026 -0400
+
+ testsuite: Pass MSVC warning suppressions in go.exp
+
+ Same latent issue as closure.exp: the Go closure tests ran with no
+ MSVC warning options, so ffitest.h's benign C4005 PRI-macro
+ redefinition warnings count as excess errors. Latent until the
+ msvcc.sh -E fix let the FFI_GO_CLOSURES feature probe succeed under
+ MSVC; the execution tests themselves pass on both x86 MSVC jobs.
+ Mirror the call.exp/closure.exp warning-suppression block.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 2e897db821a43068e3851a414bd47b2b5592d403
+Author: Anthony Green
+Date: Fri Jul 10 07:25:53 2026 -0400
+
+ testsuite: Skip closure_loc_fn0 memcmp check under trampoline tables
+
+ Now that closure_loc_fn0.c actually executes, it fails on Apple
+ aarch64: with FFI_EXEC_TRAMPOLINE_TABLE, codeloc points at a
+ trampoline table entry rather than a copy of the closure, so the
+ memcmp sanity check is invalid there -- the same reason it is already
+ skipped for static trampolines. fficonfig.h (included by ffitest.h)
+ provides the macro.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 15b9abc239a1a2f6964a4cfc92f8fb78b3475d37
+Author: Anthony Green
+Date: Fri Jul 10 07:19:28 2026 -0400
+
+ ci: Add Windows ARM64 Visual C++ to the MSVC build matrix
+
+ Build and test on GitHub's windows-11-arm runners. Cygwin and the
+ amd64_arm64 cross tools (cl, armasm64) are x64-hosted and run under
+ the runner's x64 emulation; the ARM64 binaries they produce execute
+ natively, so the full testsuite runs (784 passes, 0 failures). This
+ is the first CI coverage for the win64_armasm.S closure trampolines.
+
+ The matrix entries grow runner/mflag/pkg/rcedit_arch parameters; the
+ existing x86 job names and release artifact names are unchanged.
+ Release tags now also publish ARM64 MSVC binaries
+ (libffi--arm64-msvc-binaries).
+
+ Replaces the temporary winarm64.yml iteration workflow.
+
+ Co-Authored-By: Claude Fable 5
+
+commit e4435739c82106cf13336bf26da99a03281848a0
+Author: Anthony Green
+Date: Fri Jul 10 06:58:17 2026 -0400
+
+ testsuite: Actually run closure_loc_fn0; fix msvcc.sh -S
+
+ closure_loc_fn0.c is the only test in the suite without a dg-do
+ directive, so DejaGnu has been defaulting to its compile-only action
+ on every platform -- the ffi_closure_alloc/ffi_prep_closure_loc path
+ it exists to exercise was never executed. Add the missing
+ /* { dg-do run } */.
+
+ The compile-only default also exposed msvcc.sh's broken -S handling:
+ it mapped -S to -FAs alone, which emits a listing but still runs the
+ link step, failing with unresolved externals since compile-only
+ invocations pass no libraries. Map -S to a true compile-to-assembly:
+ -c -FAs with the listing written to the -o target.
+
+ Co-Authored-By: Claude Fable 5
+
+commit bf2fa49b6dbe68dac021c02ee3deddb343ec3541
+Author: Anthony Green
+Date: Fri Jul 10 06:36:26 2026 -0400
+
+ testsuite: Pass MSVC warning suppressions to closures C tests
+
+ closure.exp defines additional_options with the same -wd warning
+ suppressions call.exp uses, but only passed them to the C++ tests;
+ the C tests ran with "". Under MSVC the benign C4005/C4305/C4477
+ warnings then count as excess errors and fail every C test's compile
+ check. This was latent until the previous commit made the
+ FFI_CLOSURES feature probe work under msvcc.sh, allowing the closures
+ suite to run on MSVC at all.
+
+ Co-Authored-By: Claude Fable 5
+
+commit c2ace700754b6e63f07efdec39fc8c09ed960d63
+Author: Anthony Green
+Date: Fri Jul 10 06:11:00 2026 -0400
+
+ msvcc.sh: Handle -E (preprocess) so DejaGnu feature tests work
+
+ The testsuite's libffi_feature_test probes compile a snippet with
+ "-E -o " and treat any compiler output as failure. msvcc.sh
+ had no -E handling: cl's preprocessed text went to stdout, where the
+ awk diagnostics filter passed through every line containing the
+ string "warning" -- including ffi.h's own #pragma warning lines --
+ and cl printed the source file name to stderr. The probes therefore
+ always failed under msvcc.sh, silently marking the entire closures
+ suite (99 tests) and the bhaible callback tests (80) UNSUPPORTED on
+ every MSVC CI job.
+
+ Recognize -E, write the preprocessed output to the -o target, and
+ swallow cl's stderr banner so feature probes see clean output.
+
+ Co-Authored-By: Claude Fable 5
+
+commit e994c8a22d6bc480cd13c9daa2abaedf42627f88
+Author: Anthony Green
+Date: Fri Jul 10 05:49:48 2026 -0400
+
+ ci: Add Windows ARM64 Visual C++ job (iteration branch)
+
+ Build and test libffi on GitHub's windows-11-arm runners using
+ msvcc.sh -marm64 (cl + armasm64 via the amd64_arm64 cross tools,
+ running under x64 emulation; the resulting ARM64 binaries execute
+ natively). msvcc.sh already understands -marm64.
+
+ This is a temporary standalone workflow scoped to the
+ windows-arm64-ci branch so the job can be iterated on without
+ triggering the full CI matrix; it will be folded into build.yml
+ once green.
+
+ Co-Authored-By: Claude Fable 5
+
+commit e87b59f53f3879cb254a4c331fd0d2c221d6ea73
+Author: Anthony Green
+Date: Fri Jul 10 05:40:57 2026 -0400
+
+ aarch64: Fix clang-cl link failure for HFA helper functions
+
+ clang-cl defines both __clang__ and _MSC_VER. Since the build system
+ detects clang before MSVC, it sets MSVC=0 and builds sysv.S rather
+ than win64_armasm.S. But ffi.c gated its C implementations of
+ extend_hfa_type and compress_hfa_type on !defined(_MSC_VER), so under
+ clang-cl neither implementation was built and linking failed.
+
+ Gate on defined(_MSC_VER) && !defined(__clang__) instead: clang-cl
+ supports GCC-style extended inline asm, so the C implementations work
+ there. Also replace ssize_t (POSIX-only, absent on Windows) with
+ ptrdiff_t in extend_hfa_type now that it compiles for MSVC targets.
+
+ Fixes #996
+
+ Co-Authored-By: Claude Fable 5
+
+commit cacaf519159668bce9fcbab64082bc85c277e89d
+Author: Anthony Green
+Date: Thu Jul 9 21:34:47 2026 -0400
+
+ x86-64: build generic ffi_call_plan fallback on Windows x86-64
+
+ The ffi_call_plan_* API has two providers: the accelerated x86-64 SysV
+ implementation in src/x86/ffi64.c, and the portable fallback in
+ src/prep_cif.c that just wraps ffi_call. ffi64.c is not compiled for
+ X86_WIN64 (that target builds ffiw64.c/win64.S instead), but clang-cl
+ and MSYS/mingw both define __x86_64__, so prep_cif.c's guard suppressed
+ the fallback too -- leaving ffi_call_plan_alloc/invoke/free undefined at
+ link time on those toolchains. MSVC cl escaped this only because it does
+ not define __x86_64__.
+
+ Suppress the generic fallback only when ffi64.c actually provides the
+ functions, i.e. add && !defined(X86_WIN64) to the guard. X86_WIN64 is a
+ command-line define visible to the generic sources (cf. src/closures.c).
+
+ Fixes #995.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit c2c2e538c7c5403bd4edc3e77220bbe25a3ad47f
+Merge: 132fb82f 7c3144c8
+Author: Anthony Green
+Date: Thu Jul 9 09:10:49 2026 -0400
+
+ Merge pull request #994 from libffi/fix-993-aarch64-darwin-int128
+
+ aarch64: Don't split an int128 between x7 and the stack on Darwin
+
+commit 7c3144c80d8077b745588ee64bffd8504c44f5c9
+Author: Anthony Green
+Date: Wed Jul 8 21:23:31 2026 -0400
+
+ aarch64: Don't split an int128 between x7 and the stack on Darwin
+
+ The Apple arm64 ABI does not round NGRN up to an even number for
+ 128-bit integer arguments, but like AAPCS64 it still requires the
+ value to fit entirely in registers: if only x7 remains, the whole
+ value goes on the stack. allocate_int128_to_reg_or_stack only
+ checked ngrn < 8, so with ngrn == 7 it wrote the low half to x7
+ and the high half past the end of the register array, while the
+ callee reads the argument from the stack.
+
+ This fixes the libffi.call/i128-1.c failure at iteration 7 (seven
+ int args followed by an __int128) on aarch64-darwin, for both the
+ call and closure paths.
+
+ Fixes #993
+
+ Co-Authored-By: Claude Fable 5
+
+commit 132fb82ff4b43c9b8eaeeb533f38c95ae925131c
+Author: Anthony Green
+Date: Wed Jul 8 07:40:34 2026 -0400
+
+ Update libtool version to 12:0:4 for 3.7.0
+
+ 3.7.0 adds three new public interfaces (ffi_call_plan_alloc,
+ ffi_call_plan_invoke, ffi_call_plan_free) additively, with no
+ existing interface removed or changed. Per the libtool rules:
+ increment current, reset revision, increment age (11:1:3 -> 12:0:4).
+ This keeps the change ABI backward-compatible; the soname stays
+ libffi.so.8 (current - age = 8).
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit 0ad8fd46c0a32cbbf18260c72cc60679da66c89f
+Author: Anthony Green
+Date: Wed Jul 8 06:43:03 2026 -0400
+
+ Prepare for 3.7.0
+
+commit c9275302baf0d109ec474972d4679a6afe8fe64d
+Author: Anthony Green
+Date: Wed Jul 8 06:29:29 2026 -0400
+
+ Bump version to 3.7.0
+
+ Set AC_INIT to 3.7.0 and update the manual's edition/version/date
+ stamps to match.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 4f7ef24565eb60e8a093bfeba0bb77d5c576c311
+Author: Anthony Green
+Date: Wed Jul 8 06:28:30 2026 -0400
+
+ Update copyright year
+
+ Bump the LICENSE year range to 2026.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 5ab7b328c3adde14105b1f36ae30adc9cdefae0a
+Author: Anthony Green
+Date: Wed Jul 8 06:24:26 2026 -0400
+
+ Add Anthony Green copyright line for 2026 authorship
+
+ Attribute this year's work in the wasm and PA backends and the darwin
+ PowerPC closure code:
+ - wasm/ffi.c: widen narrow/unboxed returns to ffi_arg; NULL rvalue.
+ - pa/ffi.c: bump year (5-8 byte struct slot-sizing fix + avalue copy).
+ - powerpc/ffi_darwin.c: PPC_LD_* jump-table indexes for the aix/darwin
+ closure build fix.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 5e334c6aa70b937b5c0d30ba10a373e34d7526b6
+Author: Anthony Green
+Date: Wed Jul 8 06:17:58 2026 -0400
+
+ x86-64: add copyright line for the 2026 unix64.S plan trampolines
+
+ The fast-path plan trampoline (ffi_plan_fast_call) and the pure-GP64
+ direct thunks (ffi_plan_gp0..6) were added to unix64.S this year, so
+ add an Anthony Green copyright line alongside the original authors'.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 70106b98d0a333a13b509b4bdc554018785be50c
+Author: Anthony Green
+Date: Wed Jul 8 06:15:53 2026 -0400
+
+ Update copyright year to 2026
+
+ Add 2026 to the copyright line on the source files, headers and the
+ manual touched during this year's development. Following existing
+ practice, only the maintainer's own copyright line is bumped (the
+ Anthony Green line, plus the Red Hat line in types.c); the years on
+ third-party-held backends are left as their original authors set them.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 621874d6726007a6470b9c0c90b5a27c3c3ed932
+Author: Anthony Green
+Date: Wed Jul 8 05:43:18 2026 -0400
+
+ configure: derive FFI_VERSION_STRING/NUMBER from AC_INIT
+
+ The 3.6.0 release bumped AC_INIT but left FFI_VERSION_STRING and
+ FFI_VERSION_NUMBER at 3.5.2/30502, so the released headers report the
+ wrong version. Compute both from AC_PACKAGE_VERSION instead of
+ duplicating the version by hand: the string is the package version
+ verbatim, and the number encodes X.Y.Z as X*10000 + Y*100 + Z with any
+ non-numeric suffix (e.g. -rc0) ignored.
+
+ Co-Authored-By: Claude Fable 5
+
+commit b8c60b06597cc77cbe66bd208083acc20251fae0
+Author: Anthony Green
+Date: Tue Jul 7 12:57:06 2026 -0400
+
+ README: note the ffi_call fixes in the 3.7.0 history
+
+ Co-Authored-By: Claude Fable 5
+
+commit ed5da041796cd6e38d2f30b948eadf63cf96139a
+Author: Anthony Green
+Date: Tue Jul 7 12:30:56 2026 -0400
+
+ wasm: don't widen unboxed small-struct returns
+
+ The previous commit widened narrow integral returns to ffi_arg, but
+ unbox_small_structs had already collapsed single-member struct returns
+ into those same scalar type ids, and a struct return is stored at its
+ natural size -- the caller's buffer is exactly the struct. This broke
+ cls_1_1byte and the bhaible small-struct return tests.
+
+ Remember whether the original return type was a struct and widen only
+ genuine scalar returns.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 59a9e68e59205da9c4db854dd1f1bce4e8b58dad
+Author: Anthony Green
+Date: Tue Jul 7 12:02:20 2026 -0400
+
+ wasm: widen narrow integral returns to ffi_arg
+
+ The return store wrote only the natural width for integer returns
+ narrower than a register (one byte for U8/S8, two for U16/S16, four on
+ wasm64 for 32-bit types), leaving the rest of the caller's ffi_arg
+ buffer as stack garbage. The documented API widens such returns to a
+ full ffi_arg, and every native port stores the full register width.
+ This is what plan_mixed.c caught: two identical calls disagreed in the
+ garbage bytes of their ffi_arg results.
+
+ Mask or sign-extend the JS result explicitly and store a full ffi_arg
+ (32 bits on wasm32, 64 on wasm64).
+
+ Co-Authored-By: Claude Fable 5
+
+commit 6954bd1715b8a5895adfad7c2196d4eb41094dc7
+Author: Anthony Green
+Date: Tue Jul 7 11:30:55 2026 -0400
+
+ testsuite: use ffi_arg for the integral return in plan.c
+
+ The struct-argument block stored an ffi_type_slong return into a plain
+ long. Integral returns narrower than a register are widened to
+ ffi_arg, so on LLP64 targets libffi writes 8 bytes into the 4-byte
+ variable and clobbers its neighbor; this is what broke plan.c on MSVC
+ Win64 while mingw survived by stack-layout luck.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 822d2e555fd97271c7c0ba8b6325cf4b541e3e9c
+Author: Anthony Green
+Date: Tue Jul 7 11:30:55 2026 -0400
+
+ Allow a NULL rvalue in ffi_call; handle it in the wasm port
+
+ Most ports already quietly supported calling with rvalue == NULL by
+ substituting scratch space for in-memory returns, and the new plan
+ tests rely on it, but the manual never promised it and the wasm port
+ passed NULL straight to the callee as the return-by-argument pointer,
+ writing the result through address zero.
+
+ Document NULL rvalue as supported for any return type, and in the wasm
+ port allocate stack scratch for return-by-argument callees and skip the
+ store-back for scalar returns when rvalue is NULL.
+
+ Co-Authored-By: Claude Fable 5
+
+commit 110156e1cceab76b4440ced9d95b5abaa84f30f7
+Author: Anthony Green
+Date: Tue Jul 7 11:30:55 2026 -0400
+
+ ffi_call: never modify the caller's avalue array
+
+ Eight ports made large struct arguments pass-by-value by alloca'ing a
+ copy inside ffi_call and writing the copy's address back into the
+ caller's avalue[]. Those pointers dangle as soon as ffi_call returns,
+ so any caller that legally reuses its avalue array for a second call
+ reads dead stack. The new ffi_call_plan equivalence tests do exactly
+ that and exposed it as plan_struct.c failures on hppa, MSVC Win64 and
+ macOS x86_64 under Rosetta; other targets passed only because the dead
+ copy usually survives between back-to-back calls.
+
+ Clone the pointer array once, on the first argument that needs a copy,
+ and redirect the clone instead. Document that ffi_call leaves the
+ caller's vector untouched.
+
+ Co-Authored-By: Claude Fable 5
+
+commit e3dae4657710a9ddd4eb4bdbefbcc73722aad9fe
+Author: Anthony Green
+Date: Tue Jul 7 09:18:31 2026 -0400
+
+ testsuite: add ffi_call_plan equivalence tests; fix EXTRA_DIST gaps
+
+ Add plan.c, plan_mixed.c, plan_spill.c, plan_struct.c and plan_var.c.
+ Each builds a reusable call plan (ffi_call_plan_alloc/invoke/free) and
+ checks it against ffi_call across the x86-64 argument-placement matrix:
+ the pure-GP64 direct thunks, the mixed GP+SSE fast path, stack-spilled
+ arguments, struct returns (in-memory including a NULL rvalue, and the
+ register pair), a struct-by-value argument that falls back to ffi_call,
+ and variadic calls. On targets without the accelerated backend the
+ generic plan simply wraps ffi_call, so the tests still pass there.
+
+ While adding these to EXTRA_DIST, register four existing tests that were
+ never listed (i128-1.c, large_struct_by_value.c, many_small_structs.c
+ and complex_i128.c). They ran via the .exp globs but were missing from
+ distribution tarballs.
+
+ Co-Authored-By: Claude Fable 5
+
+commit a40ab3a7e85f8c9b193b8a442297bb20de2b4293
+Author: Anthony Green
+Date: Tue Jul 7 08:19:33 2026 -0400
+
+ README: update 3.7.0 history entry
+
+ Co-Authored-By: Claude Fable 5
+
+commit 50baa0458db6b2d617e0d661292bbf12354b22ce
+Author: Anthony Green
+Date: Tue Jul 7 08:10:29 2026 -0400
+
+ x86_64: size closure argument buffer to match copy loop
+
+ ffi_closure_unix64_inner allocated a fixed 16 bytes when gathering a
+ register-passed argument's eightbytes, but the copy loop writes n * 8
+ bytes and examine_argument can classify up to MAX_CLASSES (4)
+ eightbytes for vector-classified types. Allocate n * 8 bytes so the
+ buffer always matches the loop bound.
+
+ Co-Authored-By: Claude Fable 5
+
+commit ff30d37615f82ef7bf1130b34528e01e1479b63d
+Author: Anthony Green
+Date: Tue Jul 7 08:10:29 2026 -0400
+
+ tramp.c: bound sscanf path field when parsing /proc/self/maps
+
+ The %s conversion into file[PATH_MAX] was unbounded while the input
+ line buffer is PATH_MAX+100 bytes, so a mapping with a very long
+ pathname could overflow the destination. Give the conversion an
+ explicit PATH_MAX field width and size the buffer PATH_MAX+1 to
+ accommodate the terminating NUL.
+
+ Co-Authored-By: Claude Fable 5
+
+commit ed1844e6b50d0498d2b88d1b3ef31cc39b6cf117
+Author: Anthony Green
+Date: Tue Jul 7 08:10:29 2026 -0400
+
+ .gitignore: ignore local build and scan artifact directories
+
+ Co-Authored-By: Claude Fable 5
+
+commit 854df63dbbda802985facbfab7a0e7c4169aefd1
+Merge: 2a595a18 731ec08f
+Author: Anthony Green
+Date: Tue Jul 7 08:06:25 2026 -0400
+
+ Merge pull request #991 from rorth/freebsd-libffi-note-gnu-stack
+
+ Include .note.GNU-stack on FreeBSD/x86
+
+commit 731ec08f3c3bbec883d823a71f1b0f86731cf312
+Author: Rainer Orth
+Date: Tue Jul 7 11:14:09 2026 +0200
+
+ Fix typo.
+
+commit 19cc1424e924767c7ca43f9e4d4c27fbae27d1d9
+Author: Rainer Orth
+Date: Tue Jul 7 10:27:55 2026 +0200
+
+ Include .note.GNU-stack on FreeBSD/x86
+
+ When building libffi on FreeBSD/x86 with --disable-shared as is done inside
+ the GCC tree, all tests FAIL when using GNU ld:
+
+ gld-2.46: warning: win64.o: missing .note.GNU-stack section implies executable stack
+ gld-2.46: NOTE: This behaviour is deprecated and will be removed in a future version of the linker
+
+ The emission of .note.GNU-stack in src/x86/*.S is currently guarded by
+ __ELF__ && __linux__, but FreeBSD/x86 uses that NOTE section, too. This
+ doesn't happen with libffi.so which lacks that NOTE.
+
+ Therefore this patch extends the emission accordingly.
+
+ Tested on amd64-pc-freebsd15.1 with and without --disable-shared.
+
+commit 2a595a183be146974bdf55716969e7a3e860f112
+Merge: c93f9428 f7b36640
+Author: Anthony Green
+Date: Thu Jul 2 20:03:30 2026 -0400
+
+ Merge pull request #989 from rvandermeulen/dlmalloc-msvc-lock-cast
+
+ dlmalloc: cast Win32 spin-lock word to LONG volatile* in CAS_LOCK/CLEAR_LOCK
+
+commit f7b36640544a59813ee372ed1a763affe7a67562
+Author: Ryan VanderMeulen
+Date: Thu Jul 2 19:32:00 2026 -0400
+
+ dlmalloc: cast Win32 spin-lock word to LONG volatile* in CAS_LOCK/CLEAR_LOCK
+
+ MLOCK_T is int, but on the Win32 MSC path CAS_LOCK/CLEAR_LOCK map to
+ _InterlockedExchange(LONG volatile *, LONG), so the int* lock word is
+ passed to a LONG volatile * parameter. clang 22 promotes
+ -Wincompatible-pointer-types to an error by default, which breaks the
+ clang-cl build (32- and 64-bit) with:
+
+ error: incompatible pointer types passing 'int *' to parameter of type
+ 'volatile long *' [-Wincompatible-pointer-types]
+
+ Cast to (LONG volatile *), matching what the ffi_spin_peek macro just
+ below already does in its _MSC_VER branch. LONG and int are both 32-bit
+ on Windows, so this is a type-correctness fix with no behavioral change.
+
+commit c93f9428d17cde4eb35517b58feeae6fb43aba5b
+Author: Anthony Green
+Date: Mon Jun 22 17:33:33 2026 -0400
+
+ testsuite: link threads tests against libpthread on NetBSD
+
+ The libffi.threads/tsan.c test calls pthread_create but the harness
+ only appended -lpthread for OpenBSD, FreeBSD, and some Linux targets.
+ On NetBSD this caused an undefined reference to pthread_create.
+
+ Fixes #988
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit 9ca53a19833dabaf80b73763638ab988bdbc42cc
+Author: Anthony Green
+Date: Sun Jun 21 18:42:37 2026 -0400
+
+ powerpc: fix aix/darwin closure build errors
+
+ unbuildable: the PPC_LD_* jump-table indexes (defined in ffi_powerpc.h,
+ which this file cannot include) were missing, and the closure helper
+ functions still declared an ffi_type* return type inconsistent with the
+ PPC_LD_* constants they now return.
+
+ Define the PPC_LD_* constants locally and change the closure helper
+ return types to int.
+
+ Fixes #987
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit 807a93115f927229f6e7cf5675384e8dc2bf76d5
+Author: Anthony Green
+Date: Sun Jun 21 18:31:12 2026 -0400
+
+ doc: mark the 3.7.0 release date as TBD
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit c876d4663183c40edbe5218603a26290900e7794
+Author: Anthony Green
+Date: Sun Jun 21 18:28:12 2026 -0400
+
+ doc: add 3.7.0 history entry
+
+ Record the next release (3.7.0, a minor bump for the new reusable call-plan API
+ on master) and the two PA-RISC fixes merged in #986.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit c3e054afebb2d9121ff4d12a56a803eecc31cc41
+Merge: 3cc6beb7 1f04d458
+Author: Anthony Green
+Date: Sun Jun 21 18:13:59 2026 -0400
+
+ Merge pull request #986 from libffi/ci-enable-parisc-regression
+
+ Add PA32/ARCompact struct-by-value regression tests; enable hppa CI
+
+commit 1f04d458126bd79c58196073477515c88de8163d
+Author: Anthony Green
+Date: Sun Jun 21 17:37:26 2026 -0400
+
+ pa: fix stack sizing for 5-8 byte structs passed by value (FFI_PA32)
+
+ ffi_size_stack_pa32 reserved a single stack slot for every struct argument,
+ but ffi_prep_args_pa32 passes a 5-8 byte struct inline in two even-aligned
+ slots (like a 64-bit value). With enough such arguments the marshaller's slot
+ count outgrew cif->bytes and it wrote past the allocated frame -- below sp and
+ over ffi_call_pa32's saved return pointer -- corrupting the call.
+
+ Make the sizing mirror the marshalling exactly: 1-4 byte structs take one slot,
+ 5-8 byte structs take "2 + (z & 1)" slots (z stays offset from the marshaller's
+ slot by the odd FIRST_ARG_SLOT, so the parity matches), and larger structs are
+ passed by pointer in one slot. Split the PA_HPUX long double case out since it
+ is passed by pointer.
+
+ Fixes the failure exercised by testsuite/libffi.call/many_small_structs.c.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit cf45dfc368ea7373fdd946603845782dbcc1549a
+Author: Anthony Green
+Date: Sun Jun 21 16:57:56 2026 -0400
+
+ testsuite: make many_small_structs actually trigger the PA32 overflow
+
+ With only 16 structs the PA-RISC slot under-count overflows ~32 bytes below
+ sp, landing in the 64-byte register save area where it clobbers nothing live,
+ so the call still returned correctly and the test passed under qemu.
+
+ Raise the count to 40 so the marshaller's writes sweep past the save area and
+ over ffi_call_pa32's saved return pointer (sp-20), corrupting the return path.
+ On a correct backend the arguments still marshal within the allocated frame and
+ the call returns the expected sums; the test now fails only on an unfixed
+ FFI_PA32.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit d55de2637abd9518d550d18a3e1d89a37bd32f47
+Author: Anthony Green
+Date: Sun Jun 21 16:39:30 2026 -0400
+
+ pa: fix build broken by the int128 generic types
+
+ FFI_TYPE_UINT128/FFI_TYPE_SINT128 were added after FFI_TYPE_COMPLEX, moving
+ FFI_TYPE_LAST to FFI_TYPE_SINT128. The PA-RISC tripwire still pinned
+ FFI_PA_TYPE_LAST to FFI_TYPE_COMPLEX, so "#error You likely have broken jump
+ tables" fired and PA-RISC stopped building once int128 support landed.
+
+ ffi_prep_cif_machdep maps every return type it does not handle explicitly --
+ including FFI_TYPE_COMPLEX and the 128-bit integer types -- to FFI_TYPE_INT via
+ its default case, so cif->flags never exceeds FFI_TYPE_COMPLEX and the linux.S /
+ hpux32.S return-value jump tables remain sufficient. Bump FFI_PA_TYPE_LAST to
+ FFI_TYPE_SINT128 to clear the tripwire, with a comment recording why no table
+ entries were needed.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit d7177f2db574c962b4366b1948b7f7e1aaf896c2
+Author: Anthony Green
+Date: Sun Jun 21 16:26:53 2026 -0400
+
+ ci: run the testsuite on PA-RISC 32-bit (hppa) under qemu-user
+
+ Add an hppa-linux-gnu entry to the build-cross-qemu matrix so the testsuite is
+ cross-compiled with the Debian gcc-hppa-linux-gnu toolchain and run under
+ qemu-user (binfmt). hppa-linux uses FFI_PA32, so this exercises the
+ ffi_prep_args_pa32 / ffi_size_stack_pa32 marshalling path and will surface the
+ many_small_structs.c regression until the backend is fixed.
+
+ Also drop the stale g++-5-hppa-linux-gnu pin from install.sh; the toolchain and
+ qemu-user-static now come from the CROSS_QEMU block.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit a384cba94f3935ac54a38f7c6489b6fbe0488c8c
+Author: Anthony Green
+Date: Sun Jun 21 16:26:21 2026 -0400
+
+ testsuite: add struct-by-value regression tests for PA32 and ARCompact
+
+ Two ffi_call tests covering argument-marshalling defects found by a security
+ review of the backends:
+
+ * many_small_structs.c -- passes sixteen 8-byte structs by value. On
+ PA-RISC 32-bit (FFI_PA32), ffi_size_stack_pa32 reserves one stack slot per
+ struct while ffi_prep_args_pa32 consumes two slots for a 5-8 byte struct
+ passed inline, so enough such args write past the asm-allocated frame.
+
+ * large_struct_by_value.c -- passes one 64-byte struct by value. On
+ ARCompact (FFI_ARCOMPACT), ffi_call_int sizes the stack arg area at two
+ words per argument, but a by-value struct is marshalled one word at a time
+ and words beyond the eight argument registers are written to that
+ under-sized area without bound.
+
+ Both are written as ordinary correctness tests (valid CIFs, asserts on the
+ expected sums); they pass on unaffected targets and fail on PA32/ARCompact
+ until the backends are fixed.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit 3cc6beb7d404e12d3458461d76557b79795816ff
+Author: Anthony Green
+Date: Sun Jun 21 09:47:10 2026 -0400
+
+ x86-64: replace the transparent plan cache with an explicit ffi_call_plan API
+
+ The transparent plan cache sped up ffi_call and closures by caching a
+ precomputed argument-placement plan per signature, but it charged every
+ call a cache lookup and content fingerprint, and it slightly regressed
+ signatures with no fast path (struct-by-value) that paid the key
+ computation only to find there was nothing to do.
+
+ Replace it with an opt-in API. ffi_call_plan_alloc builds a plan from a
+ prepared cif, ffi_call_plan_invoke applies it with no per-call lookup,
+ and ffi_call_plan_free releases it. The plan is opaque and caller-owned,
+ immutable once built (so it may be shared across threads), and falls back
+ to ffi_call for signatures with no fast path. ffi_call and the closure
+ path return to their original behaviour, so callers that don't adopt the
+ API pay nothing.
+
+ The x86-64 backend provides an accelerated plan; every other target gets
+ a portable fallback in prep_cif.c that wraps ffi_call, so the symbols are
+ defined on all platforms. The per-thread cache, content fingerprint,
+ thread-exit cleanup and closure demarshal layout are removed.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit 0c8f175eb650bb204b679996696b3e7706505d8f
+Author: Anthony Green
+Date: Sun Jun 21 08:18:14 2026 -0400
+
+ x86-64: trim the plan-cache fingerprint to the arg_types contents
+
+ abi, nargs and rtype are stored in the slot and compared exactly on every
+ hit, so folding them into the fingerprint is redundant work on the hot
+ path. Hashing only the arg_types element pointers keeps the same collision
+ behaviour and the same protection against a reused arg_types address, while
+ shortening the per-call hash and speeding up the all-pointer thunk path.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit 7b8c3283351298c55d09f580414ab937f358156c
+Merge: d4995b5b f1e21329
+Author: Anthony Green
+Date: Sat Jun 20 19:48:22 2026 -0400
+
+ Merge pull request #985 from libffi/plan-cache-fingerprint
+
+ x86-64: content-based plan-cache key and thread-exit cleanup
+
+commit f1e2132940880128e26b885ee956e76fe3cad2d8
+Author: Anthony Green
+Date: Sat Jun 20 19:46:50 2026 -0400
+
+ x86-64: content-based plan-cache key and thread-exit cleanup
+
+ Follow-up to the plan cache (PR #984), addressing two issues.
+
+ Content key: the cache keyed slots on the abi/nargs/rtype plus the arg_types
+ *array pointer*. If a cif's arg_types array was freed and the address later
+ reused for a different signature with the same abi/nargs/rtype, the keys
+ collided and a stale plan could be returned. Key on a 64-bit fingerprint that
+ folds in the element ffi_type pointers, so distinct argument types always get
+ distinct keys. Lookup is now O(nargs) cheap pointer reads -- still far less
+ than re-classifying the call.
+
+ Thread-exit cleanup: the per-thread cache leaked its malloc'd plans when a
+ thread exited. Register a pthread key whose destructor frees the thread's
+ plans. pthread is referenced via weak symbols, so a program that never links
+ pthread simply skips the registration (the cache is already bounded per thread).
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit d4995b5bff6e5ecefb46eda218c40717f895baaf
+Merge: 918ca8ed de3826a8
+Author: Anthony Green
+Date: Sat Jun 20 18:43:14 2026 -0400
+
+ Merge pull request #984 from libffi/fast-libffi-plan
+
+ x86-64: transparent plan cache for ffi_call and closures
+
+commit de3826a8063fde113ee8d1e455d459038d5814cc
+Author: Anthony Green
+Date: Sat Jun 20 18:14:08 2026 -0400
+
+ x86-64: limit the plan cache to the SysV ABI
+
+ The Win64/EFI64 plan path failed an MSVC execution test (libffi.call/float3)
+ that can't be reproduced or validated without a Windows toolchain. Revert
+ ffiw64.c to the proven upstream implementation and build plans only for
+ FFI_UNIX64, leaving Win64/EFI64/GNUW64 on the existing path. The SysV fast
+ path -- the bulk of the speedup -- is unaffected.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit fdbf8deaf297b308731385f91868ea8b5e7dbd21
+Author: Anthony Green
+Date: Sat Jun 20 17:59:54 2026 -0400
+
+ x86-64: fix Windows build of the plan cache
+
+ Native Windows targets build only ffiw64.c + win64.S for the x86 backend;
+ ffi64.c (which instantiates the plan-cache storage and ffi_build_plan_arch)
+ is not compiled, so ffiw64.c's ffi_plan_get left ffi_plan_miss / ffi_plan_cache
+ unresolved at link time. Instantiate the cache in ffiw64.c when X86_WIN64 is
+ defined (i.e. when ffi64.c is absent); on Unix x86-64 ffi64.c still does it and
+ X86_WIN64 is undefined, so there is no duplicate definition.
+
+ Also use a portable thread-local qualifier (FFI_TLS: __declspec(thread) on
+ MSVC, __thread elsewhere).
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit 872365e143c521b4a84f712aa60f4e638a8b8053
+Author: Anthony Green
+Date: Sat Jun 20 17:25:17 2026 -0400
+
+ x86-64: reword plan-cache comments to be self-contained
+
+ Drop the in-development "Mechanism A" label and the stale references to the
+ earlier opt-in arena/slab storage, so the comments describe the cache as it
+ now stands. No functional change.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit c4c9750c37868f26d1ce914592ca826e4ffc0b35
+Author: Anthony Green
+Date: Sat Jun 20 13:10:18 2026 -0400
+
+ x86-64: transparent plan cache for ffi_call and closures
+
+ Precompute each signature's argument placement once and cache it, keyed on
+ (abi, nargs, rtype, arg_types) in a per-thread direct-mapped cache, so ffi_call
+ and closures skip the per-call ABI classification. No API or ABI change --
+ every caller benefits transparently on a relink.
+
+ - Cache: src/plan-cache.h (inline O(1) probe) + src/plan-cache-impl.h
+ (per-thread storage, evict-and-free, FFI_PLAN_NONE for non-plan-able sigs).
+ - SysV (ffi64.c, unix64.S): pure-GP64 direct thunks (avalue -> arg regs), a
+ lean trampoline for register-only calls, and a plan executor reusing
+ ffi_call_unix64; closures use a precomputed demarshal layout. CFI on the
+ thunks/trampoline so exceptions unwind through them.
+ - Win64/EFI64 (ffiw64.c): plan executor + demarshal.
+ - W^X preserved: no runtime codegen; thunks shipped in .text.
+
+ Scalar/pointer/int128/float/double args are accelerated; struct/complex/x87
+ args fall back to the existing path. Pointer-heavy calls ~5x faster.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
+commit 918ca8edddbf542d9034d8e58931edfea036ecca
+Author: Anthony Green
+Date: Sat Jun 20 12:34:46 2026 -0400
+
+ ci: actually zip the MSVC binaries before the release upload
+
+ The MSVC 'Create binary distribution' step builds a *directory*
+ (libffi--x86-bit-msvc-binaries/), but 'Upload to GitHub Release'
+ pointed softprops/action-gh-release at a .zip file that was never created. With
+ no matching file the action just warns and succeeds, so on the v3.6.0 tag the
+ step reported success while attaching nothing.
+
+ Add a pwsh Compress-Archive step to produce the .zip (contents at top level, like
+ the upload-artifact layout), and set fail_on_unmatched_files: true so a missing
+ file fails the step loudly instead of silently passing.
+
+ Co-Authored-By: Claude Opus 4.8 (1M context)
+
commit f6303b8b0dd6353c6a5bb4de2e855a13b86f22cf
Author: Anthony Green
Date: Sat Jun 20 09:54:12 2026 -0400
diff --git a/deps/libffi/LICENSE b/deps/libffi/LICENSE
index 12b4970e0ddcb4..90a8b230284e5c 100644
--- a/deps/libffi/LICENSE
+++ b/deps/libffi/LICENSE
@@ -1,4 +1,4 @@
-libffi - Copyright (c) 1996-2025 Anthony Green, Red Hat, Inc and others.
+libffi - Copyright (c) 1996-2026 Anthony Green, Red Hat, Inc and others.
See source files for details.
Permission is hereby granted, free of charge, to any person obtaining
diff --git a/deps/libffi/README.md b/deps/libffi/README.md
index 7693f1daf6f850..19f78f632b0cd4 100644
--- a/deps/libffi/README.md
+++ b/deps/libffi/README.md
@@ -1,5 +1,5 @@
-libffi-3.6.0 was released on June 20, 2026.
+libffi-3.7.1 was released on July 10, 2026.
What is libffi?
@@ -201,6 +201,30 @@ History
See the git log for details at http://github.com/libffi/libffi.
+ 3.7.1 July-10-2026
+ Fix aarch64 ffi_call memory corruption when passing many large
+ structs by value.
+ Fix i386 thiscall/fastcall closure stack cleanup for 64-bit
+ integer and struct arguments.
+ Fix aarch64 int128 argument split between x7 and the stack on
+ Darwin (#993).
+ Fix aarch64 clang-cl link failure for HFA helper functions (#996).
+ Build a generic ffi_call_plan fallback on Windows x86-64.
+ Add Windows ARM64 (MSVC) build and continuous integration support.
+
+ 3.7.0 July-7-2026
+ Add reusable call plans (ffi_call_plan_alloc/ffi_call_plan_invoke/ffi_call_plan_free).
+ Fix PA-RISC build broken by the conditional __int128 support added in 3.6.0.
+ Fix PA-RISC stack overflow passing many small structs by value.
+ Fix powerpc aix/darwin closure build errors (#987).
+ Include .note.GNU-stack on FreeBSD/x86 (#991).
+ Fix MSVC Win32 spin-lock atomics in bundled dlmalloc (#989).
+ Harden static trampoline and x86_64 closure internals.
+ Fix ffi_call clobbering the caller's argument pointer array
+ when passing large structs by value.
+ Allow a NULL rvalue in ffi_call to discard the return value.
+ Fix wasm widening of integral returns narrower than ffi_arg.
+
3.6.0 Jun-20-2026
Add LoongArch32 support.
Add RISC-V static trampoline support.
diff --git a/deps/libffi/configure b/deps/libffi/configure
index 15b227e6888f82..e050ddc8675da1 100755
--- a/deps/libffi/configure
+++ b/deps/libffi/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.71 for libffi 3.6.0.
+# Generated by GNU Autoconf 2.71 for libffi 3.7.1.
#
# Report bugs to .
#
@@ -621,8 +621,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='libffi'
PACKAGE_TARNAME='libffi'
-PACKAGE_VERSION='3.6.0'
-PACKAGE_STRING='libffi 3.6.0'
+PACKAGE_VERSION='3.7.1'
+PACKAGE_STRING='libffi 3.7.1'
PACKAGE_BUGREPORT='http://github.com/libffi/libffi/issues'
PACKAGE_URL=''
@@ -1417,7 +1417,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures libffi 3.6.0 to adapt to many kinds of systems.
+\`configure' configures libffi 3.7.1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1489,7 +1489,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of libffi 3.6.0:";;
+ short | recursive ) echo "Configuration of libffi 3.7.1:";;
esac
cat <<\_ACEOF
@@ -1628,7 +1628,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-libffi configure 3.6.0
+libffi configure 3.7.1
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@@ -2259,7 +2259,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by libffi $as_me 3.6.0, which was
+It was created by libffi $as_me 3.7.1, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@@ -3233,8 +3233,11 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
ac_config_headers="$ac_config_headers fficonfig.h"
-FFI_VERSION_STRING="3.5.2"
-FFI_VERSION_NUMBER=30502
+FFI_VERSION_STRING="3.7.1"
+ffi_version_major=`echo "3.7.1" | cut -d. -f1`
+ffi_version_minor=`echo "3.7.1" | cut -d. -f2 | sed 's/[^0-9].*//'`
+ffi_version_micro=`echo "3.7.1" | cut -d. -f3 | sed 's/[^0-9].*//'`
+FFI_VERSION_NUMBER=`expr ${ffi_version_major:-0} \* 10000 + ${ffi_version_minor:-0} \* 100 + ${ffi_version_micro:-0}`
@@ -3983,7 +3986,7 @@ fi
# Define the identity of the package.
PACKAGE='libffi'
- VERSION='3.6.0'
+ VERSION='3.7.1'
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -20658,7 +20661,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by libffi $as_me 3.6.0, which was
+This file was extended by libffi $as_me 3.7.1, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -20726,7 +20729,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
-libffi config.status 3.6.0
+libffi config.status 3.7.1
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"
diff --git a/deps/libffi/configure.ac b/deps/libffi/configure.ac
index 2c8ba77970b3b0..3370acc3a39690 100644
--- a/deps/libffi/configure.ac
+++ b/deps/libffi/configure.ac
@@ -2,11 +2,17 @@ dnl Process this with autoconf to create configure
AC_PREREQ([2.68])
-AC_INIT([libffi],[3.6.0],[http://github.com/libffi/libffi/issues])
+AC_INIT([libffi],[3.7.1],[http://github.com/libffi/libffi/issues])
AC_CONFIG_HEADERS([fficonfig.h])
-FFI_VERSION_STRING="3.5.2"
-FFI_VERSION_NUMBER=30502
+dnl Derive the version macros from AC_INIT so they cannot drift when the
+dnl release version is bumped. FFI_VERSION_NUMBER encodes X.Y.Z as
+dnl X*10000 + Y*100 + Z, ignoring any non-numeric suffix (e.g. -rc0).
+FFI_VERSION_STRING="AC_PACKAGE_VERSION"
+ffi_version_major=`echo "AC_PACKAGE_VERSION" | cut -d. -f1`
+ffi_version_minor=`echo "AC_PACKAGE_VERSION" | cut -d. -f2 | sed 's/[[^0-9]].*//'`
+ffi_version_micro=`echo "AC_PACKAGE_VERSION" | cut -d. -f3 | sed 's/[[^0-9]].*//'`
+FFI_VERSION_NUMBER=`expr ${ffi_version_major:-0} \* 10000 + ${ffi_version_minor:-0} \* 100 + ${ffi_version_micro:-0}`
AC_SUBST(FFI_VERSION_STRING)
AC_SUBST(FFI_VERSION_NUMBER)
diff --git a/deps/libffi/doc/libffi.info b/deps/libffi/doc/libffi.info
index b5b81d5bcf815f..b43246f8ed4e3a 100644
--- a/deps/libffi/doc/libffi.info
+++ b/deps/libffi/doc/libffi.info
@@ -3,7 +3,7 @@ This is libffi.info, produced by makeinfo version 7.1 from libffi.texi.
This manual is for libffi, a portable foreign function interface
library.
- Copyright © 2008-2025 Anthony Green and Red Hat, Inc.
+ Copyright © 2008-2026 Anthony Green and Red Hat, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -38,7 +38,7 @@ libffi
This manual is for libffi, a portable foreign function interface
library.
- Copyright © 2008-2025 Anthony Green and Red Hat, Inc.
+ Copyright © 2008-2026 Anthony Green and Red Hat, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -112,6 +112,7 @@ File: libffi.info, Node: Using libffi, Next: Memory Usage, Prev: Introduction
* Simple Example:: A simple example.
* Types:: libffi type descriptions.
* Multiple ABIs:: Different passing styles on one platform.
+* Reusable Call Plans:: Building a call plan once and reusing it.
* The Closure API:: Writing a generic function.
* Closure Example:: A closure example.
* Thread Safety:: Thread safety.
@@ -200,6 +201,11 @@ function:
responsibility to ensure this. If CIF declares that the function
returns ‘void’ (using ‘ffi_type_void’), then RVALUE is ignored.
+ RVALUE may also be ‘NULL’, in which case the call is performed and
+ the return value is discarded. This works for any return type,
+ including structures returned in memory; ‘libffi’ supplies internal
+ scratch space for the callee when needed.
+
In most situations, ‘libffi’ will handle promotion according to the
ABI. However, for historical reasons, there is a special case with
return values that must be handled by your code. In particular,
@@ -213,7 +219,9 @@ function:
AVALUES is a vector of ‘void *’ pointers that point to the memory
locations holding the argument values for a call. If CIF declares
that the function has no arguments (i.e., NARGS was 0), then
- AVALUES is ignored.
+ AVALUES is ignored. ‘ffi_call’ does not modify the vector or the
+ argument values it points to, so both may be reused for subsequent
+ calls.
Note that while the return value must be register-sized, arguments
should exactly match their declared type. For example, if an
@@ -739,7 +747,7 @@ compilers that support them:
type descriptors in the previous example.
-File: libffi.info, Node: Multiple ABIs, Next: The Closure API, Prev: Types, Up: Using libffi
+File: libffi.info, Node: Multiple ABIs, Next: Reusable Call Plans, Prev: Types, Up: Using libffi
2.4 Multiple ABIs
=================
@@ -751,9 +759,46 @@ instance, the x86 platform has both ‘stdcall’ and ‘fastcall’ functions.
necessarily platform-specific.
-File: libffi.info, Node: The Closure API, Next: Closure Example, Prev: Multiple ABIs, Up: Using libffi
+File: libffi.info, Node: Reusable Call Plans, Next: The Closure API, Prev: Multiple ABIs, Up: Using libffi
+
+2.5 Reusable Call Plans
+=======================
+
+When the same signature is called many times - as language bindings
+typically do - you can build a “call plan” once and reuse it, so that
+each call avoids the work ‘ffi_call’ would otherwise repeat on every
+invocation. A plan is an opaque, caller-owned object built from a
+prepared ‘ffi_cif’.
+
+ -- Function: ffi_call_plan * ffi_call_plan_alloc (ffi_cif *CIF)
+ Builds and returns a reusable plan for the signature described by
+ CIF, which must already have been prepared with ‘ffi_prep_cif’.
+ The plan does not copy CIF; CIF must remain valid for as long as
+ the plan is used.
+
+ Returns ‘NULL’ only when memory cannot be allocated. A signature
+ for which no accelerated path exists is still valid: the returned
+ plan simply falls back to ‘ffi_call’ when it is invoked.
+
+ -- Function: void ffi_call_plan_invoke (ffi_call_plan *PLAN, void *FN,
+ void *RVALUE, void **AVALUES)
+ Calls FN using PLAN. The FN, RVALUE, and AVALUES arguments have
+ exactly the same meaning as for ‘ffi_call’ (*note The Basics::),
+ including the rule that integral return values narrower than a
+ register are widened to ‘ffi_arg’.
+
+ A plan is immutable once built, so a single plan may be invoked
+ concurrently from multiple threads without additional locking.
+
+ -- Function: void ffi_call_plan_free (ffi_call_plan *PLAN)
+ Releases a plan returned by ‘ffi_call_plan_alloc’. Passing ‘NULL’
+ is harmless. The ‘ffi_cif’ the plan was built from is not
+ affected.
+
+
+File: libffi.info, Node: The Closure API, Next: Closure Example, Prev: Reusable Call Plans, Up: Using libffi
-2.5 The Closure API
+2.6 The Closure API
===================
‘libffi’ also provides a way to write a generic function - a function
@@ -857,7 +902,7 @@ executable addresses.
File: libffi.info, Node: Closure Example, Next: Thread Safety, Prev: The Closure API, Up: Using libffi
-2.6 Closure Example
+2.7 Closure Example
===================
A trivial example that creates a new ‘puts’ by binding ‘fputs’ with
@@ -916,7 +961,7 @@ A trivial example that creates a new ‘puts’ by binding ‘fputs’ with
File: libffi.info, Node: Thread Safety, Prev: Closure Example, Up: Using libffi
-2.7 Thread Safety
+2.8 Thread Safety
=================
‘libffi’ is not completely thread-safe. However, many parts are, and if
@@ -1004,17 +1049,21 @@ Index
* cif: The Basics. (line 14)
* closure API: The Closure API. (line 13)
* closures: The Closure API. (line 13)
-* const char *: The Basics. (line 106)
+* const char *: The Basics. (line 113)
* FFI: Introduction. (line 31)
* ffi_call: The Basics. (line 72)
+* ffi_call_plan *: Reusable Call Plans. (line 12)
+* ffi_call_plan_alloc: Reusable Call Plans. (line 12)
+* ffi_call_plan_free: Reusable Call Plans. (line 32)
+* ffi_call_plan_invoke: Reusable Call Plans. (line 22)
* ffi_closure_alloc: The Closure API. (line 19)
* ffi_closure_free: The Closure API. (line 26)
* FFI_CLOSURES: The Closure API. (line 13)
-* ffi_get_closure_size: The Basics. (line 118)
-* ffi_get_default_abi: The Basics. (line 115)
+* ffi_get_closure_size: The Basics. (line 125)
+* ffi_get_default_abi: The Basics. (line 122)
* ffi_get_struct_offsets: Size and Alignment. (line 39)
-* ffi_get_version: The Basics. (line 106)
-* ffi_get_version_number: The Basics. (line 110)
+* ffi_get_version: The Basics. (line 113)
+* ffi_get_version_number: The Basics. (line 117)
* ffi_prep_cif: The Basics. (line 16)
* ffi_prep_cif_var: The Basics. (line 39)
* ffi_prep_closure_loc: The Closure API. (line 41)
@@ -1051,12 +1100,14 @@ Index
* ffi_type_ushort: Primitive Types. (line 53)
* ffi_type_void: Primitive Types. (line 10)
* Foreign Function Interface: Introduction. (line 31)
-* size_t: The Basics. (line 118)
-* unsigned int: The Basics. (line 115)
-* unsigned long: The Basics. (line 110)
+* size_t: The Basics. (line 125)
+* unsigned int: The Basics. (line 122)
+* unsigned long: The Basics. (line 117)
* void: The Basics. (line 72)
-* void <1>: The Closure API. (line 19)
-* void <2>: The Closure API. (line 26)
+* void <1>: Reusable Call Plans. (line 22)
+* void <2>: Reusable Call Plans. (line 32)
+* void <3>: The Closure API. (line 19)
+* void <4>: The Closure API. (line 26)
@@ -1064,23 +1115,24 @@ Tag Table:
Node: Top1387
Node: Introduction2909
Node: Using libffi4569
-Node: The Basics5098
-Node: Simple Example10868
-Node: Types11925
-Node: Primitive Types12436
-Node: Structures14753
-Node: Size and Alignment15864
-Node: Arrays Unions Enums18135
-Node: Type Example21112
-Node: Complex22418
-Node: Complex Type Example23932
-Node: Multiple ABIs26984
-Node: The Closure API27367
-Node: Closure Example31703
-Node: Thread Safety33347
-Node: Memory Usage34180
-Node: Missing Features35455
-Node: Index35832
+Node: The Basics5172
+Node: Simple Example11346
+Node: Types12403
+Node: Primitive Types12914
+Node: Structures15231
+Node: Size and Alignment16342
+Node: Arrays Unions Enums18613
+Node: Type Example21590
+Node: Complex22896
+Node: Complex Type Example24410
+Node: Multiple ABIs27462
+Node: Reusable Call Plans27849
+Node: The Closure API29566
+Node: Closure Example33908
+Node: Thread Safety35552
+Node: Memory Usage36385
+Node: Missing Features37660
+Node: Index38037
End Tag Table
diff --git a/deps/libffi/doc/libffi.pdf b/deps/libffi/doc/libffi.pdf
index 3aa5406e61e086..250d34faf71159 100644
Binary files a/deps/libffi/doc/libffi.pdf and b/deps/libffi/doc/libffi.pdf differ
diff --git a/deps/libffi/doc/libffi.texi b/deps/libffi/doc/libffi.texi
index 73b128f863e7be..251214f890d3f5 100644
--- a/deps/libffi/doc/libffi.texi
+++ b/deps/libffi/doc/libffi.texi
@@ -18,7 +18,7 @@
This manual is for libffi, a portable foreign function interface
library.
-Copyright @copyright{} 2008--2025 Anthony Green and Red Hat, Inc.
+Copyright @copyright{} 2008--2026 Anthony Green and Red Hat, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -118,6 +118,7 @@ values passed between the two languages.
* Simple Example:: A simple example.
* Types:: libffi type descriptions.
* Multiple ABIs:: Different passing styles on one platform.
+* Reusable Call Plans:: Building a call plan once and reusing it.
* The Closure API:: Writing a generic function.
* Closure Example:: A closure example.
* Thread Safety:: Thread safety.
@@ -213,6 +214,11 @@ to ensure this. If @var{cif} declares that the function returns
@code{void} (using @code{ffi_type_void}), then @var{rvalue} is
ignored.
+@var{rvalue} may also be @code{NULL}, in which case the call is
+performed and the return value is discarded. This works for any
+return type, including structures returned in memory; @code{libffi}
+supplies internal scratch space for the callee when needed.
+
In most situations, @code{libffi} will handle promotion according to
the ABI. However, for historical reasons, there is a special case
with return values that must be handled by your code. In particular,
@@ -226,7 +232,9 @@ full @code{ffi_arg} into the return value.
@var{avalues} is a vector of @code{void *} pointers that point to the
memory locations holding the argument values for a call. If @var{cif}
declares that the function has no arguments (i.e., @var{nargs} was 0),
-then @var{avalues} is ignored.
+then @var{avalues} is ignored. @code{ffi_call} does not modify the
+vector or the argument values it points to, so both may be reused for
+subsequent calls.
Note that while the return value must be register-sized, arguments
should exactly match their declared type. For example, if an argument
@@ -806,6 +814,45 @@ necessarily platform-specific.
@c FIXME: document the platforms
+@node Reusable Call Plans
+@section Reusable Call Plans
+
+When the same signature is called many times -- as language bindings
+typically do -- you can build a @dfn{call plan} once and reuse it, so
+that each call avoids the work @code{ffi_call} would otherwise repeat
+on every invocation. A plan is an opaque, caller-owned object built
+from a prepared @code{ffi_cif}.
+
+@findex ffi_call_plan_alloc
+@defun {ffi_call_plan *} ffi_call_plan_alloc (ffi_cif *@var{cif})
+Builds and returns a reusable plan for the signature described by
+@var{cif}, which must already have been prepared with
+@code{ffi_prep_cif}. The plan does not copy @var{cif}; @var{cif} must
+remain valid for as long as the plan is used.
+
+Returns @code{NULL} only when memory cannot be allocated. A signature
+for which no accelerated path exists is still valid: the returned plan
+simply falls back to @code{ffi_call} when it is invoked.
+@end defun
+
+@findex ffi_call_plan_invoke
+@defun void ffi_call_plan_invoke (ffi_call_plan *@var{plan}, void *@var{fn}, void *@var{rvalue}, void **@var{avalues})
+Calls @var{fn} using @var{plan}. The @var{fn}, @var{rvalue}, and
+@var{avalues} arguments have exactly the same meaning as for
+@code{ffi_call} (@pxref{The Basics}), including the rule that integral
+return values narrower than a register are widened to @code{ffi_arg}.
+
+A plan is immutable once built, so a single plan may be invoked
+concurrently from multiple threads without additional locking.
+@end defun
+
+@findex ffi_call_plan_free
+@defun void ffi_call_plan_free (ffi_call_plan *@var{plan})
+Releases a plan returned by @code{ffi_call_plan_alloc}. Passing
+@code{NULL} is harmless. The @code{ffi_cif} the plan was built from is
+not affected.
+@end defun
+
@node The Closure API
@section The Closure API
diff --git a/deps/libffi/doc/stamp-vti b/deps/libffi/doc/stamp-vti
index 6658c1da7e7825..e755454e9c8215 100644
--- a/deps/libffi/doc/stamp-vti
+++ b/deps/libffi/doc/stamp-vti
@@ -1,4 +1,4 @@
-@set UPDATED 20 June 2026
-@set UPDATED-MONTH June 2026
-@set EDITION 3.6.0
-@set VERSION 3.6.0
+@set UPDATED 10 July 2026
+@set UPDATED-MONTH July 2026
+@set EDITION 3.7.1
+@set VERSION 3.7.1
diff --git a/deps/libffi/doc/version.texi b/deps/libffi/doc/version.texi
index 6658c1da7e7825..e755454e9c8215 100644
--- a/deps/libffi/doc/version.texi
+++ b/deps/libffi/doc/version.texi
@@ -1,4 +1,4 @@
-@set UPDATED 20 June 2026
-@set UPDATED-MONTH June 2026
-@set EDITION 3.6.0
-@set VERSION 3.6.0
+@set UPDATED 10 July 2026
+@set UPDATED-MONTH July 2026
+@set EDITION 3.7.1
+@set VERSION 3.7.1
diff --git a/deps/libffi/generate-headers.py b/deps/libffi/generate-headers.py
index 312b0099ef2f0c..e2d2942deffb84 100644
--- a/deps/libffi/generate-headers.py
+++ b/deps/libffi/generate-headers.py
@@ -7,8 +7,8 @@
from pathlib import Path
-LIBFFI_VERSION = '3.6.0'
-LIBFFI_VERSION_NUMBER = '30600'
+LIBFFI_VERSION = '3.7.1'
+LIBFFI_VERSION_NUMBER = '30701'
def normalize_arch(target_arch):
aliases = {
diff --git a/deps/libffi/include/ffi.h.in b/deps/libffi/include/ffi.h.in
index 4ac9c2598c1517..35f09cf43315ca 100644
--- a/deps/libffi/include/ffi.h.in
+++ b/deps/libffi/include/ffi.h.in
@@ -1,6 +1,6 @@
/* -----------------------------------------------------------------*-C-*-
libffi @VERSION@
- - Copyright (c) 2011, 2014, 2019, 2021, 2022, 2024, 2025 Anthony Green
+ - Copyright (c) 2011, 2014, 2019, 2021, 2022, 2024, 2025, 2026 Anthony Green
- Copyright (c) 1996-2003, 2007, 2008 Red Hat, Inc.
Permission is hereby granted, free of charge, to any person
@@ -524,6 +524,32 @@ void ffi_call(ffi_cif *cif,
void *rvalue,
void **avalue);
+/* Reusable call plans.
+
+ A plan captures a signature's argument placement once so that repeated
+ calls skip the per-call work that ffi_call would otherwise redo. Build one
+ with ffi_call_plan_alloc, invoke it as many times as you like, and release
+ it with ffi_call_plan_free.
+
+ The plan is opaque and caller-owned. The cif must outlive the plan.
+ ffi_call_plan_alloc returns NULL only on allocation failure; a signature
+ with no fast path is still valid and ffi_call_plan_invoke falls back to
+ ffi_call for it. A plan is immutable once built, so it may be shared and
+ invoked concurrently from multiple threads. */
+typedef struct ffi_call_plan ffi_call_plan;
+
+FFI_API
+ffi_call_plan *ffi_call_plan_alloc (ffi_cif *cif);
+
+FFI_API
+void ffi_call_plan_invoke (ffi_call_plan *plan,
+ void (*fn)(void),
+ void *rvalue,
+ void **avalue);
+
+FFI_API
+void ffi_call_plan_free (ffi_call_plan *plan);
+
FFI_API
ffi_status ffi_get_struct_offsets (ffi_abi abi, ffi_type *struct_type,
size_t *offsets);
diff --git a/deps/libffi/libffi.gyp b/deps/libffi/libffi.gyp
index 2d43dcdb733c97..f3122a4e2a0041 100644
--- a/deps/libffi/libffi.gyp
+++ b/deps/libffi/libffi.gyp
@@ -204,13 +204,6 @@
},
],
'conditions': [
- ['OS == "win" and clang == 1', {
- 'msvs_settings': {
- 'VCCLCompilerTool': {
- 'AdditionalOptions': ['-Wno-incompatible-pointer-types'],
- },
- },
- }],
['OS == "win" and (target_arch == "x64" or target_arch == "x86_64")', {
'actions': [
{
diff --git a/deps/libffi/libffi.map.in b/deps/libffi/libffi.map.in
index 838acfea503602..f4e366eb60bceb 100644
--- a/deps/libffi/libffi.map.in
+++ b/deps/libffi/libffi.map.in
@@ -58,6 +58,17 @@ LIBFFI_BASE_8.1 {
ffi_get_closure_size;
} LIBFFI_BASE_8.0;
+/* ----------------------------------------------------------------------
+ Reusable call plans (ffi_call_plan_*). Additive: new symbols only, the
+ existing struct layout and entry points are untouched.
+ -------------------------------------------------------------------- */
+LIBFFI_CALL_PLAN_8.4 {
+ global:
+ ffi_call_plan_alloc;
+ ffi_call_plan_invoke;
+ ffi_call_plan_free;
+} LIBFFI_BASE_8.1;
+
#ifdef FFI_TARGET_HAS_COMPLEX_TYPE
LIBFFI_COMPLEX_8.0 {
global:
diff --git a/deps/libffi/libtool-version b/deps/libffi/libtool-version
index 656cc6056f7401..c5545eab8bf6c2 100644
--- a/deps/libffi/libtool-version
+++ b/deps/libffi/libtool-version
@@ -26,4 +26,4 @@
# release, then set age to 0.
#
# CURRENT:REVISION:AGE
-11:1:3
+12:1:4
diff --git a/deps/libffi/msvcc.sh b/deps/libffi/msvcc.sh
index 301e2fbd6840fe..a69f57fd98882e 100755
--- a/deps/libffi/msvcc.sh
+++ b/deps/libffi/msvcc.sh
@@ -94,6 +94,13 @@ do
cl="clang-cl"
shift 1
;;
+ -E)
+ # DejaGnu-style preprocessing (-E -o ): handled specially
+ # below, since cl writes preprocessed output to stdout and prints
+ # the source file name to stderr.
+ preprocess="true"
+ shift 1
+ ;;
-O0)
args="$args -Od"
shift 1
@@ -236,19 +243,25 @@ do
shift 1
;;
-S)
- args="$args -FAs"
+ # Compile to assembly without linking, like gcc -S: emit the
+ # listing and stop after the compile step.
+ args="$args -c -FAs"
+ compile_to_asm="true"
shift 1
;;
-o)
outdir="$(dirname $2)"
base="$(basename $2|sed 's/\.[^.]*//g')"
- if [ -n "$single" ]; then
+ ppout="$2"
+ if [ -n "$compile_to_asm" ]; then
+ output="-Fo$outdir/$base.obj -Fa$2"
+ elif [ -n "$single" ]; then
output="-Fo$2"
else
output="-Fe$2"
fi
armasm_output="-o $2"
- if [ -n "$assembly" ]; then
+ if [ -n "$assembly" ] || [ -n "$compile_to_asm" ]; then
args="$args $output"
else
args="$args $output -Fd$outdir/$base -Fp$outdir/$base -Fa$outdir/$base"
@@ -294,7 +307,19 @@ if [ -n "$debug_crt" ]; then
md="${md}d"
fi
-if [ -n "$assembly" ]; then
+if [ -n "$preprocess" ]; then
+ # Write the preprocessed output to the -o target (or stdout), and
+ # swallow stderr: cl prints the source file name there, which the
+ # testsuite's feature probes would mistake for a diagnostic.
+ args="$(echo $args | sed 's%[-/]F[edpoa][^ ]*%%g')"
+
+ if test -n "$verbose"; then
+ echo "$cl -EP $args > ${ppout:-/dev/stdout}"
+ fi
+
+ eval "\"$cl\" -EP $args" > "${ppout:-/dev/stdout}" 2>/dev/null
+ result=$?
+elif [ -n "$assembly" ]; then
if [ -z "$outdir" ]; then
outdir="."
fi
diff --git a/deps/libffi/src/aarch64/ffi.c b/deps/libffi/src/aarch64/ffi.c
index 715e478c0edd14..2e6a2ad2624c07 100644
--- a/deps/libffi/src/aarch64/ffi.c
+++ b/deps/libffi/src/aarch64/ffi.c
@@ -378,13 +378,13 @@ extend_integer_type (void *source, int type)
}
}
-#if defined(_MSC_VER)
+#if defined(_MSC_VER) && !defined(__clang__)
void extend_hfa_type (void *dest, void *src, int h);
#else
static void
extend_hfa_type (void *dest, void *src, int h)
{
- ssize_t f = h - AARCH64_RET_S4;
+ ptrdiff_t f = h - AARCH64_RET_S4;
void *x0;
#define BTI_J "hint #36"
@@ -449,7 +449,7 @@ extend_hfa_type (void *dest, void *src, int h)
}
#endif
-#if defined(_MSC_VER)
+#if defined(_MSC_VER) && !defined(__clang__)
void* compress_hfa_type (void *dest, void *src, int h);
#else
static void *
@@ -550,7 +550,9 @@ allocate_int128_to_reg_or_stack (struct call_context *context,
ngrn += ngrn & 1;
#endif
- if (ngrn < N_X_ARG_REG)
+ /* The value must fit entirely in registers, i.e. the low half may
+ not be allocated to x7 with the high half spilled to the stack. */
+ if (ngrn + 2 <= N_X_ARG_REG)
{
ret = &context->x[ngrn];
ngrn += 2;
@@ -642,6 +644,23 @@ ffi_prep_cif_machdep (ffi_cif *cif)
break;
}
+ /* Composites larger than 16 bytes (that are not HFAs) are passed by
+ invisible reference: ffi_call copies the payload into the argument
+ slab (next_struct_area, growing down) and, once the X registers are
+ exhausted, also spills the by-ref pointer into the same slab (the
+ NSAA, growing up). The generic prep_cif accounting in cif->bytes
+ only charges the payload copy, not that 8-byte pointer slot, so the
+ two regions can collide and a later struct copy can overwrite an
+ already-spilled pointer with the copied payload bytes. Reserve an
+ extra 8 bytes per such argument so the slab is always large enough
+ for both. */
+ for (i = 0, n = cif->nargs; i < n; i++)
+ {
+ ffi_type *ty = cif->arg_types[i];
+ if (ty->size > 16 && !is_vfp_type (ty))
+ bytes += 8;
+ }
+
/* Round the stack up to a multiple of the stack alignment requirement. */
cif->bytes = (unsigned) FFI_ALIGN(bytes, 16);
cif->flags = flags;
diff --git a/deps/libffi/src/alpha/ffi.c b/deps/libffi/src/alpha/ffi.c
index cb683ec14317f8..5830ee3f2109e4 100644
--- a/deps/libffi/src/alpha/ffi.c
+++ b/deps/libffi/src/alpha/ffi.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- ffi.c - Copyright (c) 2012 Anthony Green
+ ffi.c - Copyright (c) 2012, 2026 Anthony Green
Copyright (c) 1998, 2001, 2007, 2008 Red Hat, Inc.
Alpha Foreign Function Interface
diff --git a/deps/libffi/src/alpha/ffitarget.h b/deps/libffi/src/alpha/ffitarget.h
index f80f13e85fe67e..da63dc08813a26 100644
--- a/deps/libffi/src/alpha/ffitarget.h
+++ b/deps/libffi/src/alpha/ffitarget.h
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------*-C-*-
- ffitarget.h - Copyright (c) 2012 Anthony Green
+ ffitarget.h - Copyright (c) 2012, 2026 Anthony Green
Copyright (c) 1996-2003 Red Hat, Inc.
Target configuration macros for Alpha.
diff --git a/deps/libffi/src/closures.c b/deps/libffi/src/closures.c
index 124e1f386a7138..c1fbcf87ce29b6 100644
--- a/deps/libffi/src/closures.c
+++ b/deps/libffi/src/closures.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- closures.c - Copyright (c) 2019, 2022 Anthony Green
+ closures.c - Copyright (c) 2019, 2022, 2026 Anthony Green
Copyright (c) 2007, 2009, 2010 Red Hat, Inc.
Copyright (C) 2007, 2009, 2010 Free Software Foundation, Inc
Copyright (c) 2011 Plausible Labs Cooperative, Inc.
diff --git a/deps/libffi/src/dlmalloc.c b/deps/libffi/src/dlmalloc.c
index 5b400d65de3db4..93e81967039359 100644
--- a/deps/libffi/src/dlmalloc.c
+++ b/deps/libffi/src/dlmalloc.c
@@ -1916,8 +1916,8 @@ static FORCEINLINE void x86_clear_lock(int* sl) {
#define CLEAR_LOCK(sl) x86_clear_lock(sl)
#else /* Win32 MSC */
-#define CAS_LOCK(sl) interlockedexchange(sl, (LONG)1)
-#define CLEAR_LOCK(sl) interlockedexchange (sl, (LONG)0)
+#define CAS_LOCK(sl) interlockedexchange((LONG volatile *)(sl), (LONG)1)
+#define CLEAR_LOCK(sl) interlockedexchange ((LONG volatile *)(sl), (LONG)0)
#endif /* ... gcc spins locks ... */
diff --git a/deps/libffi/src/frv/ffi.c b/deps/libffi/src/frv/ffi.c
index 99dd41d32d4c8f..4eb8e1ddfb87c8 100644
--- a/deps/libffi/src/frv/ffi.c
+++ b/deps/libffi/src/frv/ffi.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- ffi.c - Copyright (C) 2004 Anthony Green
+ ffi.c - Copyright (C) 2004, 2026 Anthony Green
Copyright (C) 2007 Free Software Foundation, Inc.
Copyright (C) 2008 Red Hat, Inc.
diff --git a/deps/libffi/src/ia64/ffi.c b/deps/libffi/src/ia64/ffi.c
index c00ef08543289a..00e1ae15707c5f 100644
--- a/deps/libffi/src/ia64/ffi.c
+++ b/deps/libffi/src/ia64/ffi.c
@@ -1,7 +1,7 @@
/* -----------------------------------------------------------------------
ffi.c - Copyright (c) 1998, 2007, 2008, 2012 Red Hat, Inc.
Copyright (c) 2000 Hewlett Packard Company
- Copyright (c) 2011 Anthony Green
+ Copyright (c) 2011, 2026 Anthony Green
IA64 Foreign Function Interface
diff --git a/deps/libffi/src/m32r/ffi.c b/deps/libffi/src/m32r/ffi.c
index 6fab50b115ed24..21d499abe00302 100644
--- a/deps/libffi/src/m32r/ffi.c
+++ b/deps/libffi/src/m32r/ffi.c
@@ -1,7 +1,7 @@
/* -----------------------------------------------------------------------
ffi.c - Copyright (c) 2004 Renesas Technology
Copyright (c) 2008 Red Hat, Inc.
- Copyright (c) 2022 Anthony Green
+ Copyright (c) 2022, 2026 Anthony Green
M32R Foreign Function Interface
@@ -180,10 +180,10 @@ void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue)
{
extended_cif ecif;
ffi_type **arg_types = cif->arg_types;
+ void **avalue_copy = NULL;
int i, nargs = cif->nargs;
ecif.cif = cif;
- ecif.avalue = avalue;
/* If the return value is a struct and we don't have
a return value address then we need to make one. */
@@ -196,7 +196,8 @@ void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue)
ecif.rvalue = rvalue;
/* If we have any large structure arguments, make a copy so we are passing
- by value. */
+ by value. The pointer array is cloned first: the caller owns avalue[]
+ and may reuse it for another call, so it must not be modified. */
for (i = 0; i < nargs; i++)
{
ffi_type *at = arg_types[i];
@@ -204,10 +205,17 @@ void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue)
if (at->type == FFI_TYPE_STRUCT && size > 4)
{
char *argcopy = alloca (size);
+ if (avalue_copy == NULL)
+ {
+ avalue_copy = alloca (nargs * sizeof (void *));
+ memcpy (avalue_copy, avalue, nargs * sizeof (void *));
+ avalue = avalue_copy;
+ }
memcpy (argcopy, avalue[i], size);
avalue[i] = argcopy;
}
}
+ ecif.avalue = avalue;
switch (cif->abi)
{
diff --git a/deps/libffi/src/moxie/ffi.c b/deps/libffi/src/moxie/ffi.c
index ace7b23f855763..582c06f5dcebab 100644
--- a/deps/libffi/src/moxie/ffi.c
+++ b/deps/libffi/src/moxie/ffi.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- ffi.c - Copyright (C) 2012, 2013, 2018, 2021, 2022 Anthony Green
+ ffi.c - Copyright (C) 2012, 2013, 2018, 2021, 2022, 2026 Anthony Green
Moxie Foreign Function Interface
@@ -129,10 +129,10 @@ void ffi_call(ffi_cif *cif,
{
extended_cif ecif;
ffi_type **arg_types = cif->arg_types;
+ void **avalue_copy = NULL;
int i, nargs = cif->nargs;
ecif.cif = cif;
- ecif.avalue = avalue;
/* If the return value is a struct and we don't have a return */
/* value address then we need to make one */
@@ -146,7 +146,8 @@ void ffi_call(ffi_cif *cif,
ecif.rvalue = rvalue;
/* If we have any large structure arguments, make a copy so we are passing
- by value. */
+ by value. The pointer array is cloned first: the caller owns avalue[]
+ and may reuse it for another call, so it must not be modified. */
for (i = 0; i < nargs; i++)
{
ffi_type *at = arg_types[i];
@@ -154,10 +155,17 @@ void ffi_call(ffi_cif *cif,
if (at->type == FFI_TYPE_STRUCT) /* && size > 4) All struct args?? */
{
char *argcopy = alloca (size);
+ if (avalue_copy == NULL)
+ {
+ avalue_copy = alloca (nargs * sizeof (void *));
+ memcpy (avalue_copy, avalue, nargs * sizeof (void *));
+ avalue = avalue_copy;
+ }
memcpy (argcopy, avalue[i], size);
avalue[i] = argcopy;
}
}
+ ecif.avalue = avalue;
switch (cif->abi)
{
diff --git a/deps/libffi/src/or1k/ffi.c b/deps/libffi/src/or1k/ffi.c
index 0393ea4af42af6..9303880a5e82b8 100644
--- a/deps/libffi/src/or1k/ffi.c
+++ b/deps/libffi/src/or1k/ffi.c
@@ -121,6 +121,7 @@ void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue)
int i;
int size;
ffi_type **arg;
+ void **avalue_copy = NULL;
/* Calculate size to allocate on stack */
@@ -135,13 +136,21 @@ void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue)
size += 8;
/* If we have any large structure arguments, make a copy so we are passing
- by value. */
+ by value. The pointer array is cloned first: the caller owns
+ avalue[] and may reuse it for another call, so it must not be
+ modified. */
{
ffi_type *at = cif->arg_types[i];
int size = at->size;
if (at->type == FFI_TYPE_STRUCT) /* && size > 4) All struct args? */
{
char *argcopy = alloca (size);
+ if (avalue_copy == NULL)
+ {
+ avalue_copy = alloca (cif->nargs * sizeof (void *));
+ memcpy (avalue_copy, avalue, cif->nargs * sizeof (void *));
+ avalue = avalue_copy;
+ }
memcpy (argcopy, avalue[i], size);
avalue[i] = argcopy;
}
diff --git a/deps/libffi/src/pa/ffi.c b/deps/libffi/src/pa/ffi.c
index 5460ca2fdbe147..e5de1dc72eb38e 100644
--- a/deps/libffi/src/pa/ffi.c
+++ b/deps/libffi/src/pa/ffi.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- ffi.c - (c) 2011 Anthony Green
+ ffi.c - (c) 2011, 2026 Anthony Green
(c) 2008 Red Hat, Inc.
(c) 2006 Free Software Foundation, Inc.
(c) 2003-2004 Randolph Chung
@@ -280,9 +280,24 @@ static void ffi_size_stack_pa32(ffi_cif *cif)
#ifdef PA_HPUX
case FFI_TYPE_LONGDOUBLE:
+ z += 1; /* passed by pointer, like a large struct */
+ break;
#endif
+
case FFI_TYPE_STRUCT:
- z += 1; /* pass by ptr, callee will copy */
+ /* This must mirror the slot accounting in ffi_prep_args_pa32:
+ structs of 1-4 bytes occupy one slot, structs of 5-8 bytes are
+ passed inline in two even-aligned slots (exactly like a 64-bit
+ value), and larger structs are passed by pointer in one slot.
+ z stays offset from the marshaller's slot by FIRST_ARG_SLOT (odd),
+ so (z & 1) tracks the same alignment the marshaller applies. */
+ {
+ size_t len = (*ptr)->size;
+ if (len <= 4 || len > 8)
+ z += 1;
+ else
+ z += 2 + (z & 1);
+ }
break;
default: /* <= 32-bit values */
@@ -363,12 +378,13 @@ void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue)
extended_cif ecif;
size_t i, nargs = cif->nargs;
ffi_type **arg_types = cif->arg_types;
+ void **avalue_copy = NULL;
ecif.cif = cif;
- ecif.avalue = avalue;
/* If we have any large structure arguments, make a copy so we are passing
- by value. */
+ by value. The pointer array is cloned first: the caller owns avalue[]
+ and may reuse it for another call, so it must not be modified. */
for (i = 0; i < nargs; i++)
{
ffi_type *at = arg_types[i];
@@ -376,10 +392,17 @@ void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue)
if (at->type == FFI_TYPE_STRUCT && size > 8)
{
char *argcopy = alloca (size);
+ if (avalue_copy == NULL)
+ {
+ avalue_copy = alloca (nargs * sizeof (void *));
+ memcpy (avalue_copy, avalue, nargs * sizeof (void *));
+ avalue = avalue_copy;
+ }
memcpy (argcopy, avalue[i], size);
avalue[i] = argcopy;
}
}
+ ecif.avalue = avalue;
/* If the return value is a struct and we don't have a return
value address then we need to make one. */
diff --git a/deps/libffi/src/pa/ffitarget.h b/deps/libffi/src/pa/ffitarget.h
index dae854a695bb30..f6f09975cfac36 100644
--- a/deps/libffi/src/pa/ffitarget.h
+++ b/deps/libffi/src/pa/ffitarget.h
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------*-C-*-
- ffitarget.h - Copyright (c) 2012 Anthony Green
+ ffitarget.h - Copyright (c) 2012, 2026 Anthony Green
Copyright (c) 1996-2003 Red Hat, Inc.
Target configuration macros for hppa.
@@ -82,11 +82,19 @@ typedef enum ffi_abi {
#define FFI_TYPE_SMALL_STRUCT7 -7
#define FFI_TYPE_SMALL_STRUCT8 -8
-/* linux.S and hpux32.S expect FFI_TYPE_COMPLEX is the last generic type. */
-#define FFI_PA_TYPE_LAST FFI_TYPE_COMPLEX
-
-/* If new generic types are added, the jump tables in linux.S and hpux32.S
- likely need updating. */
+/* The return-value jump tables in linux.S and hpux32.S are indexed by
+ cif->flags, which ffi_prep_cif_machdep derives from the return type. Any
+ return type it does not handle explicitly -- including FFI_TYPE_COMPLEX and
+ the 128-bit integer types FFI_TYPE_UINT128/FFI_TYPE_SINT128 -- falls through
+ to the default case and is mapped to FFI_TYPE_INT, so cif->flags never
+ exceeds FFI_TYPE_COMPLEX and the existing tables remain sufficient. Bump
+ FFI_PA_TYPE_LAST to the current FFI_TYPE_LAST once you have confirmed any
+ newly added generic type is likewise handled (or the tables extended). */
+#define FFI_PA_TYPE_LAST FFI_TYPE_SINT128
+
+/* Tripwire: when a new generic type is added FFI_TYPE_LAST changes and this
+ fires, forcing a review of ffi_prep_cif_machdep and the linux.S / hpux32.S
+ jump tables before FFI_PA_TYPE_LAST above is bumped. */
#if FFI_TYPE_LAST != FFI_PA_TYPE_LAST
# error "You likely have broken jump tables"
#endif
diff --git a/deps/libffi/src/powerpc/ffi.c b/deps/libffi/src/powerpc/ffi.c
index 1afa582ebead82..69725242344c32 100644
--- a/deps/libffi/src/powerpc/ffi.c
+++ b/deps/libffi/src/powerpc/ffi.c
@@ -1,6 +1,6 @@
/* -----------------------------------------------------------------------
ffi.c - Copyright (C) 2013 IBM
- Copyright (C) 2011 Anthony Green
+ Copyright (C) 2011, 2026 Anthony Green
Copyright (C) 2011 Kyle Moffett
Copyright (C) 2008 Red Hat, Inc
Copyright (C) 2007, 2008 Free Software Foundation, Inc
diff --git a/deps/libffi/src/powerpc/ffi_darwin.c b/deps/libffi/src/powerpc/ffi_darwin.c
index d62737f47150c2..01e2a43701d7a1 100644
--- a/deps/libffi/src/powerpc/ffi_darwin.c
+++ b/deps/libffi/src/powerpc/ffi_darwin.c
@@ -4,6 +4,7 @@
Copyright (C) 1998 Geoffrey Keating
Copyright (C) 2001 John Hornkvist
Copyright (C) 2002, 2006, 2007, 2009, 2010 Free Software Foundation, Inc.
+ Copyright (C) 2026 Anthony Green
FFI support for Darwin and AIX.
diff --git a/deps/libffi/src/powerpc/ffi_linux64.c b/deps/libffi/src/powerpc/ffi_linux64.c
index 4a4fe86beff1f2..b1f1468ed5f856 100644
--- a/deps/libffi/src/powerpc/ffi_linux64.c
+++ b/deps/libffi/src/powerpc/ffi_linux64.c
@@ -1,6 +1,6 @@
/* -----------------------------------------------------------------------
ffi_linux64.c - Copyright (C) 2013 IBM
- Copyright (C) 2011 Anthony Green
+ Copyright (C) 2011, 2026 Anthony Green
Copyright (C) 2011 Kyle Moffett
Copyright (C) 2008 Red Hat, Inc
Copyright (C) 2007, 2008 Free Software Foundation, Inc
diff --git a/deps/libffi/src/powerpc/ffi_powerpc.h b/deps/libffi/src/powerpc/ffi_powerpc.h
index c366315baefefe..bad33efe3b8cc1 100644
--- a/deps/libffi/src/powerpc/ffi_powerpc.h
+++ b/deps/libffi/src/powerpc/ffi_powerpc.h
@@ -1,6 +1,6 @@
/* -----------------------------------------------------------------------
ffi_powerpc.h - Copyright (C) 2013 IBM
- Copyright (C) 2011 Anthony Green
+ Copyright (C) 2011, 2026 Anthony Green
Copyright (C) 2011 Kyle Moffett
Copyright (C) 2008 Red Hat, Inc
Copyright (C) 2007, 2008 Free Software Foundation, Inc
diff --git a/deps/libffi/src/powerpc/ffi_sysv.c b/deps/libffi/src/powerpc/ffi_sysv.c
index 975c58c2c0e049..a33039e3dbdb1a 100644
--- a/deps/libffi/src/powerpc/ffi_sysv.c
+++ b/deps/libffi/src/powerpc/ffi_sysv.c
@@ -1,6 +1,6 @@
/* -----------------------------------------------------------------------
ffi_sysv.c - Copyright (C) 2013 IBM
- Copyright (C) 2011 Anthony Green
+ Copyright (C) 2011, 2026 Anthony Green
Copyright (C) 2011 Kyle Moffett
Copyright (C) 2008 Red Hat, Inc
Copyright (C) 2007, 2008 Free Software Foundation, Inc
diff --git a/deps/libffi/src/powerpc/ffitarget.h b/deps/libffi/src/powerpc/ffitarget.h
index cf67a9bba6ee33..7ffe36b682e4a7 100644
--- a/deps/libffi/src/powerpc/ffitarget.h
+++ b/deps/libffi/src/powerpc/ffitarget.h
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------*-C-*-
- ffitarget.h - Copyright (c) 2012 Anthony Green
+ ffitarget.h - Copyright (c) 2012, 2026 Anthony Green
Copyright (C) 2007, 2008, 2010 Free Software Foundation, Inc
Copyright (c) 1996-2003 Red Hat, Inc.
diff --git a/deps/libffi/src/prep_cif.c b/deps/libffi/src/prep_cif.c
index 47c9259470c5ee..1836270d1a9cb7 100644
--- a/deps/libffi/src/prep_cif.c
+++ b/deps/libffi/src/prep_cif.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- prep_cif.c - Copyright (c) 2011, 2012, 2021, 2025 Anthony Green
+ prep_cif.c - Copyright (c) 2011, 2012, 2021, 2025, 2026 Anthony Green
Copyright (c) 1996, 1998, 2007 Red Hat, Inc.
Copyright (c) 2022 Oracle and/or its affiliates.
@@ -278,3 +278,42 @@ ffi_get_struct_offsets (ffi_abi abi, ffi_type *struct_type, size_t *offsets)
return initialize_aggregate(struct_type, offsets);
}
+
+/* Generic ffi_call_plan: a portable fallback compiled on every target that does
+ not provide its own accelerated implementation. The x86-64 SysV backend
+ (ffi64.c) defines these with a fast path under __x86_64__ && !__ILP32__, but
+ that file is not built for Windows x86-64 (X86_WIN64), which uses ffiw64.c
+ instead -- and clang-cl and MSYS/mingw both define __x86_64__ there. So
+ exclude the fallback only when ffi64.c actually provides it; everywhere else
+ this plan just records the cif and invoke calls ffi_call, so the API is
+ always present and links on all targets. The cif must outlive the plan. */
+#if !(defined(__x86_64__) && !defined(__ILP32__) && !defined(X86_WIN64))
+
+struct ffi_call_plan
+{
+ ffi_cif *cif;
+};
+
+ffi_call_plan *
+ffi_call_plan_alloc (ffi_cif *cif)
+{
+ ffi_call_plan *plan = malloc (sizeof (struct ffi_call_plan));
+ if (plan != NULL)
+ plan->cif = cif;
+ return plan;
+}
+
+void
+ffi_call_plan_invoke (ffi_call_plan *plan, void (*fn) (void),
+ void *rvalue, void **avalue)
+{
+ ffi_call (plan->cif, fn, rvalue, avalue);
+}
+
+void
+ffi_call_plan_free (ffi_call_plan *plan)
+{
+ free (plan);
+}
+
+#endif /* generic ffi_call_plan fallback */
diff --git a/deps/libffi/src/s390/ffitarget.h b/deps/libffi/src/s390/ffitarget.h
index 6a9c3234289cb5..4fc8ef02cfe83b 100644
--- a/deps/libffi/src/s390/ffitarget.h
+++ b/deps/libffi/src/s390/ffitarget.h
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------*-C-*-
- ffitarget.h - Copyright (c) 2012 Anthony Green
+ ffitarget.h - Copyright (c) 2012, 2026 Anthony Green
Copyright (c) 1996-2003 Red Hat, Inc.
Target configuration macros for S390.
diff --git a/deps/libffi/src/sh64/ffi.c b/deps/libffi/src/sh64/ffi.c
index 4545b0fa72141b..2df6e23a3fda29 100644
--- a/deps/libffi/src/sh64/ffi.c
+++ b/deps/libffi/src/sh64/ffi.c
@@ -1,6 +1,6 @@
/* -----------------------------------------------------------------------
ffi.c - Copyright (c) 2003, 2004, 2006, 2007, 2012 Kaz Kojima
- Copyright (c) 2008 Anthony Green
+ Copyright (c) 2008, 2026 Anthony Green
SuperH SHmedia Foreign Function Interface
diff --git a/deps/libffi/src/sparc/ffi.c b/deps/libffi/src/sparc/ffi.c
index 98142b206ecd44..bcda1ff2219703 100644
--- a/deps/libffi/src/sparc/ffi.c
+++ b/deps/libffi/src/sparc/ffi.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- ffi.c - Copyright (c) 2011, 2013 Anthony Green
+ ffi.c - Copyright (c) 2011, 2013, 2026 Anthony Green
Copyright (c) 1996, 2003-2004, 2007-2008 Red Hat, Inc.
SPARC Foreign Function Interface
@@ -288,6 +288,7 @@ ffi_call_int (ffi_cif *cif, void (*fn)(void), void *rvalue,
size_t bytes = cif->bytes;
size_t i, nargs = cif->nargs;
ffi_type **arg_types = cif->arg_types;
+ void **avalue_copy = NULL;
FFI_ASSERT (cif->abi == FFI_V8);
@@ -298,7 +299,8 @@ ffi_call_int (ffi_cif *cif, void (*fn)(void), void *rvalue,
bytes += FFI_ALIGN (cif->rtype->size, 8);
/* If we have any structure arguments, make a copy so we are passing
- by value. */
+ by value. The pointer array is cloned first: the caller owns avalue[]
+ and may reuse it for another call, so it must not be modified. */
for (i = 0; i < nargs; i++)
{
ffi_type *at = arg_types[i];
@@ -306,6 +308,12 @@ ffi_call_int (ffi_cif *cif, void (*fn)(void), void *rvalue,
if (at->type == FFI_TYPE_STRUCT)
{
char *argcopy = alloca (size);
+ if (avalue_copy == NULL)
+ {
+ avalue_copy = alloca (nargs * sizeof (void *));
+ memcpy (avalue_copy, avalue, nargs * sizeof (void *));
+ avalue = avalue_copy;
+ }
memcpy (argcopy, avalue[i], size);
avalue[i] = argcopy;
}
diff --git a/deps/libffi/src/sparc/ffi64.c b/deps/libffi/src/sparc/ffi64.c
index 09a5e59dfdd0c7..5eec1499e1708d 100644
--- a/deps/libffi/src/sparc/ffi64.c
+++ b/deps/libffi/src/sparc/ffi64.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- ffi.c - Copyright (c) 2011, 2013 Anthony Green
+ ffi.c - Copyright (c) 2011, 2013, 2026 Anthony Green
Copyright (c) 1996, 2003-2004, 2007-2008 Red Hat, Inc.
SPARC Foreign Function Interface
@@ -429,6 +429,7 @@ ffi_call_int(ffi_cif *cif, void (*fn)(void), void *rvalue,
size_t bytes = cif->bytes;
size_t i, nargs = cif->nargs;
ffi_type **arg_types = cif->arg_types;
+ void **avalue_copy = NULL;
FFI_ASSERT (cif->abi == FFI_V9);
@@ -436,7 +437,8 @@ ffi_call_int(ffi_cif *cif, void (*fn)(void), void *rvalue,
bytes += FFI_ALIGN (cif->rtype->size, 16);
/* If we have any large structure arguments, make a copy so we are passing
- by value. */
+ by value. The pointer array is cloned first: the caller owns avalue[]
+ and may reuse it for another call, so it must not be modified. */
for (i = 0; i < nargs; i++)
{
ffi_type *at = arg_types[i];
@@ -444,10 +446,16 @@ ffi_call_int(ffi_cif *cif, void (*fn)(void), void *rvalue,
if (at->type == FFI_TYPE_STRUCT && size > 4)
{
char *argcopy = alloca (size);
+ if (avalue_copy == NULL)
+ {
+ avalue_copy = alloca (nargs * sizeof (void *));
+ memcpy (avalue_copy, avalue, nargs * sizeof (void *));
+ avalue = avalue_copy;
+ }
memcpy (argcopy, avalue[i], size);
avalue[i] = argcopy;
}
- }
+ }
ffi_call_v9(cif, fn, rvalue, avalue, -bytes, closure);
}
diff --git a/deps/libffi/src/tramp.c b/deps/libffi/src/tramp.c
index 76a9dfe75c2c3b..525f8154715671 100644
--- a/deps/libffi/src/tramp.c
+++ b/deps/libffi/src/tramp.c
@@ -1,6 +1,6 @@
/* -----------------------------------------------------------------------
tramp.c - Copyright (c) 2020 Madhavan T. Venkataraman
- Copyright (c) 2022 Anthony Green
+ Copyright (c) 2022, 2026 Anthony Green
API and support functions for managing statically defined closure
trampolines.
@@ -201,11 +201,18 @@ static int tramp_table_alloc (void);
#if defined (__linux__) || defined (__CYGWIN__)
+/* Stringify a macro value for use in a scanf field-width specifier. */
+#define FFI_TRAMP_STR_(x) #x
+#define FFI_TRAMP_STR(x) FFI_TRAMP_STR_(x)
+
static int
ffi_tramp_get_libffi (void)
{
FILE *fp;
- char file[PATH_MAX], line[PATH_MAX+100], perm[10], dev[10];
+ /* `file' is sized PATH_MAX+1 so a scanf field width of PATH_MAX
+ (which permits PATH_MAX characters plus the terminating NUL) cannot
+ overflow. */
+ char file[PATH_MAX+1], line[PATH_MAX+100], perm[10], dev[10];
unsigned long start, end, offset, inode;
uintptr_t addr = (uintptr_t) tramp_globals.text;
int nfields, found;
@@ -215,7 +222,7 @@ ffi_tramp_get_libffi (void)
open_flags |= O_CLOEXEC;
#endif
- snprintf (file, PATH_MAX, "/proc/%d/maps", getpid());
+ snprintf (file, sizeof (file), "/proc/%d/maps", getpid());
fp = fopen (file, "r");
if (fp == NULL)
return 0;
@@ -225,7 +232,10 @@ ffi_tramp_get_libffi (void)
if (fgets (line, sizeof (line), fp) == 0)
break;
- nfields = sscanf (line, "%lx-%lx %9s %lx %9s %ld %s",
+ /* Bound the path field width so a long mapping name cannot overflow
+ the fixed-size `file' buffer. */
+ nfields = sscanf (line,
+ "%lx-%lx %9s %lx %9s %ld %" FFI_TRAMP_STR(PATH_MAX) "s",
&start, &end, perm, &offset, dev, &inode, file);
if (nfields != 7)
continue;
diff --git a/deps/libffi/src/types.c b/deps/libffi/src/types.c
index c6e8ebe2a881be..4ac45a4ac0d790 100644
--- a/deps/libffi/src/types.c
+++ b/deps/libffi/src/types.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- types.c - Copyright (c) 1996, 1998, 2024, 2025 Red Hat, Inc.
+ types.c - Copyright (c) 1996, 1998, 2024, 2025, 2026 Red Hat, Inc.
Predefined ffi_types needed by libffi.
diff --git a/deps/libffi/src/wasm/ffi.c b/deps/libffi/src/wasm/ffi.c
index 486ffa7856aa82..b631b1b2bef9cf 100644
--- a/deps/libffi/src/wasm/ffi.c
+++ b/deps/libffi/src/wasm/ffi.c
@@ -1,5 +1,6 @@
/* -----------------------------------------------------------------------
ffi.c - Copyright (c) 2018-2023 Hood Chatham, Brion Vibber, Kleis Auke Wolthuizen, and others.
+ Copyright (c) 2026 Anthony Green
wasm32/emscripten Foreign Function Interface
@@ -70,6 +71,13 @@ EM_JS_DEPS(libffi, "$getWasmTableEntry,$setWasmTableEntry,$getEmptyTableSlot,$co
#define DEREF_PTR(addr, offset) DEREF_U32(addr, offset)
#define DEREF_PTR_NUMBER(addr, offset) DEREF_PTR(addr, offset)
+// Store an integral return value widened to ffi_arg (32 bits on wasm32), as
+// the libffi API promises for integral returns narrower than a register.
+// The typed-array store wraps modulo 2^32, so a sign-extended negative
+// Number is stored correctly by both variants.
+#define STORE_ARG_WIDENED_UNSIGNED(rvalue, x) (DEREF_U32(rvalue, 0) = (x))
+#define STORE_ARG_WIDENED_SIGNED(rvalue, x) (DEREF_U32(rvalue, 0) = (x))
+
CHECK_FIELD_OFFSET(ffi_cif, abi, 4*0);
CHECK_FIELD_OFFSET(ffi_cif, nargs, 4*1);
CHECK_FIELD_OFFSET(ffi_cif, arg_types, 4*2);
@@ -109,6 +117,16 @@ CHECK_FIELD_OFFSET(ffi_type, elements, 8);
#define DEREF_PTR(addr, offset) DEREF_U64(addr, offset)
#define DEREF_PTR_NUMBER(addr, offset) DEC_PTR(DEREF_PTR(addr, offset))
+// Store an integral return value widened to ffi_arg (64 bits on wasm64), as
+// the libffi API promises for integral returns narrower than a register.
+// BigInt masking treats the value as infinite-precision two's complement, so
+// the unsigned variant zero-extends a possibly-negative i32 Number, while
+// the BigUint64Array store wraps the signed variant modulo 2^64, which is
+// sign extension.
+#define STORE_ARG_WIDENED_UNSIGNED(rvalue, x) \
+ (DEREF_U64(rvalue, 0) = BigInt(x) & BigInt(4294967295))
+#define STORE_ARG_WIDENED_SIGNED(rvalue, x) (DEREF_U64(rvalue, 0) = BigInt(x))
+
CHECK_FIELD_OFFSET(ffi_cif, abi, 0);
CHECK_FIELD_OFFSET(ffi_cif, nargs, 4);
CHECK_FIELD_OFFSET(ffi_cif, arg_types, 8);
@@ -247,6 +265,10 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue),
var rtype_unboxed = unbox_small_structs(CIF__RTYPE(cif));
var rtype_ptr = rtype_unboxed[0];
var rtype_id = rtype_unboxed[1];
+ // A small struct return is unboxed to a scalar type id above, but it is
+ // still a struct to the caller: its buffer is exactly the struct's size,
+ // so it must not get the integral widening to ffi_arg.
+ var rtype_widen = FFI_TYPE__TYPEID(DEC_PTR(CIF__RTYPE(cif))) !== FFI_TYPE_STRUCT;
var orig_stack_ptr = stackSave();
var cur_stack_ptr = orig_stack_ptr;
@@ -267,6 +289,15 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue),
// just use this. We also mark a flag that we don't need to convert the return
// value of the dynamic call back to C.
if (rtype_id === FFI_TYPE_LONGDOUBLE || rtype_id === FFI_TYPE_STRUCT) {
+ if (rvalue === 0) {
+ // NULL rvalue: the caller discards the result, but the callee still
+ // needs somewhere to write it. Allocate scratch space on the stack;
+ // it is released by the stackRestore after the call.
+ var rsize = DEC_PTR(FFI_TYPE__SIZE(rtype_ptr));
+ var ralign = FFI_TYPE__ALIGN(rtype_ptr);
+ STACK_ALLOC(cur_stack_ptr, rsize, ralign);
+ rvalue = cur_stack_ptr;
+ }
args.push(ENC_PTR(rvalue));
ret_by_arg = true;
}
@@ -430,15 +461,31 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue),
return;
}
+ // A NULL rvalue means the caller discards the return value.
+ if (rvalue === 0) {
+ return;
+ }
+
// Otherwise the result was automatically converted from C into Javascript and
// we need to manually convert it back to C.
+ // Integral returns narrower than a register are widened to ffi_arg, as
+ // documented and as every native port does -- but only genuine scalar
+ // returns: an unboxed small-struct return keeps its natural width.
switch (rtype_id) {
case FFI_TYPE_VOID:
break;
case FFI_TYPE_INT:
- case FFI_TYPE_UINT32:
case FFI_TYPE_SINT32:
- DEREF_U32(rvalue, 0) = result;
+ if (rtype_widen)
+ STORE_ARG_WIDENED_SIGNED(rvalue, result | 0);
+ else
+ DEREF_U32(rvalue, 0) = result;
+ break;
+ case FFI_TYPE_UINT32:
+ if (rtype_widen)
+ STORE_ARG_WIDENED_UNSIGNED(rvalue, result | 0);
+ else
+ DEREF_U32(rvalue, 0) = result;
break;
case FFI_TYPE_FLOAT:
DEREF_F32(rvalue, 0) = result;
@@ -447,12 +494,28 @@ ffi_call_js, (ffi_cif *cif, ffi_fp fn, void *rvalue, void **avalue),
DEREF_F64(rvalue, 0) = result;
break;
case FFI_TYPE_UINT8:
+ if (rtype_widen)
+ STORE_ARG_WIDENED_UNSIGNED(rvalue, result & 0xff);
+ else
+ DEREF_U8(rvalue, 0) = result;
+ break;
case FFI_TYPE_SINT8:
- DEREF_U8(rvalue, 0) = result;
+ if (rtype_widen)
+ STORE_ARG_WIDENED_SIGNED(rvalue, (result << 24) >> 24);
+ else
+ DEREF_U8(rvalue, 0) = result;
break;
case FFI_TYPE_UINT16:
+ if (rtype_widen)
+ STORE_ARG_WIDENED_UNSIGNED(rvalue, result & 0xffff);
+ else
+ DEREF_U16(rvalue, 0) = result;
+ break;
case FFI_TYPE_SINT16:
- DEREF_U16(rvalue, 0) = result;
+ if (rtype_widen)
+ STORE_ARG_WIDENED_SIGNED(rvalue, (result << 16) >> 16);
+ else
+ DEREF_U16(rvalue, 0) = result;
break;
case FFI_TYPE_UINT64:
case FFI_TYPE_SINT64:
diff --git a/deps/libffi/src/x86/ffi.c b/deps/libffi/src/x86/ffi.c
index 40d153fb399e21..27f17b0c884957 100644
--- a/deps/libffi/src/x86/ffi.c
+++ b/deps/libffi/src/x86/ffi.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- ffi.c - Copyright (c) 2017, 2022 Anthony Green
+ ffi.c - Copyright (c) 2017, 2022, 2026 Anthony Green
Copyright (c) 1996, 1998, 1999, 2001, 2007, 2008 Red Hat, Inc.
Copyright (c) 2002 Ranjit Mathew
Copyright (c) 2002 Bo Thorsen
@@ -554,8 +554,14 @@ ffi_closure_inner (struct closure_frame *frame, char *stack)
return flags | (cif->bytes << X86_RET_POP_SHIFT);
case FFI_THISCALL:
case FFI_FASTCALL:
- return flags | ((cif->bytes - (narg_reg * FFI_SIZEOF_ARG))
- << X86_RET_POP_SHIFT);
+ /* The callee must pop exactly the bytes that were passed on the
+ stack. Deriving that from cif->bytes minus narg_reg * 4 is wrong
+ once narg_reg has been force-bumped to 2 (above) for a 64-bit or
+ struct argument that is itself placed on the stack: the subtraction
+ then discounts register slots that were never used, under-popping
+ the stack. argp has advanced past exactly the stack-resident
+ arguments (dir == 1 for these ABIs), so use that directly. */
+ return flags | (((unsigned) (argp - stack)) << X86_RET_POP_SHIFT);
default:
return flags;
}
diff --git a/deps/libffi/src/x86/ffi64.c b/deps/libffi/src/x86/ffi64.c
index db0aa563a87947..c24db38c4364b7 100644
--- a/deps/libffi/src/x86/ffi64.c
+++ b/deps/libffi/src/x86/ffi64.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- ffi64.c - Copyright (c) 2011, 2018, 2022 Anthony Green
+ ffi64.c - Copyright (c) 2011, 2018, 2022, 2026 Anthony Green
Copyright (c) 2013 The Written Word, Inc.
Copyright (c) 2008, 2010 Red Hat, Inc.
Copyright (c) 2002, 2007 Bo Thorsen
@@ -33,6 +33,8 @@
#include
#include
#include
+#include
+#include
#include
#include "internal64.h"
@@ -734,6 +736,340 @@ ffi_call_int (ffi_cif *cif, void (*fn)(void), void *rvalue,
}
#ifndef __ILP32__
+/* =====================================================================
+ Precompiled argument-placement plan, used by the ffi_call_plan API.
+
+ ffi_prep_cif_machdep classifies the signature once, but ffi_call_int then
+ re-derives the same per-argument placement on every call (~650 instructions
+ for a 3-argument call). A "plan" captures that placement as a flat move
+ list, built once, so the register_args + stack buffer can be filled with no
+ re-classification before handing off to the unchanged ffi_call_unix64. For
+ the common case (only 64-bit GP arguments) a direct thunk loads the values
+ straight into the argument registers, skipping the buffer entirely.
+
+ A plan is built by ffi_call_plan_alloc and applied by ffi_call_plan_invoke;
+ the caller owns it and reuses it across calls.
+
+ Scalar, pointer, int128, float and double arguments are handled; any struct,
+ complex, or x87 long double argument has no plan, so the caller's invoke
+ falls back to ffi_call. */
+
+enum ffi_move_op
+{
+ FFI_MOVE_SE8, FFI_MOVE_SE16, FFI_MOVE_SE32, /* sign-extend N bytes -> gpr */
+ FFI_MOVE_GP64, /* copy a full 8-byte word -> gpr */
+ FFI_MOVE_GP, /* zero gpr, copy len(<8) bytes */
+ FFI_MOVE_SSE64, FFI_MOVE_SSE32, /* copy 8/4 bytes -> sse slot */
+ FFI_MOVE_STACK /* copy len bytes -> stack */
+};
+
+typedef struct
+{
+ unsigned src_idx; /* avalue[] index */
+ unsigned src_off; /* byte offset within avalue[src_idx] (chunk * 8) */
+ unsigned dst_off; /* byte offset within the register_args+stack buf */
+ unsigned len; /* bytes for FFI_MOVE_GP / FFI_MOVE_STACK */
+ unsigned char op;
+} ffi_move;
+
+typedef struct
+{
+ unsigned nmoves;
+ unsigned ssecount; /* -> reg_args->rax */
+ unsigned bytes; /* stack-arg area size (== cif->bytes) */
+ unsigned flags; /* == cif->flags */
+ unsigned ret_in_mem; /* nonzero -> reg_args->gpr[0] = rvalue */
+ unsigned fast; /* nonzero -> lean trampoline eligible */
+ unsigned retcode; /* UNIX64_RET_* (low byte of flags) for the store */
+ int thunk_n; /* >=0 -> ffi_gp_thunks[thunk_n], else -1 */
+ ffi_move moves[];
+} ffi_plan;
+
+/* Return of the lean trampoline / direct thunks: callee's rax in .i, xmm0 in .d. */
+struct ffi_ret2 { UINT64 i; double d; };
+extern struct ffi_ret2 ffi_plan_fast_call (struct register_args *img,
+ void (*fn) (void)) FFI_HIDDEN;
+
+/* Count-based direct thunks: load avalue[0..N-1] into arg registers, call. */
+extern struct ffi_ret2 ffi_plan_gp0 (void **, void (*)(void)) FFI_HIDDEN;
+extern struct ffi_ret2 ffi_plan_gp1 (void **, void (*)(void)) FFI_HIDDEN;
+extern struct ffi_ret2 ffi_plan_gp2 (void **, void (*)(void)) FFI_HIDDEN;
+extern struct ffi_ret2 ffi_plan_gp3 (void **, void (*)(void)) FFI_HIDDEN;
+extern struct ffi_ret2 ffi_plan_gp4 (void **, void (*)(void)) FFI_HIDDEN;
+extern struct ffi_ret2 ffi_plan_gp5 (void **, void (*)(void)) FFI_HIDDEN;
+extern struct ffi_ret2 ffi_plan_gp6 (void **, void (*)(void)) FFI_HIDDEN;
+static struct ffi_ret2 (*const ffi_gp_thunks[7]) (void **, void (*)(void)) =
+ { ffi_plan_gp0, ffi_plan_gp1, ffi_plan_gp2, ffi_plan_gp3,
+ ffi_plan_gp4, ffi_plan_gp5, ffi_plan_gp6 };
+
+/* Store the callee return value, replicating the unix64.S store_table widths. */
+static inline void
+store_ret (void *rvalue, unsigned retcode, struct ffi_ret2 r)
+{
+ switch (retcode)
+ {
+ case UNIX64_RET_VOID: break;
+ case UNIX64_RET_UINT8: *(UINT64 *) rvalue = (UINT8) r.i; break;
+ case UNIX64_RET_UINT16: *(UINT64 *) rvalue = (UINT16) r.i; break;
+ case UNIX64_RET_UINT32: *(UINT64 *) rvalue = (UINT32) r.i; break;
+ case UNIX64_RET_SINT8: *(UINT64 *) rvalue = (UINT64)(SINT64)(SINT8) r.i; break;
+ case UNIX64_RET_SINT16: *(UINT64 *) rvalue = (UINT64)(SINT64)(SINT16) r.i; break;
+ case UNIX64_RET_SINT32: *(UINT64 *) rvalue = (UINT64)(SINT64)(SINT32) r.i; break;
+ case UNIX64_RET_INT64: *(UINT64 *) rvalue = r.i; break;
+ case UNIX64_RET_XMM32: memcpy (rvalue, &r.d, 4); break;
+ case UNIX64_RET_XMM64: memcpy (rvalue, &r.d, 8); break;
+ }
+}
+
+/* Build the move-list for CIF, or NULL if not plan-able (caller falls back). */
+static ffi_plan *
+build_plan (ffi_cif *cif)
+{
+ unsigned i, avn = cif->nargs;
+ enum x86_64_reg_class classes[MAX_CLASSES];
+ unsigned nm, gprcount, ssecount;
+ size_t argp_off;
+ ffi_plan *plan;
+ int all_gp64 = 1; /* every arg is exactly one 64-bit GP move? */
+
+ if (cif->abi != FFI_UNIX64)
+ return NULL;
+
+ /* Reject arg types this cut doesn't encode; returns are handled by flags. */
+ for (i = 0; i < avn; i++)
+ {
+ int t = cif->arg_types[i]->type;
+ if (t == FFI_TYPE_STRUCT || t == FFI_TYPE_COMPLEX)
+ return NULL;
+#if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE
+ if (t == FFI_TYPE_LONGDOUBLE)
+ return NULL;
+#endif
+ }
+
+ /* One self-contained allocation: header + moves, released with plain free(). */
+ plan = malloc (sizeof (ffi_plan) + sizeof (ffi_move) * (2 * avn + 1));
+ if (plan == NULL)
+ return NULL;
+
+ nm = gprcount = ssecount = 0;
+ argp_off = 0;
+ plan->ret_in_mem = (cif->flags & UNIX64_FLAG_RET_IN_MEM) ? 1 : 0;
+ if (plan->ret_in_mem)
+ gprcount++; /* sret pointer occupies gpr[0] */
+
+ for (i = 0; i < avn; i++)
+ {
+ ffi_type *at = cif->arg_types[i];
+ size_t size = at->size, n, rem;
+ int ngpr, nsse;
+ unsigned j;
+
+ n = examine_argument (at, classes, 0, &ngpr, &nsse);
+ if (n == 0
+ || gprcount + ngpr > MAX_GPR_REGS
+ || ssecount + nsse > MAX_SSE_REGS)
+ {
+ long align = at->alignment;
+ ffi_move *m = &plan->moves[nm++];
+ all_gp64 = 0;
+ if (align < 8)
+ align = 8;
+ argp_off = FFI_ALIGN (argp_off, align);
+ m->op = FFI_MOVE_STACK;
+ m->src_idx = i;
+ m->src_off = 0;
+ m->dst_off = (unsigned) (sizeof (struct register_args) + argp_off);
+ m->len = (unsigned) size;
+ argp_off += size;
+ continue;
+ }
+
+ for (j = 0, rem = size; j < n; j++, rem -= 8)
+ {
+ ffi_move m;
+ m.src_idx = i;
+ m.src_off = j * 8;
+ switch (classes[j])
+ {
+ case X86_64_NO_CLASS:
+ case X86_64_SSEUP_CLASS:
+ continue; /* nothing placed for this 8-byte */
+ case X86_64_INTEGER_CLASS:
+ case X86_64_INTEGERSI_CLASS:
+ m.dst_off = gprcount * 8; /* offsetof(register_args,gpr) == 0 */
+ switch (at->type)
+ {
+ case FFI_TYPE_SINT8: m.op = FFI_MOVE_SE8; all_gp64 = 0; break;
+ case FFI_TYPE_SINT16: m.op = FFI_MOVE_SE16; all_gp64 = 0; break;
+ case FFI_TYPE_SINT32: m.op = FFI_MOVE_SE32; all_gp64 = 0; break;
+ default:
+ if (rem >= 8)
+ m.op = FFI_MOVE_GP64;
+ else
+ { m.op = FFI_MOVE_GP; m.len = (unsigned) rem; all_gp64 = 0; }
+ break;
+ }
+ gprcount++;
+ break;
+ case X86_64_SSE_CLASS:
+ case X86_64_SSEDF_CLASS:
+ m.dst_off = (unsigned) (offsetof (struct register_args, sse)
+ + ssecount * sizeof (union big_int_union));
+ m.op = FFI_MOVE_SSE64;
+ ssecount++;
+ all_gp64 = 0;
+ break;
+ case X86_64_SSESF_CLASS:
+ m.dst_off = (unsigned) (offsetof (struct register_args, sse)
+ + ssecount * sizeof (union big_int_union));
+ m.op = FFI_MOVE_SSE32;
+ ssecount++;
+ all_gp64 = 0;
+ break;
+ default:
+ free (plan); /* X87 etc. in registers: bail */
+ return NULL;
+ }
+ plan->moves[nm++] = m;
+ }
+ }
+
+ plan->nmoves = nm;
+ plan->ssecount = ssecount;
+ plan->bytes = cif->bytes;
+ plan->flags = cif->flags;
+ plan->retcode = cif->flags & 0xff; /* UNIX64_RET_* */
+ /* Lean-trampoline eligible: no spilled stack args and a simple return
+ (VOID..XMM64, codes 0..9; RET_IN_MEM has low byte VOID). Struct-in-regs
+ (>=12) and x87 (10,11) returns stay on ffi_call_unix64. */
+ plan->fast = (cif->bytes == 0 && plan->retcode <= UNIX64_RET_XMM64) ? 1 : 0;
+ /* Pure-GP64 direct thunk: every arg is one 64-bit GP value (so a plain load
+ per arg is exact), <=6 of them, no sret, simple return -> load avalue
+ straight into the arg registers, no register image. */
+ plan->thunk_n =
+ (all_gp64 && !plan->ret_in_mem && nm == avn && avn <= MAX_GPR_REGS
+ && plan->fast)
+ ? (int) avn : -1;
+ return plan;
+}
+
+/* Execute PLAN: rebuild register_args + stack buffer, then ffi_call_unix64. */
+FFI_ASAN_NO_SANITIZE
+static inline __attribute__ ((always_inline)) void
+plan_exec (ffi_cif *cif, ffi_plan *plan, void (*fn) (void),
+ void *rvalue, void **avalue)
+{
+ unsigned flags = plan->flags;
+ struct register_args local __attribute__ ((aligned (16)));
+ char *stack = NULL;
+ struct register_args *reg_args;
+ unsigned k;
+
+ if (rvalue == NULL)
+ {
+ if (flags & UNIX64_FLAG_RET_IN_MEM)
+ rvalue = alloca (cif->rtype->size);
+ else
+ flags = UNIX64_RET_VOID;
+ }
+
+ if (plan->thunk_n >= 0)
+ {
+ /* Pure-GP64: load avalue straight into arg regs, no image at all. */
+ struct ffi_ret2 r = ffi_gp_thunks[plan->thunk_n] (avalue, fn);
+ if (rvalue != NULL)
+ store_ret (rvalue, plan->retcode, r);
+ return;
+ }
+
+ if (plan->fast)
+ reg_args = &local; /* no stack args: fixed local image */
+ else
+ {
+ stack = alloca (sizeof (struct register_args) + plan->bytes + 4 * 8);
+ reg_args = (struct register_args *) stack;
+ }
+ reg_args->r10 = 0; /* closure (none for ffi_call) */
+ if (plan->ret_in_mem)
+ reg_args->gpr[0] = (UINT64) (uintptr_t) rvalue;
+
+ for (k = 0; k < plan->nmoves; k++)
+ {
+ ffi_move *m = &plan->moves[k];
+ char *src = (char *) avalue[m->src_idx] + m->src_off;
+ char *dst = (char *) reg_args + m->dst_off;
+ switch (m->op)
+ {
+ /* x86-64: unaligned scalar loads from avalue[] are fine. */
+ case FFI_MOVE_SE8: *(UINT64 *) dst = (UINT64) (SINT64) *(SINT8 *) src; break;
+ case FFI_MOVE_SE16: *(UINT64 *) dst = (UINT64) (SINT64) *(SINT16 *) src; break;
+ case FFI_MOVE_SE32: *(UINT64 *) dst = (UINT64) (SINT64) *(SINT32 *) src; break;
+ case FFI_MOVE_GP64: *(UINT64 *) dst = *(UINT64 *) src; break;
+ case FFI_MOVE_GP: *(UINT64 *) dst = 0; memcpy (dst, src, m->len); break;
+ case FFI_MOVE_SSE64: *(UINT64 *) dst = *(UINT64 *) src; break;
+ case FFI_MOVE_SSE32: *(UINT32 *) dst = *(UINT32 *) src; break;
+ case FFI_MOVE_STACK: memcpy (dst, src, m->len); break;
+ }
+ }
+ reg_args->rax = plan->ssecount;
+
+ if (plan->fast)
+ {
+ /* No stack args; lean trampoline + return store replicating the
+ unix64.S store_table widths. ret_in_mem already wrote gpr[0]. */
+ struct ffi_ret2 r = ffi_plan_fast_call (reg_args, fn);
+ if (rvalue != NULL)
+ store_ret (rvalue, plan->retcode, r);
+ return;
+ }
+
+ ffi_call_unix64 (stack, plan->bytes + sizeof (struct register_args),
+ flags, rvalue, fn);
+}
+
+/* Reusable call plan: an opaque, caller-owned handle wrapping a prebuilt plan.
+ ffi_call_plan_invoke applies it directly, skipping the per-call argument
+ classification ffi_call does every time. Signatures with no fast path
+ (FAST is NULL) fall back to ffi_call. The plan is immutable after alloc, so
+ it carries no per-thread state and can be invoked from any thread. */
+struct ffi_call_plan
+{
+ ffi_cif *cif;
+ ffi_plan *fast; /* prebuilt plan, or NULL -> fall back to ffi_call */
+};
+
+ffi_call_plan *
+ffi_call_plan_alloc (ffi_cif *cif)
+{
+ ffi_call_plan *plan = malloc (sizeof (struct ffi_call_plan));
+ if (plan == NULL)
+ return NULL;
+ plan->cif = cif;
+ plan->fast = build_plan (cif); /* NULL if this signature has no fast path */
+ return plan;
+}
+
+void
+ffi_call_plan_invoke (ffi_call_plan *plan, void (*fn) (void),
+ void *rvalue, void **avalue)
+{
+ if (plan->fast != NULL)
+ plan_exec (plan->cif, plan->fast, fn, rvalue, avalue);
+ else
+ ffi_call (plan->cif, fn, rvalue, avalue);
+}
+
+void
+ffi_call_plan_free (ffi_call_plan *plan)
+{
+ if (plan != NULL)
+ {
+ free (plan->fast);
+ free (plan);
+ }
+}
+
extern void
ffi_call_efi64(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue);
#endif
@@ -742,11 +1078,13 @@ void
ffi_call (ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue)
{
ffi_type **arg_types = cif->arg_types;
+ void **avalue_copy = NULL;
int i, nargs = cif->nargs;
const int max_reg_struct_size = cif->abi == FFI_GNUW64 ? 8 : 16;
/* If we have any large structure arguments, make a copy so we are passing
- by value. */
+ by value. The pointer array is cloned first: the caller owns avalue[]
+ and may reuse it for another call, so it must not be modified. */
for (i = 0; i < nargs; i++)
{
ffi_type *at = arg_types[i];
@@ -754,6 +1092,12 @@ ffi_call (ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue)
if (at->type == FFI_TYPE_STRUCT && size > max_reg_struct_size)
{
char *argcopy = alloca (size);
+ if (avalue_copy == NULL)
+ {
+ avalue_copy = alloca (nargs * sizeof (void *));
+ memcpy (avalue_copy, avalue, nargs * sizeof (void *));
+ avalue = avalue_copy;
+ }
memcpy (argcopy, avalue[i], size);
avalue[i] = argcopy;
}
@@ -884,6 +1228,7 @@ ffi_closure_unix64_inner(ffi_cif *cif,
avn = cif->nargs;
flags = cif->flags;
+
avalue = alloca(avn * sizeof(void *));
gprcount = ssecount = 0;
@@ -940,7 +1285,7 @@ ffi_closure_unix64_inner(ffi_cif *cif,
/* Otherwise, allocate space to make them consecutive. */
else
{
- char *a = alloca (16);
+ char *a = alloca (n * 8);
unsigned int j;
avalue[i] = a;
diff --git a/deps/libffi/src/x86/ffitarget.h b/deps/libffi/src/x86/ffitarget.h
index 995469b0f59b2c..d702235f90fed5 100644
--- a/deps/libffi/src/x86/ffitarget.h
+++ b/deps/libffi/src/x86/ffitarget.h
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------*-C-*-
- ffitarget.h - Copyright (c) 2012, 2014, 2018 Anthony Green
+ ffitarget.h - Copyright (c) 2012, 2014, 2018, 2026 Anthony Green
Copyright (c) 1996-2003, 2010 Red Hat, Inc.
Copyright (C) 2008 Free Software Foundation, Inc.
diff --git a/deps/libffi/src/x86/ffiw64.c b/deps/libffi/src/x86/ffiw64.c
index 37d9a7e9c036d9..f8db4e0d96236b 100644
--- a/deps/libffi/src/x86/ffiw64.c
+++ b/deps/libffi/src/x86/ffiw64.c
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- ffiw64.c - Copyright (c) 2018 Anthony Green
+ ffiw64.c - Copyright (c) 2018, 2026 Anthony Green
Copyright (c) 2014 Red Hat, Inc.
x86 win64 Foreign Function Interface
@@ -126,12 +126,15 @@ ffi_call_int (ffi_cif *cif, void (*fn)(void), void *rvalue,
size_t rsize;
struct win64_call_frame *frame;
ffi_type **arg_types = cif->arg_types;
+ void **avalue_copy = NULL;
int nargs = cif->nargs;
FFI_ASSERT(cif->abi == FFI_GNUW64 || cif->abi == FFI_WIN64);
/* If we have any int128 or irregularly sized structure arguments,
- make a copy so we are passing by value. */
+ make a copy so we are passing by value. The pointer array is cloned
+ first: the caller owns avalue[] and may reuse it for another call,
+ so it must not be modified. */
for (i = 0; i < nargs; i++)
{
ffi_type *at = arg_types[i];
@@ -159,6 +162,12 @@ ffi_call_int (ffi_cif *cif, void (*fn)(void), void *rvalue,
if (needcopy)
{
char *argcopy = alloca (size);
+ if (avalue_copy == NULL)
+ {
+ avalue_copy = alloca (nargs * sizeof (void *));
+ memcpy (avalue_copy, avalue, nargs * sizeof (void *));
+ avalue = avalue_copy;
+ }
memcpy (argcopy, avalue[i], size);
avalue[i] = argcopy;
}
diff --git a/deps/libffi/src/x86/sysv.S b/deps/libffi/src/x86/sysv.S
index b5969df1560143..dac841421d9b2b 100644
--- a/deps/libffi/src/x86/sysv.S
+++ b/deps/libffi/src/x86/sysv.S
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- sysv.S - Copyright (c) 2017 Anthony Green
+ sysv.S - Copyright (c) 2017, 2026 Anthony Green
- Copyright (c) 2013 The Written Word, Inc.
- Copyright (c) 1996,1998,2001-2003,2005,2008,2010 Red Hat, Inc.
@@ -1269,6 +1269,6 @@ L(EFDE9):
#endif /* ifdef __i386__ */
-#if defined __ELF__ && defined __linux__
+#if defined __ELF__ && (defined __linux__ || defined __FreeBSD__)
.section .note.GNU-stack,"",@progbits
#endif
diff --git a/deps/libffi/src/x86/sysv_intel.S b/deps/libffi/src/x86/sysv_intel.S
index efdde14ac20290..c7de159916ac9c 100644
--- a/deps/libffi/src/x86/sysv_intel.S
+++ b/deps/libffi/src/x86/sysv_intel.S
@@ -1,5 +1,5 @@
/* -----------------------------------------------------------------------
- sysv.S - Copyright (c) 2017, 2022 Anthony Green
+ sysv.S - Copyright (c) 2017, 2022, 2026 Anthony Green
- Copyright (c) 2013 The Written Word, Inc.
- Copyright (c) 1996,1998,2001-2003,2005,2008,2010 Red Hat, Inc.
@@ -992,7 +992,7 @@ L(EFDE9):
#endif /* ifndef _MSC_VER */
#endif /* ifndef __x86_64__ */
-#if defined __ELF__ && defined __linux__
+#if defined __ELF__ && (defined __linux__ || defined __FreeBSD__)
.section .note.GNU-stack,"",@progbits
#endif
#endif
diff --git a/deps/libffi/src/x86/unix64.S b/deps/libffi/src/x86/unix64.S
index da30ab7577a7c3..61983ef3bcf5ec 100644
--- a/deps/libffi/src/x86/unix64.S
+++ b/deps/libffi/src/x86/unix64.S
@@ -2,6 +2,7 @@
unix64.S - Copyright (c) 2013 The Written Word, Inc.
- Copyright (c) 2008 Red Hat, Inc
- Copyright (c) 2002 Bo Thorsen
+ - Copyright (c) 2026 Anthony Green
x86-64 Foreign Function Interface
@@ -242,6 +243,154 @@ L(load_sse):
L(UW4):
ENDF(C(ffi_call_unix64))
+/* Lean trampoline for the plan fast path: no stack args, simple return.
+ struct { UINT64 rax; double xmm0; }
+ ffi_plan_fast_call (struct register_args *img /rdi/, void (*fn)(void) /rsi/);
+
+ Loads the argument registers from IMG, calls FN, and lets the callee's
+ rax/xmm0 flow straight out as this function's {UINT64,double} return -- so
+ the C caller recovers both with no return-dispatch table. Skips the frame
+ relocation that ffi_call_unix64 needs only for stack args and struct/x87
+ returns. Caller guarantees img has no spilled stack arguments. */
+ .balign 8
+ .globl C(ffi_plan_fast_call)
+ FFI_HIDDEN(C(ffi_plan_fast_call))
+C(ffi_plan_fast_call):
+ .cfi_startproc
+ _CET_ENDBR
+ movq %rsi, %r11 /* fn */
+ movq %rdi, %rax /* img */
+ movl 0xb0(%rax), %r10d /* ssecount */
+ testl %r10d, %r10d
+ jz 1f
+ movdqa 0x30(%rax), %xmm0
+ movdqa 0x40(%rax), %xmm1
+ movdqa 0x50(%rax), %xmm2
+ movdqa 0x60(%rax), %xmm3
+ movdqa 0x70(%rax), %xmm4
+ movdqa 0x80(%rax), %xmm5
+ movdqa 0x90(%rax), %xmm6
+ movdqa 0xa0(%rax), %xmm7
+1:
+ movq 0x00(%rax), %rdi
+ movq 0x08(%rax), %rsi
+ movq 0x10(%rax), %rdx
+ movq 0x18(%rax), %rcx
+ movq 0x20(%rax), %r8
+ movq 0x28(%rax), %r9
+ movl %r10d, %eax /* %al = ssecount */
+ subq $8, %rsp /* realign to 16 across the call */
+ .cfi_adjust_cfa_offset 8
+ call *%r11
+ addq $8, %rsp
+ .cfi_adjust_cfa_offset -8
+ ret /* rax + xmm0 carry the callee's return */
+ .cfi_endproc
+ ENDF(C(ffi_plan_fast_call))
+
+/* Count-based direct thunks for the pure-GP64 fast path: load avalue[0..N-1]
+ straight into the argument registers (no register_args image) and call.
+ struct { UINT64 rax; double xmm0; }
+ ffi_plan_gpN (void **avalue /rdi/, void (*fn)(void) /rsi/);
+ Eligible only when every arg is a single 64-bit GP value (no sign-extension,
+ no SSE, no stack spill, no sret) -- so a plain 8-byte load per arg is exact.
+ rax/xmm0 flow out as the {UINT64,double} return; C stores per return code. */
+
+#define FFI_GP_HEAD \
+ .cfi_startproc; \
+ _CET_ENDBR; \
+ movq %rsi, %r11; \
+ movq %rdi, %rax
+#define FFI_GP_LD(OFS, REG) \
+ movq OFS(%rax), %r10; \
+ movq (%r10), REG
+#define FFI_GP_TAIL \
+ xorl %eax, %eax; \
+ subq $8, %rsp; \
+ .cfi_adjust_cfa_offset 8; \
+ call *%r11; \
+ addq $8, %rsp; \
+ .cfi_adjust_cfa_offset -8; \
+ ret; \
+ .cfi_endproc
+
+ .balign 8
+ .globl C(ffi_plan_gp0)
+ FFI_HIDDEN(C(ffi_plan_gp0))
+C(ffi_plan_gp0):
+ FFI_GP_HEAD
+ FFI_GP_TAIL
+ ENDF(C(ffi_plan_gp0))
+
+ .balign 8
+ .globl C(ffi_plan_gp1)
+ FFI_HIDDEN(C(ffi_plan_gp1))
+C(ffi_plan_gp1):
+ FFI_GP_HEAD
+ FFI_GP_LD(0x00, %rdi)
+ FFI_GP_TAIL
+ ENDF(C(ffi_plan_gp1))
+
+ .balign 8
+ .globl C(ffi_plan_gp2)
+ FFI_HIDDEN(C(ffi_plan_gp2))
+C(ffi_plan_gp2):
+ FFI_GP_HEAD
+ FFI_GP_LD(0x08, %rsi)
+ FFI_GP_LD(0x00, %rdi)
+ FFI_GP_TAIL
+ ENDF(C(ffi_plan_gp2))
+
+ .balign 8
+ .globl C(ffi_plan_gp3)
+ FFI_HIDDEN(C(ffi_plan_gp3))
+C(ffi_plan_gp3):
+ FFI_GP_HEAD
+ FFI_GP_LD(0x08, %rsi)
+ FFI_GP_LD(0x10, %rdx)
+ FFI_GP_LD(0x00, %rdi)
+ FFI_GP_TAIL
+ ENDF(C(ffi_plan_gp3))
+
+ .balign 8
+ .globl C(ffi_plan_gp4)
+ FFI_HIDDEN(C(ffi_plan_gp4))
+C(ffi_plan_gp4):
+ FFI_GP_HEAD
+ FFI_GP_LD(0x08, %rsi)
+ FFI_GP_LD(0x10, %rdx)
+ FFI_GP_LD(0x18, %rcx)
+ FFI_GP_LD(0x00, %rdi)
+ FFI_GP_TAIL
+ ENDF(C(ffi_plan_gp4))
+
+ .balign 8
+ .globl C(ffi_plan_gp5)
+ FFI_HIDDEN(C(ffi_plan_gp5))
+C(ffi_plan_gp5):
+ FFI_GP_HEAD
+ FFI_GP_LD(0x08, %rsi)
+ FFI_GP_LD(0x10, %rdx)
+ FFI_GP_LD(0x18, %rcx)
+ FFI_GP_LD(0x20, %r8)
+ FFI_GP_LD(0x00, %rdi)
+ FFI_GP_TAIL
+ ENDF(C(ffi_plan_gp5))
+
+ .balign 8
+ .globl C(ffi_plan_gp6)
+ FFI_HIDDEN(C(ffi_plan_gp6))
+C(ffi_plan_gp6):
+ FFI_GP_HEAD
+ FFI_GP_LD(0x08, %rsi)
+ FFI_GP_LD(0x10, %rdx)
+ FFI_GP_LD(0x18, %rcx)
+ FFI_GP_LD(0x20, %r8)
+ FFI_GP_LD(0x28, %r9)
+ FFI_GP_LD(0x00, %rdi)
+ FFI_GP_TAIL
+ ENDF(C(ffi_plan_gp6))
+
/* 6 general registers, 8 vector registers,
32 bytes of rvalue, 8 bytes of alignment. */
#define ffi_closure_OFS_G 0
@@ -724,6 +873,6 @@ L(EFDE5):
#endif
#endif /* __x86_64__ */
-#if defined __ELF__ && defined __linux__
+#if defined __ELF__ && (defined __linux__ || defined __FreeBSD__)
.section .note.GNU-stack,"",@progbits
#endif
diff --git a/deps/libffi/src/x86/win64.S b/deps/libffi/src/x86/win64.S
index e994fa9e1eea62..185f0a3048fba7 100644
--- a/deps/libffi/src/x86/win64.S
+++ b/deps/libffi/src/x86/win64.S
@@ -255,6 +255,6 @@ C(ffi_closure_win64_alt):
#endif
#endif /* __x86_64__ */
-#if defined __ELF__ && defined __linux__
+#if defined __ELF__ && (defined __linux__ || defined __FreeBSD__)
.section .note.GNU-stack,"",@progbits
#endif
diff --git a/deps/libffi/src/x86/win64_intel.S b/deps/libffi/src/x86/win64_intel.S
index b278a7440040d4..e9eff00da3ce6d 100644
--- a/deps/libffi/src/x86/win64_intel.S
+++ b/deps/libffi/src/x86/win64_intel.S
@@ -237,7 +237,7 @@ ffi_closure_win64_2 LABEL near
cfi_endproc
C(ffi_closure_win64) endp
-#if defined __ELF__ && defined __linux__
+#if defined __ELF__ && (defined __linux__ || defined __FreeBSD__)
.section .note.GNU-stack,"",@progbits
#endif
_text ends
diff --git a/deps/libffi/testsuite/Makefile.am b/deps/libffi/testsuite/Makefile.am
index 2e422b9a44e8e0..c14a880959d835 100644
--- a/deps/libffi/testsuite/Makefile.am
+++ b/deps/libffi/testsuite/Makefile.am
@@ -15,9 +15,13 @@ EXTRA_DIST = config/default.exp emscripten/build.sh emscripten/conftest.py \
libffi.call/align_stdcall.c libffi.call/bpo_38748.c libffi.call/call.exp \
libffi.call/err_bad_typedef.c libffi.call/ffitest.h libffi.call/float.c \
libffi.call/float1.c libffi.call/float2.c libffi.call/float3.c \
- libffi.call/float4.c libffi.call/float_va.c libffi.call/many.c \
+ libffi.call/float4.c libffi.call/float_va.c libffi.call/i128-1.c \
+ libffi.call/large_struct_by_value.c libffi.call/many.c \
libffi.call/many2.c libffi.call/many_double.c libffi.call/many_mixed.c \
+ libffi.call/many_small_structs.c \
libffi.call/negint.c libffi.call/offsets.c libffi.call/overread.c \
+ libffi.call/plan.c libffi.call/plan_mixed.c libffi.call/plan_spill.c \
+ libffi.call/plan_struct.c libffi.call/plan_var.c \
libffi.call/pr1172638.c libffi.call/promotion.c libffi.call/pyobjc_tc.c libffi.call/return_dbl.c \
libffi.call/return_dbl1.c libffi.call/return_dbl2.c libffi.call/return_fl.c \
libffi.call/return_fl1.c libffi.call/return_fl2.c libffi.call/return_fl3.c \
@@ -77,7 +81,7 @@ EXTRA_DIST = config/default.exp emscripten/build.sh emscripten/conftest.py \
libffi.complex/cls_complex_va_double.c libffi.complex/cls_complex_va_float.c libffi.complex/cls_complex_va_longdouble.c \
libffi.complex/complex.exp libffi.complex/complex.inc libffi.complex/complex_defs_double.inc \
libffi.complex/complex_defs_float.inc libffi.complex/complex_defs_longdouble.inc libffi.complex/complex_double.c \
- libffi.complex/complex_float.c libffi.complex/complex_int.c libffi.complex/complex_longdouble.c \
+ libffi.complex/complex_float.c libffi.complex/complex_i128.c libffi.complex/complex_int.c libffi.complex/complex_longdouble.c \
libffi.complex/ffitest.h libffi.complex/many_complex.inc libffi.complex/many_complex_double.c \
libffi.complex/many_complex_float.c libffi.complex/many_complex_longdouble.c libffi.complex/return_complex.inc \
libffi.complex/return_complex1.inc libffi.complex/return_complex1_double.c libffi.complex/return_complex1_float.c \
diff --git a/deps/libffi/testsuite/Makefile.in b/deps/libffi/testsuite/Makefile.in
index 04f864ca251555..1b29b90d363346 100644
--- a/deps/libffi/testsuite/Makefile.in
+++ b/deps/libffi/testsuite/Makefile.in
@@ -303,9 +303,13 @@ EXTRA_DIST = config/default.exp emscripten/build.sh emscripten/conftest.py \
libffi.call/align_stdcall.c libffi.call/bpo_38748.c libffi.call/call.exp \
libffi.call/err_bad_typedef.c libffi.call/ffitest.h libffi.call/float.c \
libffi.call/float1.c libffi.call/float2.c libffi.call/float3.c \
- libffi.call/float4.c libffi.call/float_va.c libffi.call/many.c \
+ libffi.call/float4.c libffi.call/float_va.c libffi.call/i128-1.c \
+ libffi.call/large_struct_by_value.c libffi.call/many.c \
libffi.call/many2.c libffi.call/many_double.c libffi.call/many_mixed.c \
+ libffi.call/many_small_structs.c \
libffi.call/negint.c libffi.call/offsets.c libffi.call/overread.c \
+ libffi.call/plan.c libffi.call/plan_mixed.c libffi.call/plan_spill.c \
+ libffi.call/plan_struct.c libffi.call/plan_var.c \
libffi.call/pr1172638.c libffi.call/promotion.c libffi.call/pyobjc_tc.c libffi.call/return_dbl.c \
libffi.call/return_dbl1.c libffi.call/return_dbl2.c libffi.call/return_fl.c \
libffi.call/return_fl1.c libffi.call/return_fl2.c libffi.call/return_fl3.c \
@@ -365,7 +369,7 @@ EXTRA_DIST = config/default.exp emscripten/build.sh emscripten/conftest.py \
libffi.complex/cls_complex_va_double.c libffi.complex/cls_complex_va_float.c libffi.complex/cls_complex_va_longdouble.c \
libffi.complex/complex.exp libffi.complex/complex.inc libffi.complex/complex_defs_double.inc \
libffi.complex/complex_defs_float.inc libffi.complex/complex_defs_longdouble.inc libffi.complex/complex_double.c \
- libffi.complex/complex_float.c libffi.complex/complex_int.c libffi.complex/complex_longdouble.c \
+ libffi.complex/complex_float.c libffi.complex/complex_i128.c libffi.complex/complex_int.c libffi.complex/complex_longdouble.c \
libffi.complex/ffitest.h libffi.complex/many_complex.inc libffi.complex/many_complex_double.c \
libffi.complex/many_complex_float.c libffi.complex/many_complex_longdouble.c libffi.complex/return_complex.inc \
libffi.complex/return_complex1.inc libffi.complex/return_complex1_double.c libffi.complex/return_complex1_float.c \
diff --git a/deps/libffi/testsuite/lib/libffi.exp b/deps/libffi/testsuite/lib/libffi.exp
index 0a98a07daf6702..55afb8f094a026 100644
--- a/deps/libffi/testsuite/lib/libffi.exp
+++ b/deps/libffi/testsuite/lib/libffi.exp
@@ -411,6 +411,10 @@ proc libffi_target_compile { source dest type options } {
lappend options "libs= -lpthread"
}
+ if { [string match "*-*-netbsd*" $target_triplet] } {
+ lappend options "libs= -lpthread"
+ }
+
lappend options "libs= -lffi"
if { [string match "aarch64*-*-linux*" $target_triplet] } {
diff --git a/deps/libffi/testsuite/libffi.call/i128-1.c b/deps/libffi/testsuite/libffi.call/i128-1.c
new file mode 100644
index 00000000000000..1395f5e9426026
--- /dev/null
+++ b/deps/libffi/testsuite/libffi.call/i128-1.c
@@ -0,0 +1,103 @@
+/* Area: ffi_call
+ Purpose: Check int128 call and return.
+ Limitations: none.
+ PR: none. */
+
+/* { dg-do run } */
+#include "ffitest.h"
+
+#if defined(FFI_TARGET_HAS_INT128) && defined(__SIZEOF_INT128__)
+
+typedef __int128_t i128;
+
+static const i128 val = ((i128)0x01020304050607ull << 64) | 0x08090a0b0c0d0e0full;
+static const int dummy = 0xdeadbeef;
+
+#define D(X) int X __attribute__((unused))
+
+static i128 f0(i128 x)
+{
+ return x;
+}
+
+static i128 f1(D(a), i128 x)
+{
+ return x;
+}
+
+static i128 f2(D(a), D(b), i128 x)
+{
+ return x;
+}
+
+static i128 f3(D(a), D(b), D(c), i128 x)
+{
+ return x;
+}
+
+static i128 f4(D(a), D(b), D(c), D(d), i128 x)
+{
+ return x;
+}
+
+static i128 f5(D(a), D(b), D(c), D(d), D(e), i128 x)
+{
+ return x;
+}
+
+static i128 f6(D(a), D(b), D(c), D(d), D(e), D(f), i128 x)
+{
+ return x;
+}
+
+static i128 f7(D(a), D(b), D(c), D(d), D(e), D(f), D(g), i128 x)
+{
+ return x;
+}
+
+static i128 f8(D(a), D(b), D(c), D(d), D(e), D(f), D(g), D(h), i128 x)
+{
+ return x;
+}
+
+#define N 9
+
+static void * const funcs[N] = {
+ f0, f1, f2, f3, f4, f5, f6, f7, f8
+};
+
+int main (void)
+{
+ int i;
+
+ for (i = 0; i < N; i++)
+ {
+ ffi_cif cif;
+ ffi_status s;
+ ffi_type *args[N];
+ void *values[N];
+ i128 ret;
+ int j;
+
+ for (j = 0; j < i; j++)
+ {
+ args[j] = &ffi_type_sint;
+ values[j] = (void *)&dummy;
+ }
+ args[i] = &ffi_type_sint128;
+ values[i] = (void *)&val;
+
+ s = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, i + 1,
+ &ffi_type_sint128, args);
+ CHECK(s == FFI_OK);
+
+ ffi_call(&cif, FFI_FN(funcs[i]), &ret, values);
+ CHECK(ret == val);
+ }
+
+ return 0;
+}
+
+#else
+int main (void) { return 0; }
+#endif
diff --git a/deps/libffi/testsuite/libffi.call/large_struct_by_value.c b/deps/libffi/testsuite/libffi.call/large_struct_by_value.c
new file mode 100644
index 00000000000000..016870dfe1096c
--- /dev/null
+++ b/deps/libffi/testsuite/libffi.call/large_struct_by_value.c
@@ -0,0 +1,63 @@
+/* Area: ffi_call
+ Purpose: Pass a large struct by value (more words than arg registers).
+ Limitations: none.
+ PR: none.
+ Originator: secscan regression (ARCompact used_stack overflow).
+
+ Regression test: on ARCompact (ARC 32-bit), ffi_call_int sized the stack
+ argument area at two words per argument, but a by-value struct is marshalled
+ one word ("atom") at a time, and words beyond the argument registers were
+ written to that under-sized area without bound. Passing a 64-byte struct by
+ value (sixteen 32-bit words, eight more than the eight argument registers)
+ must marshal correctly and return the expected sum. */
+
+/* { dg-do run } */
+#include "ffitest.h"
+
+typedef struct { int v[8]; } big_struct;
+
+static int ABI_ATTR
+sum_big (big_struct s)
+{
+ int i, sum = 0;
+ for (i = 0; i < 8; i++)
+ sum += s.v[i];
+ return sum;
+}
+
+int main (void)
+{
+ ffi_cif cif;
+ ffi_type *args[1];
+ void *values[1];
+ ffi_type bs_type;
+ ffi_type *bs_elements[9];
+ big_struct in;
+ ffi_arg result = 0;
+ int i, expected = 0;
+
+ bs_type.size = 0;
+ bs_type.alignment = 0;
+ bs_type.type = FFI_TYPE_STRUCT;
+ for (i = 0; i < 8; i++)
+ bs_elements[i] = &ffi_type_sint;
+ bs_elements[8] = NULL;
+ bs_type.elements = bs_elements;
+
+ for (i = 0; i < 8; i++)
+ {
+ in.v[i] = 0x1111 * (i + 1);
+ expected += in.v[i];
+ }
+
+ args[0] = &bs_type;
+ values[0] = ∈
+
+ CHECK(ffi_prep_cif(&cif, ABI_NUM, 1, &ffi_type_sint, args) == FFI_OK);
+
+ ffi_call(&cif, FFI_FN(sum_big), &result, values);
+
+ CHECK((int) result == expected);
+
+ exit(0);
+}
diff --git a/deps/libffi/testsuite/libffi.call/many_small_structs.c b/deps/libffi/testsuite/libffi.call/many_small_structs.c
new file mode 100644
index 00000000000000..242c38cfc135bf
--- /dev/null
+++ b/deps/libffi/testsuite/libffi.call/many_small_structs.c
@@ -0,0 +1,92 @@
+/* Area: ffi_call
+ Purpose: Pass many small (8-byte) structs by value.
+ Limitations: none.
+ PR: none.
+ Originator: secscan regression (PA-RISC 32-bit slot under-count).
+
+ Regression test: on PA-RISC 32-bit (FFI_PA32), ffi_size_stack_pa32 reserves
+ one stack slot per struct (pa/ffi.c: "z += 1"), but ffi_prep_args_pa32
+ consumes two slots for a 5-8 byte struct passed inline. ffi_call_pa32 sets
+ the argument base to sp + cif->bytes and ffi_prep_args_pa32 writes each
+ argument at (base - slot*4), so once the marshaller's slot count exceeds
+ cif->bytes/4 the writes spill below sp -- first into the 64-byte register
+ save area, then over ffi_call_pa32's own saved return pointer.
+
+ A few small structs only overflow into harmless scratch (the call still
+ returns correctly), so this uses enough 8-byte structs that the overflow
+ reaches the saved return pointer and corrupts the return path. On a correct
+ backend all arguments marshal within the allocated frame and the call simply
+ returns the expected sums; the test passes everywhere except an unfixed
+ FFI_PA32. */
+
+/* { dg-do run } */
+#include "ffitest.h"
+
+typedef struct { int a; int b; } small_struct;
+
+#define NARGS 40
+
+static small_struct ABI_ATTR
+many_small (small_struct s0, small_struct s1, small_struct s2, small_struct s3,
+ small_struct s4, small_struct s5, small_struct s6, small_struct s7,
+ small_struct s8, small_struct s9, small_struct s10, small_struct s11,
+ small_struct s12, small_struct s13, small_struct s14, small_struct s15,
+ small_struct s16, small_struct s17, small_struct s18, small_struct s19,
+ small_struct s20, small_struct s21, small_struct s22, small_struct s23,
+ small_struct s24, small_struct s25, small_struct s26, small_struct s27,
+ small_struct s28, small_struct s29, small_struct s30, small_struct s31,
+ small_struct s32, small_struct s33, small_struct s34, small_struct s35,
+ small_struct s36, small_struct s37, small_struct s38, small_struct s39)
+{
+ small_struct r;
+ r.a = s0.a + s1.a + s2.a + s3.a + s4.a + s5.a + s6.a + s7.a
+ + s8.a + s9.a + s10.a + s11.a + s12.a + s13.a + s14.a + s15.a
+ + s16.a + s17.a + s18.a + s19.a + s20.a + s21.a + s22.a + s23.a
+ + s24.a + s25.a + s26.a + s27.a + s28.a + s29.a + s30.a + s31.a
+ + s32.a + s33.a + s34.a + s35.a + s36.a + s37.a + s38.a + s39.a;
+ r.b = s0.b + s1.b + s2.b + s3.b + s4.b + s5.b + s6.b + s7.b
+ + s8.b + s9.b + s10.b + s11.b + s12.b + s13.b + s14.b + s15.b
+ + s16.b + s17.b + s18.b + s19.b + s20.b + s21.b + s22.b + s23.b
+ + s24.b + s25.b + s26.b + s27.b + s28.b + s29.b + s30.b + s31.b
+ + s32.b + s33.b + s34.b + s35.b + s36.b + s37.b + s38.b + s39.b;
+ return r;
+}
+
+int main (void)
+{
+ ffi_cif cif;
+ ffi_type *args[NARGS];
+ void *values[NARGS];
+ ffi_type ss_type;
+ ffi_type *ss_elements[3];
+ small_struct in[NARGS];
+ small_struct result = { 0, 0 };
+ int i, expected_a = 0, expected_b = 0;
+
+ ss_type.size = 0;
+ ss_type.alignment = 0;
+ ss_type.type = FFI_TYPE_STRUCT;
+ ss_type.elements = ss_elements;
+ ss_elements[0] = &ffi_type_sint;
+ ss_elements[1] = &ffi_type_sint;
+ ss_elements[2] = NULL;
+
+ for (i = 0; i < NARGS; i++)
+ {
+ in[i].a = i + 1;
+ in[i].b = -(i + 1);
+ expected_a += in[i].a;
+ expected_b += in[i].b;
+ args[i] = &ss_type;
+ values[i] = &in[i];
+ }
+
+ CHECK(ffi_prep_cif(&cif, ABI_NUM, NARGS, &ss_type, args) == FFI_OK);
+
+ ffi_call(&cif, FFI_FN(many_small), &result, values);
+
+ CHECK(result.a == expected_a);
+ CHECK(result.b == expected_b);
+
+ exit(0);
+}
diff --git a/deps/libffi/testsuite/libffi.call/plan.c b/deps/libffi/testsuite/libffi.call/plan.c
new file mode 100644
index 00000000000000..afed0977e6b1cb
--- /dev/null
+++ b/deps/libffi/testsuite/libffi.call/plan.c
@@ -0,0 +1,127 @@
+/* Area: ffi_call_plan
+ Purpose: Check that a reusable call plan reproduces ffi_call for the
+ pure-GP64 fast path, pointer arguments and repeated reuse,
+ and that a signature with no fast path still yields a usable
+ plan that falls back to ffi_call.
+ Limitations: none.
+ PR: none.
+ Originator: ffi_call_plan tests */
+
+/* { dg-do run } */
+#include "ffitest.h"
+
+static uint64_t gp6(uint64_t a, uint64_t b, uint64_t c,
+ uint64_t d, uint64_t e, uint64_t f)
+{
+ return a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6;
+}
+
+static void *ptr_ident(void *p)
+{
+ return p;
+}
+
+struct small_pair { long x; long y; };
+
+static long ssum(struct small_pair s)
+{
+ return s.x - s.y;
+}
+
+int main (void)
+{
+ ffi_cif cif;
+ ffi_type *args[6];
+ void *values[6];
+ ffi_call_plan *plan;
+ uint64_t a[6], r_call, r_plan;
+ int i, k;
+
+ /* Pure GP64: every argument is one 64-bit integer, so build_plan
+ selects the ffi_plan_gpN direct thunk. Reuse the plan across many
+ invocations with changing values. */
+ for (i = 0; i < 6; i++)
+ args[i] = &ffi_type_uint64;
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 6, &ffi_type_uint64, args)
+ == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ for (k = 0; k < 100; k++)
+ {
+ for (i = 0; i < 6; i++)
+ {
+ a[i] = (uint64_t) (k * 7 + i + 1);
+ values[i] = &a[i];
+ }
+ ffi_call(&cif, FFI_FN(gp6), &r_call, values);
+ ffi_call_plan_invoke(plan, FFI_FN(gp6), &r_plan, values);
+ CHECK(r_call == r_plan);
+ CHECK(r_plan == gp6(a[0], a[1], a[2], a[3], a[4], a[5]));
+ }
+ ffi_call_plan_free(plan);
+
+ /* Pointer argument and pointer return. */
+ {
+ ffi_cif cifp;
+ ffi_type *pargs[1];
+ void *pvalues[1];
+ void *in, *rc, *rp;
+ ffi_call_plan *planp;
+
+ pargs[0] = &ffi_type_pointer;
+ CHECK(ffi_prep_cif(&cifp, FFI_DEFAULT_ABI, 1, &ffi_type_pointer, pargs)
+ == FFI_OK);
+ planp = ffi_call_plan_alloc(&cifp);
+ CHECK(planp != NULL);
+
+ in = &cifp;
+ pvalues[0] = ∈
+ ffi_call(&cifp, FFI_FN(ptr_ident), &rc, pvalues);
+ ffi_call_plan_invoke(planp, FFI_FN(ptr_ident), &rp, pvalues);
+ CHECK(rc == in);
+ CHECK(rp == in);
+ ffi_call_plan_free(planp);
+ }
+
+ /* No fast path: a struct-by-value argument. build_plan returns NULL for
+ the fast plan, but ffi_call_plan_alloc must still hand back a valid plan
+ whose invoke falls back to ffi_call and produces the same result. */
+ {
+ ffi_cif cifs;
+ ffi_type *sargs[1];
+ ffi_type stype;
+ ffi_type *selements[3];
+ void *svalues[1];
+ struct small_pair s;
+ ffi_arg rc, rp;
+ ffi_call_plan *plans;
+
+ selements[0] = &ffi_type_slong;
+ selements[1] = &ffi_type_slong;
+ selements[2] = NULL;
+ stype.size = stype.alignment = 0;
+ stype.type = FFI_TYPE_STRUCT;
+ stype.elements = selements;
+
+ sargs[0] = &stype;
+ CHECK(ffi_prep_cif(&cifs, FFI_DEFAULT_ABI, 1, &ffi_type_slong, sargs)
+ == FFI_OK);
+ plans = ffi_call_plan_alloc(&cifs);
+ CHECK(plans != NULL);
+
+ s.x = 123;
+ s.y = 45;
+ svalues[0] = &s;
+ ffi_call(&cifs, FFI_FN(ssum), &rc, svalues);
+ ffi_call_plan_invoke(plans, FFI_FN(ssum), &rp, svalues);
+ CHECK(rc == rp);
+ CHECK((long) rp == ssum(s));
+ ffi_call_plan_free(plans);
+ }
+
+ /* Freeing NULL is documented to be harmless. */
+ ffi_call_plan_free(NULL);
+
+ exit(0);
+}
diff --git a/deps/libffi/testsuite/libffi.call/plan_mixed.c b/deps/libffi/testsuite/libffi.call/plan_mixed.c
new file mode 100644
index 00000000000000..54b1f6290c4422
--- /dev/null
+++ b/deps/libffi/testsuite/libffi.call/plan_mixed.c
@@ -0,0 +1,153 @@
+/* Area: ffi_call_plan
+ Purpose: Check that a reusable call plan reproduces ffi_call for
+ signatures that mix general-purpose and SSE registers, for
+ float and double returns, for signed narrow arguments, and
+ for integer returns narrower than a register (which the plan
+ must widen to ffi_arg exactly as ffi_call does).
+ Limitations: none.
+ PR: none.
+ Originator: ffi_call_plan tests */
+
+/* { dg-do run } */
+#include "ffitest.h"
+
+static double mixed(int a, double b, long c, float d,
+ long long e, double f)
+{
+ return (double) a + b + (double) c + (double) d + (double) e + f;
+}
+
+static float faddf(float a, float b)
+{
+ return a + b;
+}
+
+static signed char ret_sc(signed char x)
+{
+ return (signed char) (x + 1);
+}
+
+static unsigned char ret_uc(unsigned char x)
+{
+ return (unsigned char) (x + 1);
+}
+
+int main (void)
+{
+ /* Mixed GP + SSE arguments, double return: exercises the fast local-image
+ path with both integer and SSE moves and the ssecount (al) setup. */
+ {
+ ffi_cif cif;
+ ffi_type *args[6];
+ void *values[6];
+ ffi_call_plan *plan;
+ int a = 3;
+ double b = 1.5, f = -2.25, rc, rp;
+ long c = 7;
+ float d = 0.5f;
+ long long e = -11;
+
+ args[0] = &ffi_type_sint;
+ args[1] = &ffi_type_double;
+ args[2] = &ffi_type_slong;
+ args[3] = &ffi_type_float;
+ args[4] = &ffi_type_sint64;
+ args[5] = &ffi_type_double;
+ values[0] = &a; values[1] = &b; values[2] = &c;
+ values[3] = &d; values[4] = &e; values[5] = &f;
+
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 6, &ffi_type_double, args)
+ == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ ffi_call(&cif, FFI_FN(mixed), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(mixed), &rp, values);
+ CHECK_DOUBLE_EQ(rc, rp);
+ CHECK_DOUBLE_EQ(rp, mixed(a, b, c, d, e, f));
+ ffi_call_plan_free(plan);
+ }
+
+ /* Float arguments and float return. */
+ {
+ ffi_cif cif;
+ ffi_type *args[2];
+ void *values[2];
+ ffi_call_plan *plan;
+ float a = 1.25f, b = 2.5f, rc, rp;
+
+ args[0] = &ffi_type_float;
+ args[1] = &ffi_type_float;
+ values[0] = &a;
+ values[1] = &b;
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2, &ffi_type_float, args)
+ == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ ffi_call(&cif, FFI_FN(faddf), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(faddf), &rp, values);
+ CHECK_FLOAT_EQ(rc, rp);
+ CHECK_FLOAT_EQ(rp, faddf(a, b));
+ ffi_call_plan_free(plan);
+ }
+
+ /* Signed narrow argument and signed narrow return: the plan sign-extends
+ the argument and widens the return to ffi_arg just as ffi_call does. */
+ {
+ ffi_cif cif;
+ ffi_type *args[1];
+ void *values[1];
+ ffi_call_plan *plan;
+ signed char in;
+ ffi_arg rc, rp;
+ int v;
+
+ args[0] = &ffi_type_schar;
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &ffi_type_schar, args)
+ == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ for (v = -128; v < 128; v++)
+ {
+ in = (signed char) v;
+ values[0] = ∈
+ ffi_call(&cif, FFI_FN(ret_sc), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(ret_sc), &rp, values);
+ CHECK(rc == rp);
+ CHECK((signed char) rp == ret_sc(in));
+ }
+ ffi_call_plan_free(plan);
+ }
+
+ /* Unsigned narrow argument and unsigned narrow return. */
+ {
+ ffi_cif cif;
+ ffi_type *args[1];
+ void *values[1];
+ ffi_call_plan *plan;
+ unsigned char in;
+ ffi_arg rc, rp;
+ int v;
+
+ args[0] = &ffi_type_uchar;
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &ffi_type_uchar, args)
+ == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ for (v = 0; v < 256; v++)
+ {
+ in = (unsigned char) v;
+ values[0] = ∈
+ ffi_call(&cif, FFI_FN(ret_uc), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(ret_uc), &rp, values);
+ CHECK(rc == rp);
+ CHECK((unsigned char) rp == ret_uc(in));
+ }
+ ffi_call_plan_free(plan);
+ }
+
+ exit(0);
+}
diff --git a/deps/libffi/testsuite/libffi.call/plan_spill.c b/deps/libffi/testsuite/libffi.call/plan_spill.c
new file mode 100644
index 00000000000000..c073c99142e423
--- /dev/null
+++ b/deps/libffi/testsuite/libffi.call/plan_spill.c
@@ -0,0 +1,142 @@
+/* Area: ffi_call_plan
+ Purpose: Check that a reusable call plan reproduces ffi_call when
+ arguments spill past the argument registers onto the stack.
+ This drives the non-fast plan path (a move list handed to
+ ffi_call_unix64) for GP spill, SSE spill, and a mix of both.
+ Limitations: none.
+ PR: none.
+ Originator: ffi_call_plan tests */
+
+/* { dg-do run } */
+#include "ffitest.h"
+
+/* 10 integers: 6 in registers, 4 spilled to the stack. */
+static long long gp10(long long a1, long long a2, long long a3, long long a4,
+ long long a5, long long a6, long long a7, long long a8,
+ long long a9, long long a10)
+{
+ return a1 + a2 * 2 + a3 * 3 + a4 * 4 + a5 * 5
+ + a6 * 6 + a7 * 7 + a8 * 8 + a9 * 9 + a10 * 10;
+}
+
+/* 12 doubles: 8 in SSE registers, 4 spilled to the stack. */
+static double sse12(double a1, double a2, double a3, double a4,
+ double a5, double a6, double a7, double a8,
+ double a9, double a10, double a11, double a12)
+{
+ return a1 + a2 * 2 + a3 * 3 + a4 * 4 + a5 * 5 + a6 * 6
+ + a7 * 7 + a8 * 8 + a9 * 9 + a10 * 10 + a11 * 11 + a12 * 12;
+}
+
+/* 8 ints + 8 doubles: both classes spill. */
+static double mix16(long i1, long i2, long i3, long i4,
+ long i5, long i6, long i7, long i8,
+ double d1, double d2, double d3, double d4,
+ double d5, double d6, double d7, double d8)
+{
+ double s = 0.0;
+ s += (double) (i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8);
+ s += d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8;
+ return s;
+}
+
+int main (void)
+{
+ /* GP spill. */
+ {
+ ffi_cif cif;
+ ffi_type *args[10];
+ void *values[10];
+ long long a[10];
+ long long rc, rp;
+ ffi_call_plan *plan;
+ int i;
+
+ for (i = 0; i < 10; i++)
+ {
+ args[i] = &ffi_type_sint64;
+ a[i] = (long long) (i + 1) * 100 - 3;
+ values[i] = &a[i];
+ }
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 10, &ffi_type_sint64, args)
+ == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ ffi_call(&cif, FFI_FN(gp10), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(gp10), &rp, values);
+ CHECK(rc == rp);
+ CHECK(rp == gp10(a[0], a[1], a[2], a[3], a[4],
+ a[5], a[6], a[7], a[8], a[9]));
+ ffi_call_plan_free(plan);
+ }
+
+ /* SSE spill. */
+ {
+ ffi_cif cif;
+ ffi_type *args[12];
+ void *values[12];
+ double a[12];
+ double rc, rp;
+ ffi_call_plan *plan;
+ int i;
+
+ for (i = 0; i < 12; i++)
+ {
+ args[i] = &ffi_type_double;
+ a[i] = (double) i + 0.25;
+ values[i] = &a[i];
+ }
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 12, &ffi_type_double, args)
+ == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ ffi_call(&cif, FFI_FN(sse12), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(sse12), &rp, values);
+ CHECK_DOUBLE_EQ(rc, rp);
+ CHECK_DOUBLE_EQ(rp, sse12(a[0], a[1], a[2], a[3], a[4], a[5],
+ a[6], a[7], a[8], a[9], a[10], a[11]));
+ ffi_call_plan_free(plan);
+ }
+
+ /* Both classes spill. */
+ {
+ ffi_cif cif;
+ ffi_type *args[16];
+ void *values[16];
+ long iv[8];
+ double dv[8];
+ double rc, rp;
+ ffi_call_plan *plan;
+ int i;
+
+ for (i = 0; i < 8; i++)
+ {
+ args[i] = &ffi_type_slong;
+ iv[i] = (long) (i + 1);
+ values[i] = &iv[i];
+ }
+ for (i = 0; i < 8; i++)
+ {
+ args[8 + i] = &ffi_type_double;
+ dv[i] = (double) (i + 1) * 0.5;
+ values[8 + i] = &dv[i];
+ }
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 16, &ffi_type_double, args)
+ == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ ffi_call(&cif, FFI_FN(mix16), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(mix16), &rp, values);
+ CHECK_DOUBLE_EQ(rc, rp);
+ CHECK_DOUBLE_EQ(rp, mix16(iv[0], iv[1], iv[2], iv[3],
+ iv[4], iv[5], iv[6], iv[7],
+ dv[0], dv[1], dv[2], dv[3],
+ dv[4], dv[5], dv[6], dv[7]));
+ ffi_call_plan_free(plan);
+ }
+
+ exit(0);
+}
diff --git a/deps/libffi/testsuite/libffi.call/plan_struct.c b/deps/libffi/testsuite/libffi.call/plan_struct.c
new file mode 100644
index 00000000000000..816296d77a8afa
--- /dev/null
+++ b/deps/libffi/testsuite/libffi.call/plan_struct.c
@@ -0,0 +1,163 @@
+/* Area: ffi_call_plan
+ Purpose: Check that a reusable call plan reproduces ffi_call for struct
+ returns. A struct return does not disable planning (only a
+ struct *argument* does), so this drives both the in-memory
+ return path (RET_IN_MEM, including a NULL rvalue) and the
+ register-pair struct return path, plus a large struct argument
+ that forces the ffi_call by-value copy fallback.
+ Limitations: none.
+ PR: none.
+ Originator: ffi_call_plan tests */
+
+/* { dg-do run } */
+#include "ffitest.h"
+
+static int call_count = 0;
+
+/* 24 bytes: returned in memory (a hidden pointer in the first argument). */
+struct big3 { double a, b, c; };
+
+static struct big3 make_big3(double a, double b, double c)
+{
+ struct big3 r;
+ call_count++;
+ r.a = a + 1.0;
+ r.b = b + 2.0;
+ r.c = c + 3.0;
+ return r;
+}
+
+/* A struct larger than 16 bytes passed by value forces ffi_call to make a
+ copy; build_plan has no fast path for a struct argument, so this exercises
+ the plan's fallback to ffi_call. */
+static double sum_big3(struct big3 s)
+{
+ return s.a + s.b + s.c;
+}
+
+/* 16 bytes: returned in a register pair (RAX:RDX on x86-64). */
+struct pair2 { long x, y; };
+
+static struct pair2 make_pair2(long x, long y)
+{
+ struct pair2 r;
+ r.x = x * 2;
+ r.y = y * 3;
+ return r;
+}
+
+int main (void)
+{
+ ffi_type *big3_elements[4];
+ ffi_type big3_t;
+ ffi_type *pair2_elements[3];
+ ffi_type pair2_t;
+
+ big3_elements[0] = &ffi_type_double;
+ big3_elements[1] = &ffi_type_double;
+ big3_elements[2] = &ffi_type_double;
+ big3_elements[3] = NULL;
+ big3_t.size = big3_t.alignment = 0;
+ big3_t.type = FFI_TYPE_STRUCT;
+ big3_t.elements = big3_elements;
+
+ pair2_elements[0] = &ffi_type_slong;
+ pair2_elements[1] = &ffi_type_slong;
+ pair2_elements[2] = NULL;
+ pair2_t.size = pair2_t.alignment = 0;
+ pair2_t.type = FFI_TYPE_STRUCT;
+ pair2_t.elements = pair2_elements;
+
+ /* In-memory struct return with scalar arguments. */
+ {
+ ffi_cif cif;
+ ffi_type *args[3];
+ void *values[3];
+ ffi_call_plan *plan;
+ double a = 10.0, b = 20.0, c = 30.0;
+ struct big3 rc, rp;
+ int before;
+
+ args[0] = &ffi_type_double;
+ args[1] = &ffi_type_double;
+ args[2] = &ffi_type_double;
+ values[0] = &a;
+ values[1] = &b;
+ values[2] = &c;
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 3, &big3_t, args) == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ ffi_call(&cif, FFI_FN(make_big3), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(make_big3), &rp, values);
+ CHECK_DOUBLE_EQ(rc.a, rp.a);
+ CHECK_DOUBLE_EQ(rc.b, rp.b);
+ CHECK_DOUBLE_EQ(rc.c, rp.c);
+ CHECK_DOUBLE_EQ(rp.a, a + 1.0);
+ CHECK_DOUBLE_EQ(rp.b, b + 2.0);
+ CHECK_DOUBLE_EQ(rp.c, c + 3.0);
+
+ /* A NULL rvalue for an in-memory struct return must not crash: libffi
+ supplies scratch space and discards the result, but the callee still
+ runs. Confirm the call actually happened. */
+ before = call_count;
+ ffi_call_plan_invoke(plan, FFI_FN(make_big3), NULL, values);
+ CHECK(call_count == before + 1);
+
+ ffi_call_plan_free(plan);
+ }
+
+ /* Large struct argument: no fast path, falls back to ffi_call. */
+ {
+ ffi_cif cif;
+ ffi_type *args[1];
+ void *values[1];
+ ffi_call_plan *plan;
+ struct big3 s;
+ double rc, rp;
+
+ s.a = 1.5;
+ s.b = 2.5;
+ s.c = 3.5;
+ args[0] = &big3_t;
+ values[0] = &s;
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &ffi_type_double, args)
+ == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ ffi_call(&cif, FFI_FN(sum_big3), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(sum_big3), &rp, values);
+ CHECK_DOUBLE_EQ(rc, rp);
+ CHECK_DOUBLE_EQ(rp, sum_big3(s));
+ ffi_call_plan_free(plan);
+ }
+
+ /* Register-pair struct return. */
+ {
+ ffi_cif cif;
+ ffi_type *args[2];
+ void *values[2];
+ ffi_call_plan *plan;
+ long x = 7, y = 11;
+ struct pair2 rc, rp;
+
+ args[0] = &ffi_type_slong;
+ args[1] = &ffi_type_slong;
+ values[0] = &x;
+ values[1] = &y;
+ CHECK(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2, &pair2_t, args) == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ ffi_call(&cif, FFI_FN(make_pair2), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(make_pair2), &rp, values);
+ CHECK(rc.x == rp.x);
+ CHECK(rc.y == rp.y);
+ CHECK(rp.x == x * 2);
+ CHECK(rp.y == y * 3);
+ ffi_call_plan_free(plan);
+ }
+
+ exit(0);
+}
diff --git a/deps/libffi/testsuite/libffi.call/plan_var.c b/deps/libffi/testsuite/libffi.call/plan_var.c
new file mode 100644
index 00000000000000..e630156b23f077
--- /dev/null
+++ b/deps/libffi/testsuite/libffi.call/plan_var.c
@@ -0,0 +1,73 @@
+/* Area: ffi_call_plan
+ Purpose: Check that a reusable call plan reproduces ffi_call for a
+ variadic signature prepared with ffi_prep_cif_var. The
+ variadic double arguments travel in SSE registers, so the
+ fast path must set the vector-register count (al) correctly.
+ Limitations: none.
+ PR: none.
+ Originator: ffi_call_plan tests */
+
+/* { dg-do run } */
+#include
+#include "ffitest.h"
+
+static double vsum(int n, ...)
+{
+ va_list ap;
+ double s = 0.0;
+ int i;
+
+ va_start(ap, n);
+ for (i = 0; i < n; i++)
+ s += va_arg(ap, double);
+ va_end(ap);
+ return s;
+}
+
+static void
+run (int nvar)
+{
+ ffi_cif cif;
+ ffi_type *args[1 + 8];
+ void *values[1 + 8];
+ double d[8];
+ int n = nvar;
+ double rc, rp, expect;
+ ffi_call_plan *plan;
+ int i;
+
+ CHECK(nvar <= 8);
+
+ args[0] = &ffi_type_sint;
+ values[0] = &n;
+ expect = 0.0;
+ for (i = 0; i < nvar; i++)
+ {
+ d[i] = (double) (i + 1) * 1.5;
+ args[1 + i] = &ffi_type_double;
+ values[1 + i] = &d[i];
+ expect += d[i];
+ }
+
+ CHECK(ffi_prep_cif_var(&cif, FFI_DEFAULT_ABI, 1, 1 + nvar,
+ &ffi_type_double, args) == FFI_OK);
+ plan = ffi_call_plan_alloc(&cif);
+ CHECK(plan != NULL);
+
+ ffi_call(&cif, FFI_FN(vsum), &rc, values);
+ ffi_call_plan_invoke(plan, FFI_FN(vsum), &rp, values);
+ CHECK_DOUBLE_EQ(rc, rp);
+ CHECK_DOUBLE_EQ(rp, expect);
+
+ ffi_call_plan_free(plan);
+}
+
+int main (void)
+{
+ run (0);
+ run (1);
+ run (3);
+ run (5);
+ run (8);
+ exit(0);
+}
diff --git a/deps/libffi/testsuite/libffi.closures/closure.exp b/deps/libffi/testsuite/libffi.closures/closure.exp
index ed4145ca843b4a..9d53294909ee73 100644
--- a/deps/libffi/testsuite/libffi.closures/closure.exp
+++ b/deps/libffi/testsuite/libffi.closures/closure.exp
@@ -36,7 +36,7 @@ if { [string match $compiler_vendor "microsoft"] } {
set tlist [lsort [glob -nocomplain -- $srcdir/$subdir/*.c]]
if { [libffi_feature_test "#if FFI_CLOSURES"] } {
- run-many-tests $tlist ""
+ run-many-tests $tlist $additional_options
} else {
foreach test $tlist {
unsupported "$test"
diff --git a/deps/libffi/testsuite/libffi.closures/closure_loc_fn0.c b/deps/libffi/testsuite/libffi.closures/closure_loc_fn0.c
index f344a6074e1429..179485d9b57b87 100644
--- a/deps/libffi/testsuite/libffi.closures/closure_loc_fn0.c
+++ b/deps/libffi/testsuite/libffi.closures/closure_loc_fn0.c
@@ -6,6 +6,7 @@
PR: none.
Originator: 20030828 */
+/* { dg-do run } */
#include "ffitest.h"
@@ -80,8 +81,10 @@ int main (void)
CHECK(ffi_prep_closure_loc(pcl, &cif, closure_loc_test_fn0,
(void *) 3 /* userdata */, codeloc) == FFI_OK);
-#if !defined(FFI_EXEC_STATIC_TRAMP) && !defined(__EMSCRIPTEN__)
- /* With static trampolines, the codeloc does not point to closure */
+#if !defined(FFI_EXEC_STATIC_TRAMP) && !defined(__EMSCRIPTEN__) \
+ && !(defined(FFI_EXEC_TRAMPOLINE_TABLE) && FFI_EXEC_TRAMPOLINE_TABLE)
+ /* With static trampolines or a trampoline table (Apple aarch64), the
+ codeloc does not point to the closure */
CHECK(memcmp(pcl, FFI_CL(codeloc), sizeof(*pcl)) == 0);
#endif
diff --git a/deps/libffi/testsuite/libffi.complex/complex_i128.c b/deps/libffi/testsuite/libffi.complex/complex_i128.c
new file mode 100644
index 00000000000000..a599d556e12083
--- /dev/null
+++ b/deps/libffi/testsuite/libffi.complex/complex_i128.c
@@ -0,0 +1,118 @@
+/* Area: ffi_call
+ Purpose: Check complex int128 call and return.
+ Limitations: none.
+ PR: none. */
+
+/* { dg-do run } */
+#include "ffitest.h"
+
+/* clang defines __SIZEOF_INT128__ but does not support _Complex __int128,
+ so exclude it here and fall through to the trivial main() below. */
+#if defined(FFI_TARGET_HAS_INT128) && \
+ defined(FFI_TARGET_HAS_COMPLEX_TYPE) && \
+ defined(__SIZEOF_INT128__) && \
+ !defined(__clang__)
+
+typedef __int128_t i128;
+typedef _Complex __int128 ci128;
+
+static const ci128 val =
+ (((i128)0x01020304050607ull << 64) | 0x08090a0b0c0d0e0full) +
+ (((i128)0x10203040506070ull << 64) | 0x8090a0b0c0d0e0f0ull) * 1i;
+static const int dummy = 0xdeadbeef;
+
+#define D(X) int X __attribute__((unused))
+
+static ci128 f0(ci128 x)
+{
+ return x;
+}
+
+static ci128 f1(D(a), ci128 x)
+{
+ return x;
+}
+
+static ci128 f2(D(a), D(b), ci128 x)
+{
+ return x;
+}
+
+static ci128 f3(D(a), D(b), D(c), ci128 x)
+{
+ return x;
+}
+
+static ci128 f4(D(a), D(b), D(c), D(d), ci128 x)
+{
+ return x;
+}
+
+static ci128 f5(D(a), D(b), D(c), D(d), D(e), ci128 x)
+{
+ return x;
+}
+
+static ci128 f6(D(a), D(b), D(c), D(d), D(e), D(f), ci128 x)
+{
+ return x;
+}
+
+static ci128 f7(D(a), D(b), D(c), D(d), D(e), D(f), D(g), ci128 x)
+{
+ return x;
+}
+
+static ci128 f8(D(a), D(b), D(c), D(d), D(e), D(f), D(g), D(h), ci128 x)
+{
+ return x;
+}
+
+#define N 9
+
+static void * const funcs[N] = {
+ f0, f1, f2, f3, f4, f5, f6, f7, f8
+};
+
+static ffi_type ffi_type_ci128 = {
+ sizeof(ci128),
+ _Alignof(ci128),
+ FFI_TYPE_COMPLEX,
+ (ffi_type *[2]){ &ffi_type_sint128, NULL },
+};
+
+int main (void)
+{
+ int i;
+
+ for (i = 0; i < N; i++)
+ {
+ ffi_cif cif;
+ ffi_status s;
+ ffi_type *args[N];
+ void *values[N];
+ ci128 ret;
+ int j;
+
+ for (j = 0; j < i; j++)
+ {
+ args[j] = &ffi_type_sint;
+ values[j] = (void *)&dummy;
+ }
+ args[i] = &ffi_type_ci128;
+ values[i] = (void *)&val;
+
+ s = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, i + 1,
+ &ffi_type_ci128, args);
+ CHECK(s == FFI_OK);
+
+ ffi_call(&cif, FFI_FN(funcs[i]), &ret, values);
+ CHECK(ret == val);
+ }
+
+ return 0;
+}
+
+#else
+int main (void) { return 0; }
+#endif
diff --git a/deps/libffi/testsuite/libffi.go/go.exp b/deps/libffi/testsuite/libffi.go/go.exp
index 100c5e75b40915..f477e99ade6911 100644
--- a/deps/libffi/testsuite/libffi.go/go.exp
+++ b/deps/libffi/testsuite/libffi.go/go.exp
@@ -19,10 +19,23 @@ libffi-init
global srcdir subdir
+if { [string match $compiler_vendor "microsoft"] } {
+ # -wd4005 macro redefinition
+ # -wd4244 implicit conversion to type of smaller size
+ # -wd4305 truncation to smaller type
+ # -wd4477 printf %lu of uintptr_t
+ # -wd4312 implicit conversion to type of greater size
+ # -wd4311 pointer truncation to unsigned long
+ # -EHsc C++ Exception Handling (no SEH exceptions)
+ set additional_options "-wd4005 -wd4244 -wd4305 -wd4477 -wd4312 -wd4311 -EHsc";
+} else {
+ set additional_options "";
+}
+
set tlist [lsort [glob -nocomplain -- $srcdir/$subdir/*.{c,cc}]]
if { [libffi_feature_test "#ifdef FFI_GO_CLOSURES"] } {
- run-many-tests $tlist ""
+ run-many-tests $tlist $additional_options
} else {
foreach test $tlist {
unsupported "$test"
diff --git a/deps/ngtcp2/ngtcp2.gyp b/deps/ngtcp2/ngtcp2.gyp
index dd0a64d5852b1b..7ae62792485125 100644
--- a/deps/ngtcp2/ngtcp2.gyp
+++ b/deps/ngtcp2/ngtcp2.gyp
@@ -49,7 +49,7 @@
'ngtcp2/lib/ngtcp2_unreachable.c',
'ngtcp2/lib/ngtcp2_vec.c',
'ngtcp2/lib/ngtcp2_version.c',
- 'ngtcp2/lib/ngtcp2_window_filter.c',
+ 'ngtcp2/lib/ngtcp2_wf.c',
'ngtcp2/crypto/shared.c'
],
'ngtcp2_sources_ossl': [
diff --git a/deps/ngtcp2/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_ossl.h b/deps/ngtcp2/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_ossl.h
index 417ec017c60c13..06e8d42bda0100 100644
--- a/deps/ngtcp2/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_ossl.h
+++ b/deps/ngtcp2/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_ossl.h
@@ -145,6 +145,18 @@ NGTCP2_EXTERN SSL *ngtcp2_crypto_ossl_ctx_get_ssl(ngtcp2_crypto_ossl_ctx *ctx);
*/
NGTCP2_EXTERN int ngtcp2_crypto_ossl_init(void);
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ossl_free` frees the resources allocated by
+ * `ngtcp2_crypto_ossl_init`. It is safe to call this function even
+ * if `ngtcp2_crypto_ossl_init` fails or is not called at all. This
+ * function might be useful to make some leak detection tools happy.
+ *
+ * .. version-added:: 1.24.0
+ */
+NGTCP2_EXTERN void ngtcp2_crypto_ossl_free(void);
+
/**
* @function
*
diff --git a/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c b/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c
index 6159567aceb6af..3fedb8df8e8dc5 100644
--- a/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c
+++ b/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c
@@ -79,6 +79,60 @@ int ngtcp2_crypto_ossl_init(void) {
return 0;
}
+void ngtcp2_crypto_ossl_free(void) {
+ if (crypto_hkdf) {
+ EVP_KDF_free(crypto_hkdf);
+ crypto_hkdf = NULL;
+ }
+
+ if (crypto_sha384) {
+ EVP_MD_free(crypto_sha384);
+ crypto_sha384 = NULL;
+ }
+
+ if (crypto_sha256) {
+ EVP_MD_free(crypto_sha256);
+ crypto_sha256 = NULL;
+ }
+
+#ifndef NGTCP2_NO_CHACHA_POLY1305
+ if (crypto_chacha20) {
+ EVP_CIPHER_free(crypto_chacha20);
+ crypto_chacha20 = NULL;
+ }
+
+ if (crypto_chacha20_poly1305) {
+ EVP_CIPHER_free(crypto_chacha20_poly1305);
+ crypto_chacha20_poly1305 = NULL;
+ }
+#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */
+
+ if (crypto_aes_256_ecb) {
+ EVP_CIPHER_free(crypto_aes_256_ecb);
+ crypto_aes_256_ecb = NULL;
+ }
+
+ if (crypto_aes_128_ecb) {
+ EVP_CIPHER_free(crypto_aes_128_ecb);
+ crypto_aes_128_ecb = NULL;
+ }
+
+ if (crypto_aes_128_ccm) {
+ EVP_CIPHER_free(crypto_aes_128_ccm);
+ crypto_aes_128_ccm = NULL;
+ }
+
+ if (crypto_aes_256_gcm) {
+ EVP_CIPHER_free(crypto_aes_256_gcm);
+ crypto_aes_256_gcm = NULL;
+ }
+
+ if (crypto_aes_128_gcm) {
+ EVP_CIPHER_free(crypto_aes_128_gcm);
+ crypto_aes_128_gcm = NULL;
+ }
+}
+
static const EVP_CIPHER *crypto_aead_aes_128_gcm(void) {
if (crypto_aes_128_gcm) {
return crypto_aes_128_gcm;
diff --git a/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.cc b/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.cc
index 1a39177e32a6d1..ac0b6132ef0b1d 100644
--- a/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.cc
+++ b/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.cc
@@ -603,9 +603,9 @@ std::expected ProtoCodec::start_response(Stream *stream) {
nghttp3_pri pri;
if (auto rv =
- nghttp3_conn_get_stream_priority(httpconn_, &pri, stream->stream_id);
+ nghttp3_conn_get_stream_priority2(httpconn_, &pri, stream->stream_id);
rv != 0) {
- std::println(stderr, "nghttp3_conn_get_stream_priority: {}",
+ std::println(stderr, "nghttp3_conn_get_stream_priority2: {}",
nghttp3_strerror(rv));
return std::unexpected{Error::HTTP3};
}
diff --git a/deps/ngtcp2/ngtcp2/examples/tls_client_context_ossl.cc b/deps/ngtcp2/ngtcp2/examples/tls_client_context_ossl.cc
index d7f9f8f58c8abc..31dc95e158bec3 100644
--- a/deps/ngtcp2/ngtcp2/examples/tls_client_context_ossl.cc
+++ b/deps/ngtcp2/ngtcp2/examples/tls_client_context_ossl.cc
@@ -36,23 +36,16 @@
#include "client_base.h"
#include "template.h"
-namespace {
-auto _ = [] {
- if (ngtcp2_crypto_ossl_init() != 0) {
- assert(0);
- abort();
- }
-
- return 0;
-}();
-} // namespace
-
extern Config config;
+TLSClientContext::TLSClientContext() { ngtcp2_crypto_ossl_init(); }
+
TLSClientContext::~TLSClientContext() {
if (ssl_ctx_) {
SSL_CTX_free(ssl_ctx_);
}
+
+ ngtcp2_crypto_ossl_free();
}
SSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; }
diff --git a/deps/ngtcp2/ngtcp2/examples/tls_client_context_ossl.h b/deps/ngtcp2/ngtcp2/examples/tls_client_context_ossl.h
index fad71595f2579c..4036a6fef9aa13 100644
--- a/deps/ngtcp2/ngtcp2/examples/tls_client_context_ossl.h
+++ b/deps/ngtcp2/ngtcp2/examples/tls_client_context_ossl.h
@@ -37,7 +37,7 @@ using namespace ngtcp2;
class TLSClientContext {
public:
- TLSClientContext() = default;
+ TLSClientContext();
~TLSClientContext();
std::expected init(const char *private_key_file,
diff --git a/deps/ngtcp2/ngtcp2/examples/tls_server_context_ossl.cc b/deps/ngtcp2/ngtcp2/examples/tls_server_context_ossl.cc
index 054db5caaf00b1..bd4fe2971c15a6 100644
--- a/deps/ngtcp2/ngtcp2/examples/tls_server_context_ossl.cc
+++ b/deps/ngtcp2/ngtcp2/examples/tls_server_context_ossl.cc
@@ -37,23 +37,16 @@
#include "server_base.h"
#include "template.h"
-namespace {
-auto _ = [] {
- if (ngtcp2_crypto_ossl_init() != 0) {
- assert(0);
- abort();
- }
-
- return 0;
-}();
-} // namespace
-
extern Config config;
+TLSServerContext::TLSServerContext() { ngtcp2_crypto_ossl_init(); }
+
TLSServerContext::~TLSServerContext() {
if (ssl_ctx_) {
SSL_CTX_free(ssl_ctx_);
}
+
+ ngtcp2_crypto_ossl_free();
}
SSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; }
diff --git a/deps/ngtcp2/ngtcp2/examples/tls_server_context_ossl.h b/deps/ngtcp2/ngtcp2/examples/tls_server_context_ossl.h
index 619f1310fec093..cc1aa38301d6ec 100644
--- a/deps/ngtcp2/ngtcp2/examples/tls_server_context_ossl.h
+++ b/deps/ngtcp2/ngtcp2/examples/tls_server_context_ossl.h
@@ -37,7 +37,7 @@ using namespace ngtcp2;
class TLSServerContext {
public:
- TLSServerContext() = default;
+ TLSServerContext();
~TLSServerContext();
std::expected init(const char *private_key_file,
diff --git a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h
index 278b30ca07bf18..01f93e8c41ad3b 100644
--- a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h
+++ b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h
@@ -3485,6 +3485,24 @@ typedef int (*ngtcp2_stream_stop_sending)(ngtcp2_conn *conn, int64_t stream_id,
void *user_data,
void *stream_user_data);
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_recv_stop_sending` is invoked when a STOP_SENDING frame
+ * is received from a remote endpoint for a stream identified by
+ * |stream_id|. |app_error_code| is the application error code carried
+ * by the STOP_SENDING frame. This callback is called at most
+ * once per stream.
+ *
+ * The callback function must return 0 if it succeeds. Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_recv_stop_sending)(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t app_error_code,
+ void *user_data,
+ void *stream_user_data);
+
/**
* @functypedef
*
@@ -3626,7 +3644,8 @@ typedef int (*ngtcp2_get_path_challenge_data2)(ngtcp2_conn *conn,
#define NGTCP2_CALLBACKS_V1 1
#define NGTCP2_CALLBACKS_V2 2
#define NGTCP2_CALLBACKS_V3 3
-#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_V3
+#define NGTCP2_CALLBACKS_V4 4
+#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_V4
/**
* @struct
@@ -3964,6 +3983,16 @@ typedef struct ngtcp2_callbacks {
* .. version-added:: 1.22.0
*/
ngtcp2_get_path_challenge_data2 get_path_challenge_data2;
+ /* The following fields have been added since
+ NGTCP2_CALLBACKS_V3. */
+ /**
+ * :member:`recv_stop_sending` is a callback function which is invoked
+ * when a STOP_SENDING frame is received from a remote endpoint. This
+ * callback function is optional.
+ *
+ * .. version-added:: 1.24.0
+ */
+ ngtcp2_recv_stop_sending recv_stop_sending;
} ngtcp2_callbacks;
/**
diff --git a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h
index 7a47cdaba0bdb9..a71100abbaf586 100644
--- a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h
+++ b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h
@@ -36,7 +36,7 @@
*
* Version number of the ngtcp2 library release.
*/
-#define NGTCP2_VERSION "1.23.0"
+#define NGTCP2_VERSION "1.24.0"
/**
* @macro
@@ -46,6 +46,6 @@
* number, 8 bits for minor and 8 bits for patch. Version 1.2.3
* becomes 0x010203.
*/
-#define NGTCP2_VERSION_NUM 0x011700
+#define NGTCP2_VERSION_NUM 0x011800
#endif /* !defined(NGTCP2_VERSION_H) */
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c
index cdeb29bb506523..b4cf2b02bbf422 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c
@@ -289,9 +289,8 @@ static void bbr_handle_recovery(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
ngtcp2_tstamp initial_ts) {
- ngtcp2_window_filter_init(&bbr->max_bw_filter, NGTCP2_BBR_MAX_BW_FILTERLEN);
- ngtcp2_window_filter_init(&bbr->extra_acked_filter,
- NGTCP2_BBR_EXTRA_ACKED_FILTERLEN);
+ ngtcp2_wf_init(&bbr->max_bw_filter, NGTCP2_BBR_MAX_BW_FILTERLEN);
+ ngtcp2_wf_init(&bbr->extra_acked_filter, NGTCP2_BBR_EXTRA_ACKED_FILTERLEN);
bbr->min_rtt =
cstat->first_rtt_sample_ts == UINT64_MAX ? UINT64_MAX : cstat->smoothed_rtt;
@@ -590,10 +589,10 @@ static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat,
if (cstat->delivery_rate_sec && (cstat->delivery_rate_sec >= bbr->max_bw ||
!bbr->rst->rs.is_app_limited)) {
- ngtcp2_window_filter_update(&bbr->max_bw_filter, cstat->delivery_rate_sec,
- bbr->cycle_count);
+ ngtcp2_wf_update(&bbr->max_bw_filter, cstat->delivery_rate_sec,
+ bbr->cycle_count);
- bbr->max_bw = ngtcp2_window_filter_get_best(&bbr->max_bw_filter);
+ bbr->max_bw = ngtcp2_wf_get_best(&bbr->max_bw_filter);
}
}
@@ -667,15 +666,14 @@ static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr,
}
if (bbr->full_bw_reached) {
- bbr->extra_acked_filter.window_length = NGTCP2_BBR_EXTRA_ACKED_FILTERLEN;
+ bbr->extra_acked_filter.win = NGTCP2_BBR_EXTRA_ACKED_FILTERLEN;
} else {
- bbr->extra_acked_filter.window_length = 1;
+ bbr->extra_acked_filter.win = 1;
}
- ngtcp2_window_filter_update(&bbr->extra_acked_filter, extra,
- bbr->round_count);
+ ngtcp2_wf_update(&bbr->extra_acked_filter, extra, bbr->round_count);
- bbr->extra_acked = ngtcp2_window_filter_get_best(&bbr->extra_acked_filter);
+ bbr->extra_acked = ngtcp2_wf_get_best(&bbr->extra_acked_filter);
}
static void bbr_enter_drain(ngtcp2_cc_bbr *bbr) {
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h
index 81384e6d9f5cda..5a8b470b893f12 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h
@@ -32,7 +32,7 @@
#include
#include "ngtcp2_cc.h"
-#include "ngtcp2_window_filter.h"
+#include "ngtcp2_wf.h"
typedef struct ngtcp2_rst ngtcp2_rst;
typedef struct ngtcp2_pcg32 ngtcp2_pcg32;
@@ -67,9 +67,9 @@ typedef struct ngtcp2_cc_bbr {
/* max_bw_filter for tracking the maximum recent delivery rate
samples for estimating max_bw. */
- ngtcp2_window_filter max_bw_filter;
+ ngtcp2_wf max_bw_filter;
- ngtcp2_window_filter extra_acked_filter;
+ ngtcp2_wf extra_acked_filter;
ngtcp2_duration min_rtt;
ngtcp2_tstamp min_rtt_stamp;
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c
index 1d65d93d566944..cbd1d677275166 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c
@@ -63,6 +63,9 @@ size_t ngtcp2_callbackslen_version(int callbacks_version) {
switch (callbacks_version) {
case NGTCP2_CALLBACKS_VERSION:
return sizeof(callbacks);
+ case NGTCP2_CALLBACKS_V3:
+ return offsetof(ngtcp2_callbacks, get_path_challenge_data2) +
+ sizeof(callbacks.get_path_challenge_data2);
case NGTCP2_CALLBACKS_V2:
return offsetof(ngtcp2_callbacks, begin_path_validation) +
sizeof(callbacks.begin_path_validation);
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c
index 28bbde1233e1e4..4a295d2aab6ac2 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c
@@ -230,6 +230,24 @@ static int conn_call_stream_reset(ngtcp2_conn *conn, int64_t stream_id,
return 0;
}
+static int conn_call_recv_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t app_error_code,
+ void *stream_user_data) {
+ int rv;
+
+ if (!conn->callbacks.recv_stop_sending) {
+ return 0;
+ }
+
+ rv = conn->callbacks.recv_stop_sending(conn, stream_id, app_error_code,
+ conn->user_data, stream_user_data);
+ if (rv != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
static int conn_call_extend_max_local_streams_bidi(ngtcp2_conn *conn,
uint64_t max_streams) {
int rv;
@@ -2621,6 +2639,8 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest,
ngtcp2_unreachable();
}
}
+
+ conn->frame_counts.crypto += (size_t)datacnt;
} else {
left = ngtcp2_pkt_crypto_max_datalen(crypto_offset, left, left);
if (left == (size_t)-1) {
@@ -2644,6 +2664,8 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest,
if (rv != 0) {
ngtcp2_unreachable();
}
+
+ ++conn->frame_counts.crypto;
}
*pfrc = nfrc;
@@ -3859,6 +3881,8 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING |
NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING |
NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE;
+
+ ++conn->frame_counts.crypto;
}
}
@@ -4073,6 +4097,8 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING |
NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE;
+ ++conn->frame_counts.stream;
+
if (ngtcp2_strm_streamfrq_empty(strm)) {
ngtcp2_conn_tx_strmq_pop(conn);
continue;
@@ -4236,6 +4262,8 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING |
NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE;
+ ++conn->frame_counts.stream;
+
vmsg->stream.strm->tx.offset += ndatalen;
conn->tx.offset += ndatalen;
vmsg->stream.strm->flags |= NGTCP2_STRM_FLAG_ANY_SENT;
@@ -7932,6 +7960,12 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn,
ngtcp2_strm_set_app_error_code(strm, fr->app_error_code);
+ rv = conn_call_recv_stop_sending(conn, fr->stream_id, fr->app_error_code,
+ strm->stream_user_data);
+ if (rv != 0) {
+ return rv;
+ }
+
/* No RESET_STREAM is required if we have sent FIN and all data have
been acknowledged. */
if (!ngtcp2_strm_is_all_tx_data_fin_acked(strm) &&
@@ -12035,6 +12069,12 @@ ngtcp2_ssize ngtcp2_conn_write_stream_versioned(
stream_id, v, datacnt, ts);
}
+static int conn_no_app_data_written(const ngtcp2_conn *conn) {
+ const ngtcp2_frame_counts *counts = &conn->frame_counts;
+
+ return counts->crypto == 0 && counts->stream == 0;
+}
+
static ngtcp2_ssize
conn_write_vmsg_wrapper(ngtcp2_conn *conn, ngtcp2_path *path,
int pkt_info_version, ngtcp2_pkt_info *pi,
@@ -12053,14 +12093,9 @@ conn_write_vmsg_wrapper(ngtcp2_conn *conn, ngtcp2_path *path,
if (cstat->bytes_in_flight >= cstat->cwnd) {
conn->rst.is_cwnd_limited = 1;
- } else if ((cstat->cwnd >= cstat->ssthresh ||
- cstat->bytes_in_flight * 2 < cstat->cwnd) &&
- nwrite == 0 && conn_pacing_pkt_tx_allowed(conn, ts) &&
- (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED) &&
- /* Because NGTCP2_CONN_FLAG_AGGREGATE_PKTS is set after a
- packet is produced, if it is set, we are sure that we
- are not app-limited. */
- !(conn->flags & NGTCP2_CONN_FLAG_AGGREGATE_PKTS)) {
+ } else if (conn_pacing_pkt_tx_allowed(conn, ts) && !vmsg &&
+ conn_no_app_data_written(conn) &&
+ (!path || ngtcp2_path_eq(&conn->dcid.current.ps.path, path))) {
conn->rst.app_limited =
ngtcp2_max(conn->rst.delivered + cstat->bytes_in_flight, 1);
}
@@ -12238,8 +12273,12 @@ ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path,
origlen = destlen =
conn_shape_udp_payload(conn, &conn->dcid.current, destlen);
- if (!ppe_pending && pi) {
- pi->ecn = NGTCP2_ECN_NOT_ECT;
+ if (!ppe_pending) {
+ if (pi) {
+ pi->ecn = NGTCP2_ECN_NOT_ECT;
+ }
+
+ conn->frame_counts = (ngtcp2_frame_counts){0};
}
switch (conn->state) {
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h
index 548c296e8ae1d0..18af11727e0646 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h
@@ -308,6 +308,11 @@ typedef struct ngtcp2_early_transport_params {
uint64_t max_datagram_frame_size;
} ngtcp2_early_transport_params;
+typedef struct ngtcp2_frame_counts {
+ size_t crypto;
+ size_t stream;
+} ngtcp2_frame_counts;
+
ngtcp2_static_ringbuf_def(path_challenge, 4,
sizeof(ngtcp2_path_challenge_entry))
@@ -644,6 +649,7 @@ struct ngtcp2_conn {
confirmed. For server, it is confirmed when completed. */
ngtcp2_tstamp handshake_confirmed_ts;
ngtcp2_pcg32 pcg;
+ ngtcp2_frame_counts frame_counts;
void *user_data;
uint32_t client_chosen_version;
uint32_t negotiated_version;
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.h
index 86346f49295add..4ae71aa6daa6d3 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.h
@@ -31,8 +31,6 @@
#include
-#include "ngtcp2_window_filter.h"
-
typedef struct ngtcp2_rtb_entry ngtcp2_rtb_entry;
typedef struct ngtcp2_conn_stat ngtcp2_conn_stat;
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_wf.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_wf.c
new file mode 100644
index 00000000000000..1adfc9ffd9ac1d
--- /dev/null
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_wf.c
@@ -0,0 +1,81 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2026 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_wf.h"
+
+void ngtcp2_wf_init(ngtcp2_wf *wf, uint64_t win) {
+ *wf = (ngtcp2_wf){
+ .win = win,
+ };
+}
+
+void ngtcp2_wf_update(ngtcp2_wf *wf, uint64_t value, uint64_t ts) {
+ ngtcp2_wf_sample s = {
+ .value = value,
+ .ts = ts,
+ };
+
+ if (wf->samples[0].value <= value || ts - wf->samples[2].ts > wf->win) {
+ wf->samples[0] = wf->samples[1] = wf->samples[2] = s;
+
+ return;
+ }
+
+ if (wf->samples[1].value <= value) {
+ wf->samples[1] = wf->samples[2] = s;
+ } else if (wf->samples[2].value <= value) {
+ wf->samples[2] = s;
+ }
+
+ if (ts - wf->samples[1].ts > wf->win) {
+ wf->samples[0] = wf->samples[2];
+ wf->samples[1] = wf->samples[2] = s;
+
+ return;
+ }
+
+ if (ts - wf->samples[0].ts > wf->win) {
+ wf->samples[0] = wf->samples[1];
+ wf->samples[1] = wf->samples[2];
+ wf->samples[2] = s;
+
+ return;
+ }
+
+ if (wf->samples[0].value == wf->samples[1].value &&
+ ts - wf->samples[0].ts > wf->win / 4) {
+ wf->samples[1] = wf->samples[2] = s;
+
+ return;
+ }
+
+ if (wf->samples[1].value == wf->samples[2].value &&
+ ts - wf->samples[0].ts > wf->win / 2) {
+ wf->samples[2] = s;
+ }
+}
+
+uint64_t ngtcp2_wf_get_best(const ngtcp2_wf *wf) {
+ return wf->samples[0].value;
+}
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_wf.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_wf.h
new file mode 100644
index 00000000000000..72bdf28c865473
--- /dev/null
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_wf.h
@@ -0,0 +1,55 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2026 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_WF_H
+#define NGTCP2_WF_H
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include
+
+/*
+ * ngtcp2_wf implements Kathleen Nichols's windowed min/max tracking
+ * algorithm.
+ */
+
+typedef struct ngtcp2_wf_sample {
+ uint64_t value;
+ uint64_t ts;
+} ngtcp2_wf_sample;
+
+typedef struct ngtcp2_wf {
+ uint64_t win;
+ ngtcp2_wf_sample samples[3];
+} ngtcp2_wf;
+
+void ngtcp2_wf_init(ngtcp2_wf *wf, uint64_t win);
+
+void ngtcp2_wf_update(ngtcp2_wf *wf, uint64_t value, uint64_t ts);
+
+uint64_t ngtcp2_wf_get_best(const ngtcp2_wf *wf);
+
+#endif /* !defined(NGTCP2_WF_H) */
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_window_filter.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_window_filter.c
deleted file mode 100644
index 707cd570799e46..00000000000000
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_window_filter.c
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * ngtcp2
- *
- * Copyright (c) 2021 ngtcp2 contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-/*
- * Translated to C from the original C++ code
- * https://quiche.googlesource.com/quiche/+/5be974e29f7e71a196e726d6e2272676d33ab77d/quic/core/congestion_control/windowed_filter.h
- * with the following license:
- *
- * // Copyright (c) 2016 The Chromium Authors. All rights reserved.
- * // Use of this source code is governed by a BSD-style license that can be
- * // found in the LICENSE file.
- */
-#include "ngtcp2_window_filter.h"
-
-#include
-
-void ngtcp2_window_filter_init(ngtcp2_window_filter *wf,
- uint64_t window_length) {
- wf->window_length = window_length;
- memset(wf->estimates, 0xFF, sizeof(wf->estimates));
-}
-
-void ngtcp2_window_filter_update(ngtcp2_window_filter *wf, uint64_t new_sample,
- uint64_t new_time) {
- /* Reset all estimates if they have not yet been initialized, if new
- sample is a new best, or if the newest recorded estimate is too
- old. */
- if (wf->estimates[0].sample == UINT64_MAX ||
- new_sample > wf->estimates[0].sample ||
- new_time - wf->estimates[2].time > wf->window_length) {
- ngtcp2_window_filter_reset(wf, new_sample, new_time);
- return;
- }
-
- if (new_sample > wf->estimates[1].sample) {
- wf->estimates[1].sample = new_sample;
- wf->estimates[1].time = new_time;
- wf->estimates[2] = wf->estimates[1];
- } else if (new_sample > wf->estimates[2].sample) {
- wf->estimates[2].sample = new_sample;
- wf->estimates[2].time = new_time;
- }
-
- /* Expire and update estimates as necessary. */
- if (new_time - wf->estimates[0].time > wf->window_length) {
- /* The best estimate hasn't been updated for an entire window, so
- promote second and third best estimates. */
- wf->estimates[0] = wf->estimates[1];
- wf->estimates[1] = wf->estimates[2];
- wf->estimates[2].sample = new_sample;
- wf->estimates[2].time = new_time;
-
- /* Need to iterate one more time. Check if the new best estimate
- is outside the window as well, since it may also have been
- recorded a long time ago. Don't need to iterate once more
- since we cover that case at the beginning of the method. */
- if (new_time - wf->estimates[0].time > wf->window_length) {
- wf->estimates[0] = wf->estimates[1];
- wf->estimates[1] = wf->estimates[2];
- }
- return;
- }
-
- if (wf->estimates[1].sample == wf->estimates[0].sample &&
- new_time - wf->estimates[1].time > wf->window_length >> 2) {
- /* A quarter of the window has passed without a better sample, so
- the second-best estimate is taken from the second quarter of
- the window. */
- wf->estimates[2].sample = new_sample;
- wf->estimates[2].time = new_time;
- wf->estimates[1] = wf->estimates[2];
- return;
- }
-
- if (wf->estimates[2].sample == wf->estimates[1].sample &&
- new_time - wf->estimates[2].time > wf->window_length >> 1) {
- /* We've passed a half of the window without a better estimate, so
- take a third-best estimate from the second half of the
- window. */
- wf->estimates[2].sample = new_sample;
- wf->estimates[2].time = new_time;
- }
-}
-
-void ngtcp2_window_filter_reset(ngtcp2_window_filter *wf, uint64_t new_sample,
- uint64_t new_time) {
- wf->estimates[0].sample = new_sample;
- wf->estimates[0].time = new_time;
- wf->estimates[1] = wf->estimates[2] = wf->estimates[0];
-}
-
-uint64_t ngtcp2_window_filter_get_best(ngtcp2_window_filter *wf) {
- return wf->estimates[0].sample;
-}
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_window_filter.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_window_filter.h
deleted file mode 100644
index c90a9fdb9078da..00000000000000
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_window_filter.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * ngtcp2
- *
- * Copyright (c) 2021 ngtcp2 contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-/*
- * Translated to C from the original C++ code
- * https://quiche.googlesource.com/quiche/+/5be974e29f7e71a196e726d6e2272676d33ab77d/quic/core/congestion_control/windowed_filter.h
- * with the following license:
- *
- * // Copyright (c) 2016 The Chromium Authors. All rights reserved.
- * // Use of this source code is governed by a BSD-style license that can be
- * // found in the LICENSE file.
- */
-#ifndef NGTCP2_WINDOW_FILTER_H
-#define NGTCP2_WINDOW_FILTER_H
-
-#ifdef HAVE_CONFIG_H
-# include
-#endif /* defined(HAVE_CONFIG_H) */
-
-#include
-
-typedef struct ngtcp2_window_filter_sample {
- uint64_t sample;
- uint64_t time;
-} ngtcp2_window_filter_sample;
-
-typedef struct ngtcp2_window_filter {
- uint64_t window_length;
- ngtcp2_window_filter_sample estimates[3];
-} ngtcp2_window_filter;
-
-void ngtcp2_window_filter_init(ngtcp2_window_filter *wf,
- uint64_t window_length);
-
-void ngtcp2_window_filter_update(ngtcp2_window_filter *wf, uint64_t new_sample,
- uint64_t new_time);
-
-void ngtcp2_window_filter_reset(ngtcp2_window_filter *wf, uint64_t new_sample,
- uint64_t new_time);
-
-uint64_t ngtcp2_window_filter_get_best(ngtcp2_window_filter *wf);
-
-#endif /* !defined(NGTCP2_WINDOW_FILTER_H) */
diff --git a/deps/npm/docs/content/commands/npm-approve-scripts.md b/deps/npm/docs/content/commands/npm-approve-scripts.md
index a89a250a769dc1..c4d3c15a8149f2 100644
--- a/deps/npm/docs/content/commands/npm-approve-scripts.md
+++ b/deps/npm/docs/content/commands/npm-approve-scripts.md
@@ -59,6 +59,14 @@ the command cannot infer. Existing `false` entries always win;
`approve-scripts` will not silently re-allow a package you previously
denied.
+If a registry dependency has no `resolved` URL in your `package-lock.json`
+(for example, an older lockfile or one written with
+`omit-lockfile-registry-resolved`), npm cannot verify a trusted version for
+it and cannot pin it: a `pkg@1.2.3` entry never matches, so the package
+keeps appearing under `--allow-scripts-pending`. `approve-scripts` approves
+these by name (`pkg: true`) and warns when it does. To restore pinning,
+refresh the lockfile with `npm install`.
+
### Examples
```bash
diff --git a/deps/npm/docs/content/commands/npm-ci.md b/deps/npm/docs/content/commands/npm-ci.md
index bc460070459604..741caf5eae7026 100644
--- a/deps/npm/docs/content/commands/npm-ci.md
+++ b/deps/npm/docs/content/commands/npm-ci.md
@@ -74,8 +74,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -298,6 +306,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
diff --git a/deps/npm/docs/content/commands/npm-dedupe.md b/deps/npm/docs/content/commands/npm-dedupe.md
index 8186ee2c1f31c7..a48bf1bd622e5c 100644
--- a/deps/npm/docs/content/commands/npm-dedupe.md
+++ b/deps/npm/docs/content/commands/npm-dedupe.md
@@ -74,8 +74,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
diff --git a/deps/npm/docs/content/commands/npm-exec.md b/deps/npm/docs/content/commands/npm-exec.md
index 13a0939209a5ea..ff08d07786a8d3 100644
--- a/deps/npm/docs/content/commands/npm-exec.md
+++ b/deps/npm/docs/content/commands/npm-exec.md
@@ -194,6 +194,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
diff --git a/deps/npm/docs/content/commands/npm-find-dupes.md b/deps/npm/docs/content/commands/npm-find-dupes.md
index 52bb59b9f81b57..2d72186fe5a22e 100644
--- a/deps/npm/docs/content/commands/npm-find-dupes.md
+++ b/deps/npm/docs/content/commands/npm-find-dupes.md
@@ -25,8 +25,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
diff --git a/deps/npm/docs/content/commands/npm-install-ci-test.md b/deps/npm/docs/content/commands/npm-install-ci-test.md
index 4528f63dfe28e8..2194a4df84a80c 100644
--- a/deps/npm/docs/content/commands/npm-install-ci-test.md
+++ b/deps/npm/docs/content/commands/npm-install-ci-test.md
@@ -27,8 +27,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -251,6 +259,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
diff --git a/deps/npm/docs/content/commands/npm-install-scripts.md b/deps/npm/docs/content/commands/npm-install-scripts.md
new file mode 100644
index 00000000000000..4182392c9a3d1e
--- /dev/null
+++ b/deps/npm/docs/content/commands/npm-install-scripts.md
@@ -0,0 +1,166 @@
+---
+title: npm-install-scripts
+section: 1
+description: Manage install-script approvals for dependencies
+---
+
+### Synopsis
+
+```bash
+npm install-scripts approve [ ...]
+npm install-scripts approve --all
+npm install-scripts deny [ ...]
+npm install-scripts deny --all
+npm install-scripts ls
+npm install-scripts prune
+```
+
+Note: This command is unaware of workspaces.
+
+### Description
+
+Manages the `allowScripts` field in your project's `package.json`, which
+records which of your dependencies are permitted to run install scripts
+(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry
+sources). This is the recommended way to maintain that field.
+
+Dependency install scripts are blocked by default. Install commands
+silently skip lifecycle scripts for any dependency that does not have a
+matching entry in `allowScripts`, and end with a list of the packages
+whose scripts were skipped so you can review them here.
+
+This command only works inside a project that has a `package.json`. Running
+it with `--global` (`-g`) fails with an `EGLOBAL` error, since global
+installs (`npm install -g`) and one-off executions (`npm exec` / `npx`) have
+no project `package.json` to write to. To allow install scripts in those
+contexts, use the `--allow-scripts` flag at install time (for example
+`npm install -g --allow-scripts=canvas,sharp`) or persist the setting with
+`npm config set allow-scripts=canvas,sharp --location=user`.
+
+There are four subcommands:
+
+```bash
+npm install-scripts approve [ ...]
+npm install-scripts approve --all
+npm install-scripts deny [ ...]
+npm install-scripts deny --all
+npm install-scripts ls
+npm install-scripts prune
+```
+
+`approve` allows install scripts for the named packages. `` matches
+every installed version of that package. By default it writes pinned entries
+(`pkg@1.2.3`), which keep their approval narrowed to the specific version you
+reviewed. Pass `--no-allow-scripts-pin` to write name-only entries that allow
+any future version. `--all` approves every package with unreviewed install
+scripts in one go.
+
+`deny` records an explicit denial for the named packages (a name-only `false`
+entry), which survives `npm install-scripts approve --all` and excludes the
+package from any future blanket approval. `--all` denies every package with
+unreviewed install scripts.
+
+`ls` is read-only: it lists every package whose install scripts are not yet
+covered by `allowScripts`, without modifying `package.json`.
+
+`prune` removes `allowScripts` entries that no longer match an installed
+package with an install script, either because the package is no longer
+installed (a transitive dependency changed, or a pinned `pkg@1.2.3` was
+upgraded) or because it no longer has an install script. Both approvals
+(`true`) and denials (`false`) are removed. It edits only the `allowScripts`
+field in `package.json`, never `.npmrc` or `--allow-scripts`. Pass `--dry-run`
+to preview without writing. Unparseable keys are left alone.
+
+`approve` honours the asymmetric pin rule: if you re-approve a package whose
+installed version has changed, the existing pin is rewritten to track the new
+installed version. Multi-version statements (`pkg@1 || 2`) are left alone,
+since they likely capture intent that the command cannot infer. Existing
+`false` entries always win; `approve` will not silently re-allow a package you
+previously denied.
+
+The standalone commands [`npm approve-scripts`](/commands/npm-approve-scripts)
+and [`npm deny-scripts`](/commands/npm-deny-scripts) are aliases for
+`npm install-scripts approve` and `npm install-scripts deny`.
+
+### Examples
+
+```bash
+# Approve all currently-installed install scripts after reviewing them
+npm install-scripts approve --all
+
+# Approve specific packages, pinned to their installed version
+npm install-scripts approve canvas sharp
+
+# Deny a package so it stays blocked
+npm install-scripts deny telemetry-pkg
+
+# Preview which packages still need review
+npm install-scripts ls
+
+# Preview stale allowScripts entries, then remove them
+npm install-scripts prune --dry-run
+npm install-scripts prune
+```
+
+### Configuration
+
+#### `all`
+
+* Default: false
+* Type: Boolean
+
+Show or act on all packages, not just the ones your project directly depends
+on. For `npm outdated` and `npm ls` this lists every outdated or installed
+package. For `npm approve-scripts` and `npm deny-scripts` it selects every
+package with pending install scripts.
+
+
+
+#### `allow-scripts-pin`
+
+* Default: true
+* Type: Boolean
+
+Write pinned (`pkg@version`) entries when approving install scripts. Set to
+`false` to write name-only entries that allow any version. Has no effect on
+`npm deny-scripts`, which always writes name-only entries regardless of this
+setting.
+
+
+
+#### `dry-run`
+
+* Default: false
+* Type: Boolean
+
+Indicates that you don't want npm to make any changes and that it should
+only report what it would have done. This can be passed into any of the
+commands that modify your local installation, eg, `install`, `update`,
+`dedupe`, `uninstall`, as well as `pack` and `publish`.
+
+Note: This is NOT honored by other network related commands, eg `dist-tags`,
+`owner`, etc.
+
+
+
+#### `json`
+
+* Default: false
+* Type: Boolean
+
+Whether or not to output JSON data, rather than the normal output.
+
+* In `npm pkg set` it enables parsing set values with JSON.parse() before
+ saving them to your `package.json`.
+
+Not supported by all npm commands.
+
+
+
+### See Also
+
+* [npm approve-scripts](/commands/npm-approve-scripts)
+* [npm deny-scripts](/commands/npm-deny-scripts)
+* [npm install](/commands/npm-install)
+* [npm rebuild](/commands/npm-rebuild)
+* [package.json](/configuring-npm/package-json)
diff --git a/deps/npm/docs/content/commands/npm-install-test.md b/deps/npm/docs/content/commands/npm-install-test.md
index 44de058c56aa05..e13f79a51e6f18 100644
--- a/deps/npm/docs/content/commands/npm-install-test.md
+++ b/deps/npm/docs/content/commands/npm-install-test.md
@@ -68,8 +68,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -328,6 +336,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
@@ -375,6 +387,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -397,6 +413,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
diff --git a/deps/npm/docs/content/commands/npm-install.md b/deps/npm/docs/content/commands/npm-install.md
index 5bd36c1fa45320..98d69d5e842451 100644
--- a/deps/npm/docs/content/commands/npm-install.md
+++ b/deps/npm/docs/content/commands/npm-install.md
@@ -410,8 +410,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -670,6 +678,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
@@ -717,6 +729,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -739,6 +755,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
diff --git a/deps/npm/docs/content/commands/npm-link.md b/deps/npm/docs/content/commands/npm-link.md
index 37efda66408fbd..fa9626c4edd804 100644
--- a/deps/npm/docs/content/commands/npm-link.md
+++ b/deps/npm/docs/content/commands/npm-link.md
@@ -138,8 +138,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
diff --git a/deps/npm/docs/content/commands/npm-ls.md b/deps/npm/docs/content/commands/npm-ls.md
index ae8e64ada95551..fba77fd01cffaf 100644
--- a/deps/npm/docs/content/commands/npm-ls.md
+++ b/deps/npm/docs/content/commands/npm-ls.md
@@ -23,7 +23,7 @@ Note that nested packages will *also* show the paths to the specified packages.
For example, running `npm ls promzard` in npm's source tree will show:
```bash
-npm@11.17.0 /path/to/npm
+npm@11.18.0 /path/to/npm
└─┬ init-package-json@0.0.4
└── promzard@0.1.5
```
diff --git a/deps/npm/docs/content/commands/npm-outdated.md b/deps/npm/docs/content/commands/npm-outdated.md
index 1f368132c465a5..40f7e835a68a51 100644
--- a/deps/npm/docs/content/commands/npm-outdated.md
+++ b/deps/npm/docs/content/commands/npm-outdated.md
@@ -172,6 +172,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -194,6 +198,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
diff --git a/deps/npm/docs/content/commands/npm-query.md b/deps/npm/docs/content/commands/npm-query.md
index 83ea73188f7f10..4349e47ad23e06 100644
--- a/deps/npm/docs/content/commands/npm-query.md
+++ b/deps/npm/docs/content/commands/npm-query.md
@@ -284,6 +284,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -306,6 +310,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
diff --git a/deps/npm/docs/content/commands/npm-rebuild.md b/deps/npm/docs/content/commands/npm-rebuild.md
index 18b1d37c779956..c70307a2a7fe2d 100644
--- a/deps/npm/docs/content/commands/npm-rebuild.md
+++ b/deps/npm/docs/content/commands/npm-rebuild.md
@@ -136,6 +136,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
diff --git a/deps/npm/docs/content/commands/npm-update.md b/deps/npm/docs/content/commands/npm-update.md
index ee61e27dcda0e6..317f85f7d0dbd6 100644
--- a/deps/npm/docs/content/commands/npm-update.md
+++ b/deps/npm/docs/content/commands/npm-update.md
@@ -177,8 +177,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -338,6 +346,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
@@ -385,6 +397,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -407,6 +423,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
diff --git a/deps/npm/docs/content/commands/npm.md b/deps/npm/docs/content/commands/npm.md
index d924d23c0ece0e..088553c6ffc486 100644
--- a/deps/npm/docs/content/commands/npm.md
+++ b/deps/npm/docs/content/commands/npm.md
@@ -14,7 +14,7 @@ Note: This command is unaware of workspaces.
### Version
-11.17.0
+11.18.0
### Description
diff --git a/deps/npm/docs/content/using-npm/config.md b/deps/npm/docs/content/using-npm/config.md
index 6bbb68fdf56528..5d951493e8a1c1 100644
--- a/deps/npm/docs/content/using-npm/config.md
+++ b/deps/npm/docs/content/using-npm/config.md
@@ -349,6 +349,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -1029,8 +1033,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -1224,6 +1236,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -1620,7 +1638,14 @@ registry (https://registry.npmjs.org) to the configured registry. If set to
"never", then use the registry value. If set to "always", then replace the
registry host with the configured host every time.
-You may also specify a bare hostname (e.g., "registry.npmjs.org").
+You may also specify a bare hostname (e.g., "registry.npmjs.org") to only
+replace URLs coming from that host.
+
+You may also specify a full URL including a path (e.g.,
+"https://old-registry.example.com/npm/path"). In that case, resolved URLs
+whose host and path begin with that prefix will have the entire prefix
+replaced with the configured registry URL (host and path), without
+duplicating path segments.
@@ -1877,6 +1902,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `strict-peer-deps`
diff --git a/deps/npm/docs/content/using-npm/developers.md b/deps/npm/docs/content/using-npm/developers.md
index de0cb848c59ff4..b1fff0e3894f30 100644
--- a/deps/npm/docs/content/using-npm/developers.md
+++ b/deps/npm/docs/content/using-npm/developers.md
@@ -171,6 +171,24 @@ to install it locally into the node_modules folder in that other place.
Then go into the node-repl, and try using require("my-thing") to bring in your module's main module.
+#### Catching undeclared ("phantom") dependencies
+
+Under the default hoisted `node_modules` layout, your package can `import` a dependency it never declared and still resolve it.
+A transitive dependency hoisted alongside it, or your workspace root's `node_modules`, happens to satisfy the `import`.
+That undeclared ("phantom") dependency passes your own build silently, then fails for anyone who installs your package on its own.
+
+We recommend developing your package under [`install-strategy=linked`](/using-npm/config#install-strategy).
+The isolated layout only exposes a package's *declared* dependencies, so an `import` of an undeclared package fails for you during development instead of resolving by accident, shipping broken, and failing for your users:
+
+```bash
+npm install --install-strategy=linked
+npm test
+```
+
+> **Note:** This doesn't catch every case.
+> A dependency that's still satisfied at your build by a `devDependency` or by your workspace root's `node_modules` can resolve fine for you and still be missing for whoever installs your package.
+> So treat it as one check, not a guarantee, alongside auditing the dependencies your published package actually uses.
+
### Create a User Account
Create a user with the adduser command.
diff --git a/deps/npm/docs/output/commands/npm-access.html b/deps/npm/docs/output/commands/npm-access.html
index fd3ccdbdf886aa..13475a5d16f425 100644
--- a/deps/npm/docs/output/commands/npm-access.html
+++ b/deps/npm/docs/output/commands/npm-access.html
@@ -186,9 +186,9 @@
-
+
npm-access
- @11.17.0
+ @11.18.0
Set access level on published packages
diff --git a/deps/npm/docs/output/commands/npm-adduser.html b/deps/npm/docs/output/commands/npm-adduser.html
index 56679269f19860..e43659736fec33 100644
--- a/deps/npm/docs/output/commands/npm-adduser.html
+++ b/deps/npm/docs/output/commands/npm-adduser.html
@@ -186,9 +186,9 @@
-
+
npm-adduser
- @11.17.0
+ @11.18.0
Add a registry user account
diff --git a/deps/npm/docs/output/commands/npm-approve-scripts.html b/deps/npm/docs/output/commands/npm-approve-scripts.html
index 8d13f05fb30d4b..c3bc0172b5a4db 100644
--- a/deps/npm/docs/output/commands/npm-approve-scripts.html
+++ b/deps/npm/docs/output/commands/npm-approve-scripts.html
@@ -186,9 +186,9 @@
-
+
npm-approve-scripts
- @11.17.0
+ @11.18.0
Approve install scripts for specific dependencies
@@ -238,6 +238,13 @@ Description
the command cannot infer. Existing false entries always win;
approve-scripts will not silently re-allow a package you previously
denied.
+If a registry dependency has no resolved URL in your package-lock.json
+(for example, an older lockfile or one written with
+omit-lockfile-registry-resolved), npm cannot verify a trusted version for
+it and cannot pin it: a pkg@1.2.3 entry never matches, so the package
+keeps appearing under --allow-scripts-pending. approve-scripts approves
+these by name (pkg: true) and warns when it does. To restore pinning,
+refresh the lockfile with npm install.
Examples
# Approve all currently-installed install scripts after reviewing them
npm approve-scripts --all
diff --git a/deps/npm/docs/output/commands/npm-audit.html b/deps/npm/docs/output/commands/npm-audit.html
index e250ae85e00907..61042b6fbb07c0 100644
--- a/deps/npm/docs/output/commands/npm-audit.html
+++ b/deps/npm/docs/output/commands/npm-audit.html
@@ -186,9 +186,9 @@
-
+
npm-audit
- @11.17.0
+ @11.18.0
Run a security audit
diff --git a/deps/npm/docs/output/commands/npm-bugs.html b/deps/npm/docs/output/commands/npm-bugs.html
index 9d7cabb2eb07a9..660f011d1a0d9f 100644
--- a/deps/npm/docs/output/commands/npm-bugs.html
+++ b/deps/npm/docs/output/commands/npm-bugs.html
@@ -186,9 +186,9 @@
-
+
npm-bugs
- @11.17.0
+ @11.18.0
Report bugs for a package in a web browser
diff --git a/deps/npm/docs/output/commands/npm-cache.html b/deps/npm/docs/output/commands/npm-cache.html
index 49b091085717d2..553a38d06f42ab 100644
--- a/deps/npm/docs/output/commands/npm-cache.html
+++ b/deps/npm/docs/output/commands/npm-cache.html
@@ -186,9 +186,9 @@
-
+
npm-cache
- @11.17.0
+ @11.18.0
Manipulates packages cache
diff --git a/deps/npm/docs/output/commands/npm-ci.html b/deps/npm/docs/output/commands/npm-ci.html
index f01388a8be4ff3..3e61cba31beaa8 100644
--- a/deps/npm/docs/output/commands/npm-ci.html
+++ b/deps/npm/docs/output/commands/npm-ci.html
@@ -186,9 +186,9 @@
-
+
npm-ci
- @11.17.0
+ @11.18.0
Clean install a project
@@ -251,8 +251,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
@@ -419,6 +426,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
diff --git a/deps/npm/docs/output/commands/npm-completion.html b/deps/npm/docs/output/commands/npm-completion.html
index e0769eb5665c5a..d24da48ebb03ff 100644
--- a/deps/npm/docs/output/commands/npm-completion.html
+++ b/deps/npm/docs/output/commands/npm-completion.html
@@ -186,9 +186,9 @@
-
+
npm-completion
- @11.17.0
+ @11.18.0
Tab Completion for npm
diff --git a/deps/npm/docs/output/commands/npm-config.html b/deps/npm/docs/output/commands/npm-config.html
index 83b7a3b0eeab8c..a3c0dab2dee799 100644
--- a/deps/npm/docs/output/commands/npm-config.html
+++ b/deps/npm/docs/output/commands/npm-config.html
@@ -186,9 +186,9 @@
-
+
npm-config
- @11.17.0
+ @11.18.0
Manage the npm configuration files
diff --git a/deps/npm/docs/output/commands/npm-dedupe.html b/deps/npm/docs/output/commands/npm-dedupe.html
index 26065b3862e16c..fa8d4b7084379d 100644
--- a/deps/npm/docs/output/commands/npm-dedupe.html
+++ b/deps/npm/docs/output/commands/npm-dedupe.html
@@ -186,9 +186,9 @@
-
+
npm-dedupe
- @11.17.0
+ @11.18.0
Reduce duplication in the package tree
@@ -245,8 +245,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
diff --git a/deps/npm/docs/output/commands/npm-deny-scripts.html b/deps/npm/docs/output/commands/npm-deny-scripts.html
index 957193be87c446..4f7653c40173a6 100644
--- a/deps/npm/docs/output/commands/npm-deny-scripts.html
+++ b/deps/npm/docs/output/commands/npm-deny-scripts.html
@@ -186,9 +186,9 @@
-
+
npm-deny-scripts
- @11.17.0
+ @11.18.0
Deny install scripts for specific dependencies
diff --git a/deps/npm/docs/output/commands/npm-deprecate.html b/deps/npm/docs/output/commands/npm-deprecate.html
index b58d5388a985b9..30c4c912238480 100644
--- a/deps/npm/docs/output/commands/npm-deprecate.html
+++ b/deps/npm/docs/output/commands/npm-deprecate.html
@@ -186,9 +186,9 @@
-
+
npm-deprecate
- @11.17.0
+ @11.18.0
Deprecate a version of a package
diff --git a/deps/npm/docs/output/commands/npm-diff.html b/deps/npm/docs/output/commands/npm-diff.html
index a092efb1faf40c..6cf770360c1076 100644
--- a/deps/npm/docs/output/commands/npm-diff.html
+++ b/deps/npm/docs/output/commands/npm-diff.html
@@ -186,9 +186,9 @@
-
+
npm-diff
- @11.17.0
+ @11.18.0
The registry diff command
diff --git a/deps/npm/docs/output/commands/npm-dist-tag.html b/deps/npm/docs/output/commands/npm-dist-tag.html
index 2fc358fef37ca3..59985e4a43f316 100644
--- a/deps/npm/docs/output/commands/npm-dist-tag.html
+++ b/deps/npm/docs/output/commands/npm-dist-tag.html
@@ -186,9 +186,9 @@
-
+
npm-dist-tag
- @11.17.0
+ @11.18.0
Modify package distribution tags
diff --git a/deps/npm/docs/output/commands/npm-docs.html b/deps/npm/docs/output/commands/npm-docs.html
index 2eaade58d4d9ae..884f20d70d2f93 100644
--- a/deps/npm/docs/output/commands/npm-docs.html
+++ b/deps/npm/docs/output/commands/npm-docs.html
@@ -186,9 +186,9 @@
-
+
npm-docs
- @11.17.0
+ @11.18.0
Open documentation for a package in a web browser
diff --git a/deps/npm/docs/output/commands/npm-doctor.html b/deps/npm/docs/output/commands/npm-doctor.html
index e7a3771d5d654e..13bb25228dd28b 100644
--- a/deps/npm/docs/output/commands/npm-doctor.html
+++ b/deps/npm/docs/output/commands/npm-doctor.html
@@ -186,9 +186,9 @@
-
+
npm-doctor
- @11.17.0
+ @11.18.0
Check the health of your npm environment
diff --git a/deps/npm/docs/output/commands/npm-edit.html b/deps/npm/docs/output/commands/npm-edit.html
index 6f1ee44d54b0fe..6d1215ebfe1038 100644
--- a/deps/npm/docs/output/commands/npm-edit.html
+++ b/deps/npm/docs/output/commands/npm-edit.html
@@ -186,9 +186,9 @@
-
+
npm-edit
- @11.17.0
+ @11.18.0
Edit an installed package
diff --git a/deps/npm/docs/output/commands/npm-exec.html b/deps/npm/docs/output/commands/npm-exec.html
index d64cf7d587da27..3f57293a17fc56 100644
--- a/deps/npm/docs/output/commands/npm-exec.html
+++ b/deps/npm/docs/output/commands/npm-exec.html
@@ -186,9 +186,9 @@
-
+
npm-exec
- @11.17.0
+ @11.18.0
Run a command from a local or remote npm package
@@ -336,6 +336,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
diff --git a/deps/npm/docs/output/commands/npm-explain.html b/deps/npm/docs/output/commands/npm-explain.html
index 4c61e4dc5fbf14..5d5f6cea5ce113 100644
--- a/deps/npm/docs/output/commands/npm-explain.html
+++ b/deps/npm/docs/output/commands/npm-explain.html
@@ -186,9 +186,9 @@
-
+
npm-explain
- @11.17.0
+ @11.18.0
Explain installed packages
diff --git a/deps/npm/docs/output/commands/npm-explore.html b/deps/npm/docs/output/commands/npm-explore.html
index 68f61b31ed7d1b..d1ecb4e81886a8 100644
--- a/deps/npm/docs/output/commands/npm-explore.html
+++ b/deps/npm/docs/output/commands/npm-explore.html
@@ -186,9 +186,9 @@
-
+
npm-explore
- @11.17.0
+ @11.18.0
Browse an installed package
diff --git a/deps/npm/docs/output/commands/npm-find-dupes.html b/deps/npm/docs/output/commands/npm-find-dupes.html
index c6dea14d12f997..28a9066398760c 100644
--- a/deps/npm/docs/output/commands/npm-find-dupes.html
+++ b/deps/npm/docs/output/commands/npm-find-dupes.html
@@ -186,9 +186,9 @@
-
+
npm-find-dupes
- @11.17.0
+ @11.18.0
Find duplication in the package tree
@@ -213,8 +213,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
diff --git a/deps/npm/docs/output/commands/npm-fund.html b/deps/npm/docs/output/commands/npm-fund.html
index 0b7602251f020f..26761cc90196c8 100644
--- a/deps/npm/docs/output/commands/npm-fund.html
+++ b/deps/npm/docs/output/commands/npm-fund.html
@@ -186,9 +186,9 @@
-
+
npm-fund
- @11.17.0
+ @11.18.0
Retrieve funding information
diff --git a/deps/npm/docs/output/commands/npm-get.html b/deps/npm/docs/output/commands/npm-get.html
index e81310c39b33c0..e8fe5dfc0373ea 100644
--- a/deps/npm/docs/output/commands/npm-get.html
+++ b/deps/npm/docs/output/commands/npm-get.html
@@ -186,9 +186,9 @@
-
+
npm-get
- @11.17.0
+ @11.18.0
Get a value from the npm configuration
diff --git a/deps/npm/docs/output/commands/npm-help-search.html b/deps/npm/docs/output/commands/npm-help-search.html
index b44eddadf701b0..24f4ddafaea589 100644
--- a/deps/npm/docs/output/commands/npm-help-search.html
+++ b/deps/npm/docs/output/commands/npm-help-search.html
@@ -186,9 +186,9 @@
-
+
npm-help-search
- @11.17.0
+ @11.18.0
Search npm help documentation
diff --git a/deps/npm/docs/output/commands/npm-help.html b/deps/npm/docs/output/commands/npm-help.html
index d22edb7c826b09..4fb2ef4be76c30 100644
--- a/deps/npm/docs/output/commands/npm-help.html
+++ b/deps/npm/docs/output/commands/npm-help.html
@@ -186,9 +186,9 @@
-
+
npm-help
- @11.17.0
+ @11.18.0
Get help on npm
diff --git a/deps/npm/docs/output/commands/npm-init.html b/deps/npm/docs/output/commands/npm-init.html
index 836969ed135304..deeaf25a2348a0 100644
--- a/deps/npm/docs/output/commands/npm-init.html
+++ b/deps/npm/docs/output/commands/npm-init.html
@@ -186,9 +186,9 @@
-
+
npm-init
- @11.17.0
+ @11.18.0
Create a package.json file
diff --git a/deps/npm/docs/output/commands/npm-install-ci-test.html b/deps/npm/docs/output/commands/npm-install-ci-test.html
index 6f633c2c18a02d..2227da94885950 100644
--- a/deps/npm/docs/output/commands/npm-install-ci-test.html
+++ b/deps/npm/docs/output/commands/npm-install-ci-test.html
@@ -186,9 +186,9 @@
-
+
npm-install-ci-test
- @11.17.0
+ @11.18.0
Install a project with a clean slate and run tests
@@ -215,8 +215,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
@@ -383,6 +390,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
diff --git a/deps/npm/docs/output/commands/npm-install-scripts.html b/deps/npm/docs/output/commands/npm-install-scripts.html
new file mode 100644
index 00000000000000..1b036581f6a36e
--- /dev/null
+++ b/deps/npm/docs/output/commands/npm-install-scripts.html
@@ -0,0 +1,341 @@
+
+
+npm-install-scripts
+
+
+
+
+
+
+
+
+
+
+
+npm command-line interface
+
+
+
+
+
+
+
+ npm-install-scripts
+ @11.18.0
+
+Manage install-script approvals for dependencies
+
+
+
+
+Synopsis
+
npm install-scripts approve <pkg> [<pkg> ...]
+npm install-scripts approve --all
+npm install-scripts deny <pkg> [<pkg> ...]
+npm install-scripts deny --all
+npm install-scripts ls
+npm install-scripts prune
+
+
Note: This command is unaware of workspaces.
+
Description
+
Manages the allowScripts field in your project's package.json, which
+records which of your dependencies are permitted to run install scripts
+(preinstall, install, postinstall, and prepare for non-registry
+sources). This is the recommended way to maintain that field.
+
Dependency install scripts are blocked by default. Install commands
+silently skip lifecycle scripts for any dependency that does not have a
+matching entry in allowScripts, and end with a list of the packages
+whose scripts were skipped so you can review them here.
+
This command only works inside a project that has a package.json. Running
+it with --global (-g) fails with an EGLOBAL error, since global
+installs (npm install -g) and one-off executions (npm exec / npx) have
+no project package.json to write to. To allow install scripts in those
+contexts, use the --allow-scripts flag at install time (for example
+npm install -g --allow-scripts=canvas,sharp) or persist the setting with
+npm config set allow-scripts=canvas,sharp --location=user.
+
There are four subcommands:
+
npm install-scripts approve <pkg> [<pkg> ...]
+npm install-scripts approve --all
+npm install-scripts deny <pkg> [<pkg> ...]
+npm install-scripts deny --all
+npm install-scripts ls
+npm install-scripts prune
+
+
approve allows install scripts for the named packages. <pkg> matches
+every installed version of that package. By default it writes pinned entries
+(pkg@1.2.3), which keep their approval narrowed to the specific version you
+reviewed. Pass --no-allow-scripts-pin to write name-only entries that allow
+any future version. --all approves every package with unreviewed install
+scripts in one go.
+
deny records an explicit denial for the named packages (a name-only false
+entry), which survives npm install-scripts approve --all and excludes the
+package from any future blanket approval. --all denies every package with
+unreviewed install scripts.
+
ls is read-only: it lists every package whose install scripts are not yet
+covered by allowScripts, without modifying package.json.
+
prune removes allowScripts entries that no longer match an installed
+package with an install script, either because the package is no longer
+installed (a transitive dependency changed, or a pinned pkg@1.2.3 was
+upgraded) or because it no longer has an install script. Both approvals
+(true) and denials (false) are removed. It edits only the allowScripts
+field in package.json, never .npmrc or --allow-scripts. Pass --dry-run
+to preview without writing. Unparseable keys are left alone.
+
approve honours the asymmetric pin rule: if you re-approve a package whose
+installed version has changed, the existing pin is rewritten to track the new
+installed version. Multi-version statements (pkg@1 || 2) are left alone,
+since they likely capture intent that the command cannot infer. Existing
+false entries always win; approve will not silently re-allow a package you
+previously denied.
+
The standalone commands npm approve-scripts
+and npm deny-scripts are aliases for
+npm install-scripts approve and npm install-scripts deny.
+
Examples
+
# Approve all currently-installed install scripts after reviewing them
+npm install-scripts approve --all
+
+# Approve specific packages, pinned to their installed version
+npm install-scripts approve canvas sharp
+
+# Deny a package so it stays blocked
+npm install-scripts deny telemetry-pkg
+
+# Preview which packages still need review
+npm install-scripts ls
+
+# Preview stale allowScripts entries, then remove them
+npm install-scripts prune --dry-run
+npm install-scripts prune
+
+
Configuration
+
all
+
+Default: false
+Type: Boolean
+
+
Show or act on all packages, not just the ones your project directly depends
+on. For npm outdated and npm ls this lists every outdated or installed
+package. For npm approve-scripts and npm deny-scripts it selects every
+package with pending install scripts.
+
allow-scripts-pin
+
+Default: true
+Type: Boolean
+
+
Write pinned (pkg@version) entries when approving install scripts. Set to
+false to write name-only entries that allow any version. Has no effect on
+npm deny-scripts, which always writes name-only entries regardless of this
+setting.
+
dry-run
+
+Default: false
+Type: Boolean
+
+
Indicates that you don't want npm to make any changes and that it should
+only report what it would have done. This can be passed into any of the
+commands that modify your local installation, eg, install, update,
+dedupe, uninstall, as well as pack and publish.
+
Note: This is NOT honored by other network related commands, eg dist-tags,
+owner, etc.
+
json
+
+Default: false
+Type: Boolean
+
+
Whether or not to output JSON data, rather than the normal output.
+
+In npm pkg set it enables parsing set values with JSON.parse() before
+saving them to your package.json.
+
+
Not supported by all npm commands.
+
See Also
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deps/npm/docs/output/commands/npm-install-test.html b/deps/npm/docs/output/commands/npm-install-test.html
index bd12094e8ca1c6..94231076345ad4 100644
--- a/deps/npm/docs/output/commands/npm-install-test.html
+++ b/deps/npm/docs/output/commands/npm-install-test.html
@@ -186,9 +186,9 @@
-
+
npm-install-test
- @11.17.0
+ @11.18.0
Install package(s) and run tests
@@ -246,8 +246,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
@@ -439,6 +446,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
@@ -475,6 +485,8 @@ before
sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with min-release-age, when this cutoff blocks a fix that npm audit fix would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
Packages whose names match min-release-age-exclude are exempt from this
filter.
min-release-age
@@ -492,6 +504,11 @@ min-release-age
--before while preparing a git: or github: dependency); when both
apply, before wins within a single source and across sources the standard
precedence rules apply.
+When this window stops npm audit fix from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+min-release-age-exclude, or relax min-release-age or before.
Packages whose names match min-release-age-exclude are exempt from this
filter.
This value is not exported to the environment for child processes.
diff --git a/deps/npm/docs/output/commands/npm-install.html b/deps/npm/docs/output/commands/npm-install.html
index 767a65f28bbd2c..fd6fe12eb8cd6d 100644
--- a/deps/npm/docs/output/commands/npm-install.html
+++ b/deps/npm/docs/output/commands/npm-install.html
@@ -186,9 +186,9 @@
-
+
npm-install
- @11.17.0
+ @11.18.0
Install a package
@@ -521,8 +521,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
@@ -714,6 +721,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
@@ -750,6 +760,8 @@ before
sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with min-release-age, when this cutoff blocks a fix that npm audit fix would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
Packages whose names match min-release-age-exclude are exempt from this
filter.
min-release-age
@@ -767,6 +779,11 @@ min-release-age
--before while preparing a git: or github: dependency); when both
apply, before wins within a single source and across sources the standard
precedence rules apply.
+When this window stops npm audit fix from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+min-release-age-exclude, or relax min-release-age or before.
Packages whose names match min-release-age-exclude are exempt from this
filter.
This value is not exported to the environment for child processes.
diff --git a/deps/npm/docs/output/commands/npm-link.html b/deps/npm/docs/output/commands/npm-link.html
index 42fd82304ecf17..e4c9f092f37c09 100644
--- a/deps/npm/docs/output/commands/npm-link.html
+++ b/deps/npm/docs/output/commands/npm-link.html
@@ -186,9 +186,9 @@
-
+
npm-link
- @11.17.0
+ @11.18.0
Symlink a package folder
@@ -288,8 +288,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
diff --git a/deps/npm/docs/output/commands/npm-ll.html b/deps/npm/docs/output/commands/npm-ll.html
index e288cc8a65d634..a7e3061ddf98bf 100644
--- a/deps/npm/docs/output/commands/npm-ll.html
+++ b/deps/npm/docs/output/commands/npm-ll.html
@@ -186,9 +186,9 @@
-
+
npm-ll
- @11.17.0
+ @11.18.0
List installed packages
diff --git a/deps/npm/docs/output/commands/npm-login.html b/deps/npm/docs/output/commands/npm-login.html
index 7ead867b8d396c..b08ac7b3357030 100644
--- a/deps/npm/docs/output/commands/npm-login.html
+++ b/deps/npm/docs/output/commands/npm-login.html
@@ -186,9 +186,9 @@
-
+
npm-login
- @11.17.0
+ @11.18.0
Login to a registry user account
diff --git a/deps/npm/docs/output/commands/npm-logout.html b/deps/npm/docs/output/commands/npm-logout.html
index 19ecf24f01efad..dfc1b256538f45 100644
--- a/deps/npm/docs/output/commands/npm-logout.html
+++ b/deps/npm/docs/output/commands/npm-logout.html
@@ -186,9 +186,9 @@
-
+
npm-logout
- @11.17.0
+ @11.18.0
Log out of the registry
diff --git a/deps/npm/docs/output/commands/npm-ls.html b/deps/npm/docs/output/commands/npm-ls.html
index 181541d978ea07..a3de66d0323c8e 100644
--- a/deps/npm/docs/output/commands/npm-ls.html
+++ b/deps/npm/docs/output/commands/npm-ls.html
@@ -186,9 +186,9 @@
-
+
npm-ls
- @11.17.0
+ @11.18.0
List installed packages
@@ -209,7 +209,7 @@ Description
Positional arguments are name@version-range identifiers, which will limit the results to only the paths to the packages named.
Note that nested packages will also show the paths to the specified packages.
For example, running npm ls promzard in npm's source tree will show:
-npm@11.17.0 /path/to/npm
+npm@11.18.0 /path/to/npm
└─┬ init-package-json@0.0.4
└── promzard@0.1.5
diff --git a/deps/npm/docs/output/commands/npm-org.html b/deps/npm/docs/output/commands/npm-org.html
index 361f44be6f3040..7c0526d750c62f 100644
--- a/deps/npm/docs/output/commands/npm-org.html
+++ b/deps/npm/docs/output/commands/npm-org.html
@@ -186,9 +186,9 @@
-
+
npm-org
- @11.17.0
+ @11.18.0
Manage orgs
diff --git a/deps/npm/docs/output/commands/npm-outdated.html b/deps/npm/docs/output/commands/npm-outdated.html
index 9703cf4bacf71f..5bf8ee630c262c 100644
--- a/deps/npm/docs/output/commands/npm-outdated.html
+++ b/deps/npm/docs/output/commands/npm-outdated.html
@@ -186,9 +186,9 @@
-
+
npm-outdated
- @11.17.0
+ @11.18.0
Check for outdated packages
@@ -334,6 +334,8 @@ before
sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with min-release-age, when this cutoff blocks a fix that npm audit fix would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
Packages whose names match min-release-age-exclude are exempt from this
filter.
min-release-age
@@ -351,6 +353,11 @@ min-release-age
--before while preparing a git: or github: dependency); when both
apply, before wins within a single source and across sources the standard
precedence rules apply.
+When this window stops npm audit fix from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+min-release-age-exclude, or relax min-release-age or before.
Packages whose names match min-release-age-exclude are exempt from this
filter.
This value is not exported to the environment for child processes.
diff --git a/deps/npm/docs/output/commands/npm-owner.html b/deps/npm/docs/output/commands/npm-owner.html
index 649e7408130966..c4947a01ae92a3 100644
--- a/deps/npm/docs/output/commands/npm-owner.html
+++ b/deps/npm/docs/output/commands/npm-owner.html
@@ -186,9 +186,9 @@
-
+
npm-owner
- @11.17.0
+ @11.18.0
Manage package owners
diff --git a/deps/npm/docs/output/commands/npm-pack.html b/deps/npm/docs/output/commands/npm-pack.html
index ab45154d21a3e2..a875a527f81f37 100644
--- a/deps/npm/docs/output/commands/npm-pack.html
+++ b/deps/npm/docs/output/commands/npm-pack.html
@@ -186,9 +186,9 @@
-
+
npm-pack
- @11.17.0
+ @11.18.0
Create a tarball from a package
diff --git a/deps/npm/docs/output/commands/npm-ping.html b/deps/npm/docs/output/commands/npm-ping.html
index 2f2ad0f779fa0a..74a7e2adc927e4 100644
--- a/deps/npm/docs/output/commands/npm-ping.html
+++ b/deps/npm/docs/output/commands/npm-ping.html
@@ -186,9 +186,9 @@
-
+
npm-ping
- @11.17.0
+ @11.18.0
Ping npm registry
diff --git a/deps/npm/docs/output/commands/npm-pkg.html b/deps/npm/docs/output/commands/npm-pkg.html
index 7462527a590499..9aec2f5f560235 100644
--- a/deps/npm/docs/output/commands/npm-pkg.html
+++ b/deps/npm/docs/output/commands/npm-pkg.html
@@ -186,9 +186,9 @@
-
+
npm-pkg
- @11.17.0
+ @11.18.0
Manages your package.json
diff --git a/deps/npm/docs/output/commands/npm-prefix.html b/deps/npm/docs/output/commands/npm-prefix.html
index 3fe3a77f2baf7c..887a4f26432c3c 100644
--- a/deps/npm/docs/output/commands/npm-prefix.html
+++ b/deps/npm/docs/output/commands/npm-prefix.html
@@ -186,9 +186,9 @@
-
+
npm-prefix
- @11.17.0
+ @11.18.0
Display prefix
diff --git a/deps/npm/docs/output/commands/npm-profile.html b/deps/npm/docs/output/commands/npm-profile.html
index 9a03fa2579618f..c45be8a6052c7f 100644
--- a/deps/npm/docs/output/commands/npm-profile.html
+++ b/deps/npm/docs/output/commands/npm-profile.html
@@ -186,9 +186,9 @@
-
+
npm-profile
- @11.17.0
+ @11.18.0
Change settings on your registry profile
diff --git a/deps/npm/docs/output/commands/npm-prune.html b/deps/npm/docs/output/commands/npm-prune.html
index 539533b3e9a824..bfb3914265bd60 100644
--- a/deps/npm/docs/output/commands/npm-prune.html
+++ b/deps/npm/docs/output/commands/npm-prune.html
@@ -186,9 +186,9 @@
-
+
npm-prune
- @11.17.0
+ @11.18.0
Remove extraneous packages
diff --git a/deps/npm/docs/output/commands/npm-publish.html b/deps/npm/docs/output/commands/npm-publish.html
index 4200a9105d2376..f2a7ba05905630 100644
--- a/deps/npm/docs/output/commands/npm-publish.html
+++ b/deps/npm/docs/output/commands/npm-publish.html
@@ -186,9 +186,9 @@
-
+
npm-publish
- @11.17.0
+ @11.18.0
Publish a package
diff --git a/deps/npm/docs/output/commands/npm-query.html b/deps/npm/docs/output/commands/npm-query.html
index 862a6a6ad62f9d..7d9bf80b88d216 100644
--- a/deps/npm/docs/output/commands/npm-query.html
+++ b/deps/npm/docs/output/commands/npm-query.html
@@ -186,9 +186,9 @@
-
+
npm-query
- @11.17.0
+ @11.18.0
Dependency selector query
@@ -432,6 +432,8 @@ before
sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with min-release-age, when this cutoff blocks a fix that npm audit fix would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
Packages whose names match min-release-age-exclude are exempt from this
filter.
min-release-age
@@ -449,6 +451,11 @@ min-release-age
--before while preparing a git: or github: dependency); when both
apply, before wins within a single source and across sources the standard
precedence rules apply.
+When this window stops npm audit fix from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+min-release-age-exclude, or relax min-release-age or before.
Packages whose names match min-release-age-exclude are exempt from this
filter.
This value is not exported to the environment for child processes.
diff --git a/deps/npm/docs/output/commands/npm-rebuild.html b/deps/npm/docs/output/commands/npm-rebuild.html
index 822091ab35b0ed..5245b156b685ce 100644
--- a/deps/npm/docs/output/commands/npm-rebuild.html
+++ b/deps/npm/docs/output/commands/npm-rebuild.html
@@ -186,9 +186,9 @@
-
+
npm-rebuild
- @11.17.0
+ @11.18.0
Rebuild a package
@@ -298,6 +298,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
diff --git a/deps/npm/docs/output/commands/npm-repo.html b/deps/npm/docs/output/commands/npm-repo.html
index c18499bd35e937..c210a4efd30802 100644
--- a/deps/npm/docs/output/commands/npm-repo.html
+++ b/deps/npm/docs/output/commands/npm-repo.html
@@ -186,9 +186,9 @@
-
+
npm-repo
- @11.17.0
+ @11.18.0
Open package repository page in the browser
diff --git a/deps/npm/docs/output/commands/npm-restart.html b/deps/npm/docs/output/commands/npm-restart.html
index e9be582f21a6b0..cc0ee51c458eee 100644
--- a/deps/npm/docs/output/commands/npm-restart.html
+++ b/deps/npm/docs/output/commands/npm-restart.html
@@ -186,9 +186,9 @@
-
+
npm-restart
- @11.17.0
+ @11.18.0
Restart a package
diff --git a/deps/npm/docs/output/commands/npm-root.html b/deps/npm/docs/output/commands/npm-root.html
index 2aaae514e72d49..6f0af8585373f7 100644
--- a/deps/npm/docs/output/commands/npm-root.html
+++ b/deps/npm/docs/output/commands/npm-root.html
@@ -186,9 +186,9 @@
-
+
npm-root
- @11.17.0
+ @11.18.0
Display npm root
diff --git a/deps/npm/docs/output/commands/npm-run.html b/deps/npm/docs/output/commands/npm-run.html
index 4036f6e0153b30..c7119fea964e7f 100644
--- a/deps/npm/docs/output/commands/npm-run.html
+++ b/deps/npm/docs/output/commands/npm-run.html
@@ -186,9 +186,9 @@
-
+
npm-run
- @11.17.0
+ @11.18.0
Run arbitrary package scripts
diff --git a/deps/npm/docs/output/commands/npm-sbom.html b/deps/npm/docs/output/commands/npm-sbom.html
index be951feb19036c..70eb4780c5c9e5 100644
--- a/deps/npm/docs/output/commands/npm-sbom.html
+++ b/deps/npm/docs/output/commands/npm-sbom.html
@@ -186,9 +186,9 @@
-
+
npm-sbom
- @11.17.0
+ @11.18.0
Generate a Software Bill of Materials (SBOM)
diff --git a/deps/npm/docs/output/commands/npm-search.html b/deps/npm/docs/output/commands/npm-search.html
index 8b397932b4bd7c..b417f9ba815c2b 100644
--- a/deps/npm/docs/output/commands/npm-search.html
+++ b/deps/npm/docs/output/commands/npm-search.html
@@ -186,9 +186,9 @@
-
+
npm-search
- @11.17.0
+ @11.18.0
Search for packages
diff --git a/deps/npm/docs/output/commands/npm-set.html b/deps/npm/docs/output/commands/npm-set.html
index 4be22387a40786..dfd53bcf72f287 100644
--- a/deps/npm/docs/output/commands/npm-set.html
+++ b/deps/npm/docs/output/commands/npm-set.html
@@ -186,9 +186,9 @@
-
+
npm-set
- @11.17.0
+ @11.18.0
Set a value in the npm configuration
diff --git a/deps/npm/docs/output/commands/npm-shrinkwrap.html b/deps/npm/docs/output/commands/npm-shrinkwrap.html
index 83e1f76e657287..8989040dd9f6c5 100644
--- a/deps/npm/docs/output/commands/npm-shrinkwrap.html
+++ b/deps/npm/docs/output/commands/npm-shrinkwrap.html
@@ -186,9 +186,9 @@
-
+
npm-shrinkwrap
- @11.17.0
+ @11.18.0
Lock down dependency versions for publication
diff --git a/deps/npm/docs/output/commands/npm-stage.html b/deps/npm/docs/output/commands/npm-stage.html
index 7a72369ac86e7a..de76caf167e94b 100644
--- a/deps/npm/docs/output/commands/npm-stage.html
+++ b/deps/npm/docs/output/commands/npm-stage.html
@@ -186,9 +186,9 @@
-
+
npm-stage
- @11.17.0
+ @11.18.0
Stage packages for publishing
diff --git a/deps/npm/docs/output/commands/npm-star.html b/deps/npm/docs/output/commands/npm-star.html
index a58d3d91913c47..0642cfefb4bc83 100644
--- a/deps/npm/docs/output/commands/npm-star.html
+++ b/deps/npm/docs/output/commands/npm-star.html
@@ -186,9 +186,9 @@
-
+
npm-star
- @11.17.0
+ @11.18.0
Mark your favorite packages
diff --git a/deps/npm/docs/output/commands/npm-stars.html b/deps/npm/docs/output/commands/npm-stars.html
index b13d859bffac06..3f586b4fb0a84a 100644
--- a/deps/npm/docs/output/commands/npm-stars.html
+++ b/deps/npm/docs/output/commands/npm-stars.html
@@ -186,9 +186,9 @@
-
+
npm-stars
- @11.17.0
+ @11.18.0
View packages marked as favorites
diff --git a/deps/npm/docs/output/commands/npm-start.html b/deps/npm/docs/output/commands/npm-start.html
index 0c88d59c649266..35a7c7798f85d4 100644
--- a/deps/npm/docs/output/commands/npm-start.html
+++ b/deps/npm/docs/output/commands/npm-start.html
@@ -186,9 +186,9 @@
-
+
npm-start
- @11.17.0
+ @11.18.0
Start a package
diff --git a/deps/npm/docs/output/commands/npm-stop.html b/deps/npm/docs/output/commands/npm-stop.html
index 12b8779cc221e9..37c8d91d4ba01b 100644
--- a/deps/npm/docs/output/commands/npm-stop.html
+++ b/deps/npm/docs/output/commands/npm-stop.html
@@ -186,9 +186,9 @@
-
+
npm-stop
- @11.17.0
+ @11.18.0
Stop a package
diff --git a/deps/npm/docs/output/commands/npm-team.html b/deps/npm/docs/output/commands/npm-team.html
index 9e311e6d52ad51..66263e4f1f5327 100644
--- a/deps/npm/docs/output/commands/npm-team.html
+++ b/deps/npm/docs/output/commands/npm-team.html
@@ -186,9 +186,9 @@
-
+
npm-team
- @11.17.0
+ @11.18.0
Manage organization teams and team memberships
diff --git a/deps/npm/docs/output/commands/npm-test.html b/deps/npm/docs/output/commands/npm-test.html
index dddced02bd1fd4..b9e84c47671532 100644
--- a/deps/npm/docs/output/commands/npm-test.html
+++ b/deps/npm/docs/output/commands/npm-test.html
@@ -186,9 +186,9 @@
-
+
npm-test
- @11.17.0
+ @11.18.0
Test a package
diff --git a/deps/npm/docs/output/commands/npm-token.html b/deps/npm/docs/output/commands/npm-token.html
index 45962d19ed7b8f..c3c59a86cabe85 100644
--- a/deps/npm/docs/output/commands/npm-token.html
+++ b/deps/npm/docs/output/commands/npm-token.html
@@ -186,9 +186,9 @@