mirror of
https://github.com/open-goal/jak-project
synced 2026-09-01 01:52:03 -04:00
5d5e35fb9b
## Problem Windows builds break with a current local toolchain (Scoop LLVM 22.1.8, CMake 4.4.0, VS 2026), in two independent ways: 1. The build stops at curl's deliberate guard: `#error "no non-blocking method was found/used/set"` in `third-party/curl/lib/nonblock.c`. 2. From the second configure onward, `cmake --build` re-runs CMake in an endless loop (observed 42 consecutive reconfigure cycles in a single build). Likely the same mechanism behind the "endlessly building" VS 2026 note in `docs/setup/dev/vs.md`. ## Root cause 1. `third-party/curl/CMake/CurlTests.c` passes `int *` to `ioctlsocket()`, whose third parameter is `u_long *`. Clang 22 promotes `-Wincompatible-pointer-types` to a hard error in C, so the `HAVE_IOCTLSOCKET_FIONBIO` try_compile silently fails and `curl_config.h` never defines it. Upstream CI does not see this because the windows-2022 runner image ships an older LLVM. GCC 14 promotes the same warning to a hard error, which is very likely the `CurlTests.c.obj` failure reported from MSYS2 in open-goal/jak-project#3551. Upstream curl hit the identical problem with GCC 14 and fixed the probe in curl 8.8.0 (curl/curl#13578). 2. The root CMakeLists copies the build tree's `compile_commands.json` into `<src>/build/` for clangd using `configure_file()`, which registers its input as a configure dependency. CMake rewrites `compile_commands.json` late in every generation, after `CTestTestfile.cmake` and `cmake_install.cmake` (outputs of the same Ninja regen rule), so once the dependency is registered the rule is deterministically dirty and every `ninja` invocation re-runs CMake. A pristine first configure is safe (the file does not exist yet, so the `if(EXISTS ...)` guard skips the copy), which is why the loop looks machine- or IDE-specific. ## Fix 1. Per review, re-vendor `third-party/curl` at the `curl-8_21_0` tag (previously `curl-8_3_0`), which carries the upstream probe fix plus two years of upstream development; `vendor.yaml` updated to match. Adjustments the version jump forced: - curl 8.15 removed the native macOS Secure Transport backend (`CURL_USE_SECTRANSP`), so macOS now builds curl against OpenSSL like Linux. The two macOS workflows install Homebrew `openssl@3` and export `OPENSSL_ROOT_DIR` (keg-only), and the macOS setup docs gained the same two lines. - `CURL_BROTLI` / `CURL_ZSTD` switched to AUTO-detection in curl 8.10; pinned OFF to keep the previous no-compression behavior and avoid silently linking whatever the CI images happen to have. - curl's new top-level `BUILD_EXAMPLES` cache option (default ON) leaked into discord-rpc's identically named option and broke configure at a nonexistent `examples/send-presence` directory; pinned OFF ahead of the third-party subdirectories. The diff is dominated by the mechanical tag-tree swap under `third-party/curl` (linguist-vendored, collapsed in review). The hand-written changes are `CMakeLists.txt`, the two macOS workflows, `docs/setup/system/macos.md`, and `vendor.yaml`. 2. Swap `configure_file()` for `file(COPY ...)`: the same clangd copy with no configure dependency registered. (`file(COPY_FILE ... ONLY_IF_DIFFERENT)` would be cleaner still but requires CMake 3.21, above the declared `cmake_minimum_required(VERSION 3.10)`.) ## Test plan - [x] Fresh `cmake --preset Release-windows-clang` (LLVM 22, no cache seeding) completes and logs `Enabled SSL backends: Schannel`; the FIONBIO probe passes without the previous `#error` - [x] Full Windows Release build from scratch in the branch worktree (all 1422 targets) - [x] goalc-test suite: 1509 passed, 0 failed - [x] Second consecutive configure with `compile_commands.json` present: the regen rule in `build.ninja` has no `compile_commands.json` input; `<src>/build/compile_commands.json` is still refreshed for clangd - [x] Repeated `ninja` invocations after a full build no longer re-run CMake - [x] macOS Intel and ARM CI green (first exercise of the OpenSSL backend switch) --- I work off a self-hosted forge, so this GitHub account is quiet; the configure logs and ninja dirty-node traces from the investigation are available if anyone wants the raw data. (AI-assisted)
262 lines
9.9 KiB
C
Vendored
Generated
262 lines
9.9 KiB
C
Vendored
Generated
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at https://curl.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
* SPDX-License-Identifier: curl
|
|
*
|
|
***************************************************************************/
|
|
|
|
#include "unitcheck.h"
|
|
|
|
#include "bufq.h"
|
|
#include "vquic/capsule.h"
|
|
|
|
#if defined(USE_PROXY_HTTP3) && defined(USE_NGTCP2) && \
|
|
!defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
|
|
static void queue_bytes(struct bufq *q, const unsigned char *src, size_t len)
|
|
{
|
|
size_t nwritten = 0;
|
|
CURLcode result = Curl_bufq_write(q, src, len, &nwritten);
|
|
fail_unless(result == CURLE_OK, "queue failed");
|
|
fail_unless(nwritten == len, "queue short write");
|
|
}
|
|
#endif
|
|
|
|
#if defined(USE_PROXY_HTTP3) && \
|
|
!defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
|
|
static void check_capsule_hdr(size_t payload_len,
|
|
const unsigned char *expected,
|
|
size_t expected_len)
|
|
{
|
|
unsigned char hdr[HTTP_CAPSULE_HEADER_MAX_SIZE];
|
|
size_t hdr_len;
|
|
|
|
memset(hdr, 0xA5, sizeof(hdr));
|
|
hdr_len = capsule_encap_udp_hdr(hdr, sizeof(hdr), payload_len);
|
|
fail_unless(hdr_len == expected_len, "capsule header length mismatch");
|
|
fail_unless(!memcmp(hdr, expected, expected_len),
|
|
"capsule header bytes mismatch");
|
|
}
|
|
|
|
static void test_capsule_encap_udp_hdr_boundaries(void)
|
|
{
|
|
const unsigned char p0[] = { 0x00, 0x01, 0x00 };
|
|
const unsigned char p62[] = { 0x00, 0x3F, 0x00 };
|
|
const unsigned char p63[] = { 0x00, 0x40, 0x40, 0x00 };
|
|
const unsigned char p64[] = { 0x00, 0x40, 0x41, 0x00 };
|
|
const unsigned char p16382[] = { 0x00, 0x7F, 0xFF, 0x00 };
|
|
const unsigned char p16383[] = { 0x00, 0x80, 0x00, 0x40, 0x00, 0x00 };
|
|
const unsigned char p16384[] = { 0x00, 0x80, 0x00, 0x40, 0x01, 0x00 };
|
|
|
|
check_capsule_hdr(0, p0, sizeof(p0));
|
|
check_capsule_hdr(62, p62, sizeof(p62));
|
|
check_capsule_hdr(63, p63, sizeof(p63));
|
|
check_capsule_hdr(64, p64, sizeof(p64));
|
|
check_capsule_hdr(16382, p16382, sizeof(p16382));
|
|
check_capsule_hdr(16383, p16383, sizeof(p16383));
|
|
check_capsule_hdr(16384, p16384, sizeof(p16384));
|
|
}
|
|
|
|
#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
|
|
|
|
#if defined(USE_PROXY_HTTP3) && defined(USE_NGTCP2) && \
|
|
!defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
|
|
static void check_capsule_result(struct bufq *q,
|
|
const unsigned char *capsule, size_t capslen,
|
|
size_t outlen, CURLcode expect_err,
|
|
size_t expect_nread)
|
|
{
|
|
unsigned char out[32];
|
|
CURLcode err = CURLE_OK;
|
|
size_t nread;
|
|
|
|
memset(out, 0, sizeof(out));
|
|
Curl_bufq_reset(q);
|
|
if(capsule && capslen)
|
|
queue_bytes(q, capsule, capslen);
|
|
|
|
nread = Curl_capsule_process_udp_raw(NULL, NULL, q, out, outlen, &err);
|
|
fail_unless(err == expect_err, "unexpected capsule error");
|
|
fail_unless(nread == expect_nread, "unexpected capsule read size");
|
|
}
|
|
|
|
static void test_capsule_encode_decode_roundtrip(void)
|
|
{
|
|
struct bufq q;
|
|
unsigned char payload[128];
|
|
unsigned char out[128];
|
|
CURLcode result, err;
|
|
size_t payload_len;
|
|
size_t i, nread;
|
|
|
|
Curl_bufq_init2(&q, 32, 8, BUFQ_OPT_NONE);
|
|
|
|
for(i = 0; i < sizeof(payload); ++i)
|
|
payload[i] = (unsigned char)i;
|
|
|
|
for(i = 0; i < 2; ++i) {
|
|
payload_len = i ? 64 : 7;
|
|
memset(out, 0, sizeof(out));
|
|
Curl_bufq_reset(&q);
|
|
|
|
result = Curl_capsule_encap_udp_datagram(&q, payload, payload_len);
|
|
fail_unless(result == CURLE_OK, "failed to encapsulate UDP datagram");
|
|
|
|
err = CURLE_OK;
|
|
nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, sizeof(out),
|
|
&err);
|
|
fail_unless(err == CURLE_OK, "failed to decode UDP datagram");
|
|
fail_unless(nread == payload_len, "decoded payload length mismatch");
|
|
fail_unless(!memcmp(out, payload, payload_len),
|
|
"decoded payload bytes mismatch");
|
|
fail_unless(Curl_bufq_is_empty(&q), "decoded capsule must be consumed");
|
|
}
|
|
Curl_bufq_free(&q);
|
|
}
|
|
|
|
static void test_capsule_sequential_decode(void)
|
|
{
|
|
/* Verify that multiple back-to-back capsules in the same bufq are
|
|
each decoded in turn and the buffer is fully consumed. */
|
|
struct bufq q;
|
|
unsigned char out[8];
|
|
CURLcode err;
|
|
size_t nread;
|
|
/* Two back-to-back 3-byte UDP capsules */
|
|
const unsigned char two_caps[] = {
|
|
0x00, 0x04, 0x00, 0x11, 0x22, 0x33, /* capsule 1: [0x11,0x22,0x33] */
|
|
0x00, 0x04, 0x00, 0xAA, 0xBB, 0xCC /* capsule 2: [0xAA,0xBB,0xCC] */
|
|
};
|
|
|
|
Curl_bufq_init2(&q, 32, 4, BUFQ_OPT_NONE);
|
|
|
|
queue_bytes(&q, two_caps, sizeof(two_caps));
|
|
|
|
/* First capsule */
|
|
memset(out, 0, sizeof(out));
|
|
err = CURLE_OK;
|
|
nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, sizeof(out), &err);
|
|
fail_unless(err == CURLE_OK, "sequential: first capsule decode failed");
|
|
fail_unless(nread == 3, "sequential: first capsule size mismatch");
|
|
fail_unless(out[0] == 0x11 && out[1] == 0x22 && out[2] == 0x33,
|
|
"sequential: first capsule bytes mismatch");
|
|
|
|
/* Second capsule */
|
|
memset(out, 0, sizeof(out));
|
|
err = CURLE_OK;
|
|
nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, sizeof(out), &err);
|
|
fail_unless(err == CURLE_OK, "sequential: second capsule decode failed");
|
|
fail_unless(nread == 3, "sequential: second capsule size mismatch");
|
|
fail_unless(out[0] == 0xAA && out[1] == 0xBB && out[2] == 0xCC,
|
|
"sequential: second capsule bytes mismatch");
|
|
|
|
/* Buffer must be empty after both capsules */
|
|
fail_unless(Curl_bufq_is_empty(&q),
|
|
"sequential: buffer must be empty after two capsules");
|
|
|
|
/* No more data - CURLE_AGAIN expected */
|
|
err = CURLE_OK;
|
|
nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, sizeof(out), &err);
|
|
fail_unless(err == CURLE_AGAIN,
|
|
"sequential: empty queue should return AGAIN");
|
|
fail_unless(nread == 0, "sequential: empty queue should read zero bytes");
|
|
|
|
Curl_bufq_free(&q);
|
|
}
|
|
|
|
static void test_capsule_decode_paths(void)
|
|
{
|
|
struct bufq q;
|
|
unsigned char out[8];
|
|
CURLcode err = CURLE_OK;
|
|
size_t nread;
|
|
const unsigned char invalid_type[] = { 0x01 };
|
|
const unsigned char partial_len[] = { 0x00, 0x40 };
|
|
const unsigned char invalid_context[] = { 0x00, 0x01, 0x01 };
|
|
const unsigned char invalid_caps_len[] = { 0x00, 0x00, 0x00 };
|
|
const unsigned char partial_payload[] = { 0x00, 0x04, 0x00, 0x11, 0x22 };
|
|
const unsigned char payload_3b[] = { 0x00, 0x04, 0x00, 0x11, 0x22, 0x33 };
|
|
const unsigned char payload_empty[] = { 0x00, 0x01, 0x00 };
|
|
|
|
Curl_bufq_init2(&q, 32, 4, BUFQ_OPT_NONE);
|
|
|
|
check_capsule_result(&q, NULL, 0, 0, CURLE_BAD_FUNCTION_ARGUMENT, 0);
|
|
check_capsule_result(&q, NULL, 0, sizeof(out), CURLE_AGAIN, 0);
|
|
check_capsule_result(&q, invalid_type, sizeof(invalid_type), sizeof(out),
|
|
CURLE_RECV_ERROR, 0);
|
|
check_capsule_result(&q, partial_len, sizeof(partial_len), sizeof(out),
|
|
CURLE_AGAIN, 0);
|
|
check_capsule_result(&q, invalid_context, sizeof(invalid_context),
|
|
sizeof(out), CURLE_RECV_ERROR, 0);
|
|
check_capsule_result(&q, invalid_caps_len, sizeof(invalid_caps_len),
|
|
sizeof(out), CURLE_RECV_ERROR, 0);
|
|
check_capsule_result(&q, partial_payload, sizeof(partial_payload),
|
|
sizeof(out), CURLE_AGAIN, 0);
|
|
|
|
/* oversized payload is rejected and discarded */
|
|
Curl_bufq_reset(&q);
|
|
queue_bytes(&q, payload_3b, sizeof(payload_3b));
|
|
nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, 2, &err);
|
|
fail_unless(err == CURLE_RECV_ERROR,
|
|
"expected RECV_ERROR for short output buffer");
|
|
fail_unless(nread == 0, "expected zero read on short output buffer");
|
|
fail_unless(Curl_bufq_is_empty(&q), "oversized capsule must be discarded");
|
|
|
|
/* zero-length UDP payload is accepted and consumed */
|
|
Curl_bufq_reset(&q);
|
|
queue_bytes(&q, payload_empty, sizeof(payload_empty));
|
|
nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, sizeof(out), &err);
|
|
fail_unless(err == CURLE_OK, "zero-length UDP payload should succeed");
|
|
fail_unless(nread == 0, "zero-length UDP payload should read zero");
|
|
fail_unless(Curl_bufq_is_empty(&q), "zero-length capsule must be consumed");
|
|
|
|
/* normal payload decode */
|
|
Curl_bufq_reset(&q);
|
|
queue_bytes(&q, payload_3b, sizeof(payload_3b));
|
|
memset(out, 0, sizeof(out));
|
|
nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, sizeof(out), &err);
|
|
fail_unless(err == CURLE_OK, "payload decode should succeed");
|
|
fail_unless(nread == 3, "payload decode size mismatch");
|
|
fail_unless(out[0] == 0x11 && out[1] == 0x22 && out[2] == 0x33,
|
|
"payload decode bytes mismatch");
|
|
fail_unless(Curl_bufq_is_empty(&q), "payload capsule must be consumed");
|
|
|
|
Curl_bufq_free(&q);
|
|
}
|
|
#endif /* USE_NGTCP2 && !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
|
|
|
|
static CURLcode test_unit3400(const char *arg)
|
|
{
|
|
UNITTEST_BEGIN_SIMPLE
|
|
|
|
#if defined(USE_PROXY_HTTP3) && \
|
|
!defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
|
|
test_capsule_encap_udp_hdr_boundaries();
|
|
#endif
|
|
|
|
#if defined(USE_PROXY_HTTP3) && defined(USE_NGTCP2) && \
|
|
!defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
|
|
test_capsule_encode_decode_roundtrip();
|
|
test_capsule_decode_paths();
|
|
test_capsule_sequential_decode();
|
|
#endif
|
|
|
|
UNITTEST_END_SIMPLE
|
|
}
|