diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cb7dc668..dd2e82c7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -401,6 +401,15 @@ jobs: # DRBGs, the TLS PRF, ChaCha20 and Poly1305. Slow # AES / DES KAT loops are tracked under TODO. run: python demos/bearssl/smoke.py + - name: curl smoke (static + shared + system + BearSSL HTTPS) + # Compiles the whole libcurl source tree (HTTP + file + ws) + # and builds it as a static archive, a shared library, and an + # exe linked against each, plus a flavour bound to the system + # libcurl. HTTPS uses badc-compiled BearSSL. Transfers run + # against a hermetic loopback server. The Windows build is + # skipped for now (config-win32 surface pending), so the step + # is a no-op there. + run: python demos/curl/smoke.py - name: raylib standalone build + cdb load gate (Windows) # Build the vendored raylib + Lode Runner game standalone, then run # it under cdb with show-loader-snaps. A win32 / CRT import bound to @@ -862,7 +871,7 @@ jobs: run: | set -e export BADC="$PWD/target/release/badc" - for d in sqlite3 miniz kissfft bzip2 tweetnacl monocypher bearssl lua stb quickjs raylib; do + for d in sqlite3 miniz kissfft bzip2 tweetnacl monocypher bearssl lua stb quickjs raylib curl; do echo "::group::$d (N=${{ matrix.pressure }})" python "demos/$d/smoke.py" echo "::endgroup::" diff --git a/.gitignore b/.gitignore index e95de2032..4481f7683 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,16 @@ demos/bearssl/* !demos/bearssl/smoke.py !demos/bearssl/smoke_main.c !demos/bearssl/README.md +# curl -- vendored by `demos/curl/setup.py` (library sources -> src/, +# public API headers -> include/curl/). Only the hand-written config, +# driver, binding header, runner, and README are committed. +demos/curl/* +!demos/curl/setup.py +!demos/curl/smoke.py +!demos/curl/README.md +!demos/curl/curl_client.c +!demos/curl/curl_config.h +!demos/curl/curl_syslink.h # stb -- a collection of single-file public-domain headers. # `setup.py` pulls the upstream archive from the vendor-deps # release and drops a curated set of headers into `demos/stb/`; diff --git a/README.md b/README.md index dd92b5462..ca792321e 100644 --- a/README.md +++ b/README.md @@ -70,25 +70,27 @@ a (fast) script. There are various demo's under [`demos`](./demos/): -* Few small-ish ones (`threads.c`, `coro_pool.c`, `hello_server.c`), -* `maze.c ` - maze builder and solver, -* `gui_hello` - GUI demos for macOS, Linux and Windows, -* `wdm_driver`, `nt_hello`, `nt_loader` - examples of the Windows native (NT) executable, Windows driver, -* `efi_hello` - a UEFI binary, -* `sqlite3` - the most famous embedded database, -* `miniz` - compression, CRC32, integers, bit twiddling, -* `kissfft` - floating points, Fast Fourier Transform, -* `bzip2` - compression, integers, bit twiddling, -* `stb` - header-only C library with lots of incredible features (math +* Few small-ish ones ([`threads.c`](./demos/threads.c), [`coro_pool.c`](./demos/coro_pool.c), [`hello_server.c`](./demos/hello_server.c)), +* [`maze.c`](./demos/maze.c) - maze builder and solver, +* [`gui_hello`](./demos/gui_hello/) - GUI demos for macOS, Linux and Windows, +* [`wdm_driver`](./demos/wdm_driver/), [`nt_hello`](./demos/nt_hello/), [`nt_loader`](./demos/nt_loader/) - examples of the Windows native (NT) executable, Windows driver, +* [`efi_hello`](./demos/efi_hello/) - a UEFI binary, +* [`sqlite3`](./demos/sqlite3/) - the most famous embedded database ([sqlite.org](https://sqlite.org)), +* [`miniz`](./demos/miniz/) - compression, CRC32, integers, bit twiddling ([richgel999/miniz](https://github.com/richgel999/miniz)), +* [`kissfft`](./demos/kissfft/) - floating points, Fast Fourier Transform ([mborgerding/kissfft](https://github.com/mborgerding/kissfft)), +* [`bzip2`](./demos/bzip2/) - compression, integers, bit twiddling ([sourceware.org/bzip2](https://sourceware.org/bzip2/)), +* [`stb`](./demos/stb/) - header-only C library with lots of incredible features (math noise generation, sound, JPEG, PNG, BMP, PSD support to name a few). - It really stresses all of the compiler. -* `chibicc` - a small C compiler -* `tinycc` - a cool and small C toolchain -* `TweetNaCl`, `Monocypher`, `BearSSL` - cryptography -* `Lua` - the embeddable scripting language -* `quickjs` - JavaScript interpreter -* [`TCL`](https://en.wikipedia.org/wiki/Tcl_(programming_language)) - Tool command language -* `Python` - Python 3.14 + It really stresses all of the compiler ([nothings/stb](https://github.com/nothings/stb)), +* [`chibicc`](./demos/chibicc/) - a small C compiler ([rui314/chibicc](https://github.com/rui314/chibicc)), +* [`tinycc`](./demos/tinycc/) - a cool and small C toolchain ([TinyCC/tinycc](https://github.com/TinyCC/tinycc)), +* [`TweetNaCl`](./demos/tweetnacl/) ([tweetnacl.cr.yp.to](https://tweetnacl.cr.yp.to/)), [`Monocypher`](./demos/monocypher/) ([monocypher.org](https://monocypher.org/)), [`BearSSL`](./demos/bearssl/) ([bearssl.org](https://bearssl.org/)) - cryptography, +* [`Lua`](./demos/lua/) - the embeddable scripting language ([lua.org](https://www.lua.org/)), +* [`quickjs`](./demos/quickjs/) - JavaScript interpreter ([bellard.org/quickjs](https://bellard.org/quickjs/)), +* [`TCL`](./demos/tcl/) - Tool command language ([tcl-lang.org](https://www.tcl-lang.org/)), +* [`raylib`](./demos/raylib/) - Library for games, (there is also [`loderunner`](./demos/raylib/loderunner.c) game included) ([raylib.com](https://www.raylib.com/)), +* [`curl`](./demos/curl/) - The library and the tools that handle HTTP and friends on PCs, smart phones/watches, TVs, ... ([curl.se](https://curl.se/)), +* [`Python`](./demos/python/) - Python 3.14 ([python.org](https://www.python.org/)). Besides these, there are some fun test fixtures implementing Horner scheme, RK4, 8-Queens and more. diff --git a/demos/README.md b/demos/README.md index a9d595ae3..8b6750c93 100644 --- a/demos/README.md +++ b/demos/README.md @@ -152,6 +152,18 @@ standalone build + headless run is wired for macOS today (the X11 / Win32 header surface for the Linux / Windows ports is pending). See [`raylib/README.md`](./raylib/README.md). +## curl/ + +badc compiles the curl 8.11.1 library (HTTP + `file://` + WebSocket, +threaded resolver, IPv6, no external dependencies) and builds it as a +static archive, a shared library, and an executable linked against +each, plus a flavour that binds a badc-compiled client to the +platform's installed libcurl to check the frontend matches the OS ABI. +HTTPS is provided by badc-compiled BearSSL (`USE_BEARSSL`); the smoke +drives HTTP / HTTPS / `file://` transfers against a hermetic loopback +server, so it needs no external network. See +[`curl/README.md`](./curl/README.md). + ## efi_hello/ UEFI application that prints "Hello, EFI!" through diff --git a/demos/bearssl/smoke.py b/demos/bearssl/smoke.py index 2f4c5e0e5..44d2eede2 100644 --- a/demos/bearssl/smoke.py +++ b/demos/bearssl/smoke.py @@ -57,18 +57,11 @@ def __exit__(self, *args: object) -> None: WIN = sys.platform == "win32" EXE_SUFFIX = ".exe" if WIN else "" -# Disable the Win32 CryptoAPI seeder in src/rand/sysrng.c: it -# pulls in `` types and functions (HCRYPTPROV, -# PROV_RSA_FULL, CryptAcquireContext, ...) that c5's ambient -# Windows headers do not surface. The portable PRNGs still build -# and the smoke / KAT drivers never call br_prng_seeder_system, -# so the disabled codepath has no behavioural effect on the -# tests. The override is a no-op on POSIX, where BR_USE_URANDOM -# carries the runtime path. TODO: add a wincrypt.h surface to -# the c5 headers so this can come back on Windows. -BUILD_DEFINES: tuple[str, ...] = ( - "BR_USE_WIN32_RAND=0", -) +# BearSSL selects its system RNG seeder automatically: the Win32 CryptoAPI on +# Windows () and /dev/urandom on POSIX. The Windows path compiles +# against the shipped wincrypt.h surface (HCRYPTPROV, CryptAcquireContext, +# CryptGenRandom, ...). +BUILD_DEFINES: tuple[str, ...] = () INCLUDE_PATHS = ( BEAR_DIR / "inc", BEAR_DIR / "src", diff --git a/demos/curl/README.md b/demos/curl/README.md new file mode 100644 index 000000000..f16ea0c43 --- /dev/null +++ b/demos/curl/README.md @@ -0,0 +1,53 @@ +# curl + +badc compiles the [curl](https://curl.se) 8.11.1 library from source and +builds it three ways -- a static archive, a shared library, and an executable +that links against each -- plus a fourth flavour that links a badc-compiled +client against the platform's **installed** libcurl to prove the frontend +matches the OS ABI. + +The vendored library is trimmed to no external dependencies: HTTP, `file://`, +and the WebSocket / URL / multi / easy APIs, with the threaded resolver and +IPv6 on. HTTPS is provided by [BearSSL](../bearssl) when the TLS lane is built +(`USE_BEARSSL`); the system-libcurl flavour gets HTTPS from the OS's own TLS +backend. The other protocols (FTP, SMTP, LDAP, ...) are compiled out via the +`CURL_DISABLE_*` set. + +## Layout + +`setup.py` fetches the pinned tarball (vendor-deps mirror, falling back to +`curl.se`) and drops curl's `lib/` tree in `src/` and the public headers in +`include/curl/`. Committed alongside: + +* `curl_config.h` -- the hand-written build configuration (curl normally + generates this from configure/CMake). Describes exactly the POSIX surface + badc ships on macOS and Linux; Windows uses curl's own `lib/config-win32.h`. +* `curl_client.c` -- the driver: version / feature reporting, the URL API, + escape round-tripping, the string list, a `file://` transfer, and -- when a + base URL is passed -- an `http(s)://` fetch used by the loopback test. +* `curl_syslink.h` -- `#pragma dylib` / `#pragma binding` bindings for the + system-libcurl flavour. + +## Build flavours + +The smoke builds every flavour at `-O0` and `-O`: + +* **static** -- `badc --ar` bundles the objects into `libcurl.a`; the driver + links it with `-L/-l`. +* **shared** -- `badc --shared --export-all` produces `libcurl.{dylib,so,dll}`; + the driver binds to it through a generated `#pragma` header. +* **system** -- the driver binds to the installed libcurl through + `curl_syslink.h`. macOS resolves `/usr/lib/libcurl.4.dylib` from the dyld + shared cache; Linux uses the `libcurl.so.4` soname. Skipped where absent + (Windows ships no redistributable libcurl). + +## Running + +```sh +cargo build --release --manifest-path Cargo.toml +python3 demos/curl/smoke.py +``` + +Each built driver runs the offline scenarios plus a `file://` transfer and an +`http://` transfer against a loopback HTTP server the harness starts on +`127.0.0.1` -- no external network, so it is deterministic under CI. diff --git a/demos/curl/curl_client.c b/demos/curl/curl_client.c new file mode 100644 index 000000000..0213ce001 --- /dev/null +++ b/demos/curl/curl_client.c @@ -0,0 +1,205 @@ +/* curl_client.c -- exercises libcurl's public API end to end. + * + * Runs against any libcurl the smoke links it to: the badc-built static or + * dynamic library, or the system libcurl (via the -include binding header). + * The scenarios are offline unless a base URL is passed on argv: version and + * feature reporting, the URL API, escape round-tripping, the string list, and + * a file:// transfer through the easy handle. When argv[1] is an http(s):// or + * file:// base URL the client additionally fetches "/hello" and checks + * the body, which the smoke uses to drive a hermetic loopback server. + */ + +#include +#include +#include +#include +#include /* getcwd (mapped to _getcwd on Windows) */ + +struct membuf { + char *data; + size_t len; +}; + +static size_t collect(char *ptr, size_t size, size_t nmemb, void *userp) +{ + size_t n = size * nmemb; + struct membuf *m = (struct membuf *)userp; + char *grown = (char *)realloc(m->data, m->len + n + 1); + if(!grown) + return 0; + m->data = grown; + memcpy(m->data + m->len, ptr, n); + m->len += n; + m->data[m->len] = '\0'; + return n; +} + +static int fail(const char *what) +{ + fprintf(stderr, "curl smoke FAIL: %s\n", what); + return 1; +} + +static int scenario_version(void) +{ + const char *v = curl_version(); + if(!v || !strstr(v, "libcurl/")) + return fail("curl_version missing libcurl/ prefix"); + printf("version OK: %s\n", v); + + curl_version_info_data *vi = curl_version_info(CURLVERSION_NOW); + if(!vi) + return fail("curl_version_info returned NULL"); + printf("version_info OK: num=0x%06x ssl=%s\n", vi->version_num, + (vi->features & CURL_VERSION_SSL) ? "yes" : "no"); + return 0; +} + +static int scenario_url(void) +{ + CURLU *u = curl_url(); + if(!u) + return fail("curl_url alloc"); + /* CURLU_NON_SUPPORT_SCHEME so the structural parse succeeds whether or + not https is a compiled-in protocol (HTTP-only vs BearSSL builds). */ + if(curl_url_set(u, CURLUPART_URL, + "https://bob@example.com:8443/dir/file?q=1&r=2", + CURLU_NON_SUPPORT_SCHEME)) { + curl_url_cleanup(u); + return fail("curl_url_set"); + } + char *host = NULL, *port = NULL, *path = NULL, *query = NULL, *user = NULL; + int bad = curl_url_get(u, CURLUPART_HOST, &host, 0) + || curl_url_get(u, CURLUPART_PORT, &port, 0) + || curl_url_get(u, CURLUPART_PATH, &path, 0) + || curl_url_get(u, CURLUPART_QUERY, &query, 0) + || curl_url_get(u, CURLUPART_USER, &user, 0); + int ok = !bad && !strcmp(host, "example.com") && !strcmp(port, "8443") + && !strcmp(path, "/dir/file") && !strcmp(query, "q=1&r=2") + && !strcmp(user, "bob"); + printf("url OK: host=%s port=%s path=%s query=%s user=%s\n", + host, port, path, query, user); + curl_free(host); + curl_free(port); + curl_free(path); + curl_free(query); + curl_free(user); + curl_url_cleanup(u); + return ok ? 0 : fail("url parts mismatch"); +} + +static int scenario_escape(void) +{ + CURL *e = curl_easy_init(); + if(!e) + return fail("curl_easy_init for escape"); + const char *raw = "a b/c?d=e&f"; + char *enc = curl_easy_escape(e, raw, 0); + int outlen = 0; + char *dec = enc ? curl_easy_unescape(e, enc, 0, &outlen) : NULL; + int ok = enc && dec && !strcmp(dec, raw) && !strchr(enc, ' '); + printf("escape OK: '%s' -> '%s' -> '%s'\n", raw, enc ? enc : "(null)", + dec ? dec : "(null)"); + curl_free(enc); + curl_free(dec); + curl_easy_cleanup(e); + return ok ? 0 : fail("escape round-trip"); +} + +static int scenario_slist(void) +{ + struct curl_slist *h = NULL; + h = curl_slist_append(h, "X-One: 1"); + h = curl_slist_append(h, "X-Two: 2"); + int n = 0; + for(struct curl_slist *p = h; p; p = p->next) + n++; + curl_slist_free_all(h); + printf("slist OK: %d entries\n", n); + return n == 2 ? 0 : fail("slist count"); +} + +static int transfer(const char *url, const char *want) +{ + CURL *e = curl_easy_init(); + if(!e) + return fail("curl_easy_init for transfer"); + struct membuf body = {NULL, 0}; + curl_easy_setopt(e, CURLOPT_URL, url); + curl_easy_setopt(e, CURLOPT_WRITEFUNCTION, collect); + curl_easy_setopt(e, CURLOPT_WRITEDATA, &body); + curl_easy_setopt(e, CURLOPT_TIMEOUT, 10L); + /* Loopback TLS uses a throwaway self-signed cert, so peer/host + verification is turned off for the hermetic test. */ + curl_easy_setopt(e, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(e, CURLOPT_SSL_VERIFYHOST, 0L); + CURLcode rc = curl_easy_perform(e); + int ok = (rc == CURLE_OK) && body.data && strstr(body.data, want); + if(rc != CURLE_OK) + fprintf(stderr, " transfer %s: %s\n", url, curl_easy_strerror(rc)); + else + printf("transfer OK: %s -> %zu bytes\n", url, body.len); + free(body.data); + curl_easy_cleanup(e); + return ok ? 0 : fail("transfer"); +} + +static int scenario_file(void) +{ + /* Write a temp file and read it back through file://. */ + const char *path = "curl_smoke_file.txt"; + const char *content = "badc-file-scheme-payload"; + FILE *f = fopen(path, "wb"); + if(!f) + return fail("temp file create"); + fwrite(content, 1, strlen(content), f); + fclose(f); + char url[512]; + char cwd[400]; + if(!getcwd(cwd, sizeof cwd)) + return fail("getcwd"); +#ifdef _WIN32 + /* file:///C:/dir/file -- forward slashes and the extra leading slash for + the drive-letter path. */ + for(char *p = cwd; *p; p++) + if(*p == '\\') + *p = '/'; + snprintf(url, sizeof url, "file:///%s/%s", cwd, path); +#else + snprintf(url, sizeof url, "file://%s/%s", cwd, path); +#endif + int rc = transfer(url, content); + remove(path); + return rc; +} + +int main(int argc, char **argv) +{ + // Emit each progress line immediately. The Windows C runtime treats line + // buffering (_IOLBF) as full buffering (verified identical to _IOFBF under + // both this compiler and MSVC), so a piped stdout would hold the whole run + // and lose it if the process-exit flush is preempted by lingering network + // worker threads. Unbuffered output is written per line on every platform. + setvbuf(stdout, NULL, _IONBF, 0); + if(curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) + return fail("curl_global_init"); + + int rc = 0; + rc |= scenario_version(); + rc |= scenario_url(); + rc |= scenario_escape(); + rc |= scenario_slist(); + rc |= scenario_file(); + + /* Optional networked fetch against a base URL supplied by the smoke. */ + if(argc > 1 && argv[1][0]) { + char url[512]; + snprintf(url, sizeof url, "%s/hello", argv[1]); + rc |= transfer(url, "hello-from-loopback"); + } + + curl_global_cleanup(); + if(rc == 0) + printf("curl smoke: all scenarios green\n"); + return rc; +} diff --git a/demos/curl/curl_config.h b/demos/curl/curl_config.h new file mode 100644 index 000000000..94e47c6fb --- /dev/null +++ b/demos/curl/curl_config.h @@ -0,0 +1,152 @@ +/* curl_config.h -- hand-written libcurl build configuration for badc. + * + * curl normally generates this from configure/CMake against the host libc. + * badc ships a curated POSIX header surface, so this file is authored to + * describe exactly what badc backs on macOS (aarch64) and Linux + * (x86_64/aarch64): HTTP + file:// + WebSocket, threaded resolver, IPv6, no + * external dependencies. HTTPS is provided by BearSSL when USE_BEARSSL is set + * on the command line (see the demo smoke). Windows builds do not use this + * file: curl_setup.h pulls curl's own lib/config-win32.h when HAVE_CONFIG_H is + * undefined. + * + * Selected by -DHAVE_CONFIG_H. The CURL_DISABLE_* protocol trim is passed on + * the command line (honoured after this include) so the same config header + * serves every lane. + */ + +#ifndef BADC_CURL_CONFIG_H +#define BADC_CURL_CONFIG_H + +/* ---- platform identity ---- */ +#define CURL_OS "badc" +#define STDC_HEADERS 1 +#define HAVE_BOOL_T 1 +#define HAVE_LONGLONG 1 +#define HAVE_VARIADIC_MACROS_C99 1 + +/* ---- sizes (LP64 on every POSIX target badc supports) ---- */ +#define SIZEOF_INT 4 +#define SIZEOF_LONG 8 +#define SIZEOF_LONG_LONG 8 +#define SIZEOF_SHORT 2 +#define SIZEOF_OFF_T 8 +#define SIZEOF_CURL_OFF_T 8 +#define SIZEOF_SIZE_T 8 +#define SIZEOF_TIME_T 8 +#define SIZEOF_CURL_SOCKET_T 4 + +/* ---- headers badc ships ---- */ +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_SOCKET_H 1 +#define HAVE_SYS_SELECT_H 1 +#define HAVE_SYS_IOCTL_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_TIME_H 1 +#define HAVE_SYS_PARAM_H 1 +#define HAVE_SYS_UN_H 1 +#define HAVE_SYS_RESOURCE_H 1 +#define HAVE_NETINET_IN_H 1 +#define HAVE_NETINET_TCP_H 1 +#define HAVE_NETDB_H 1 +#define HAVE_ARPA_INET_H 1 +#define HAVE_NET_IF_H 1 +#define HAVE_POLL_H 1 +#define HAVE_FCNTL_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_SIGNAL_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STDBOOL_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_STDATOMIC_H 1 +#define HAVE_LOCALE_H 1 +#define HAVE_LIBGEN_H 1 +#define HAVE_UTIME_H 1 +#define HAVE_PWD_H 1 +#define HAVE_DIRENT_H 1 +#define HAVE_TERMIOS_H 1 + +/* ---- functions badc backs ---- */ +#define HAVE_SOCKET 1 +#define HAVE_SOCKETPAIR 1 +#define HAVE_RECV 1 +#define HAVE_SEND 1 +#define HAVE_SELECT 1 +#define HAVE_POLL 1 +#define HAVE_POLL_FINE 1 +#define HAVE_GETADDRINFO 1 +#define HAVE_GETADDRINFO_THREADSAFE 1 +#define HAVE_FREEADDRINFO 1 +#define HAVE_GETNAMEINFO 1 +#define HAVE_GETHOSTNAME 1 +#define HAVE_GETPEERNAME 1 +#define HAVE_GETSOCKNAME 1 +#define HAVE_IF_NAMETOINDEX 1 +#define HAVE_INET_NTOP 1 +#define HAVE_INET_PTON 1 +#define HAVE_FCNTL 1 +#define HAVE_FCNTL_O_NONBLOCK 1 +#define HAVE_IOCTL 1 +#define HAVE_IOCTL_FIONBIO 1 +#define HAVE_GETTIMEOFDAY 1 +#define HAVE_GMTIME_R 1 +#define HAVE_LOCALTIME_R 1 +#define HAVE_STRTOK_R 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_SIGNAL 1 +#define HAVE_SIGACTION 1 +#define HAVE_SIGSETJMP 1 +#define HAVE_SIGINTERRUPT 1 +#define HAVE_ALARM 1 +#define HAVE_FTRUNCATE 1 +#define HAVE_UTIME 1 +#define HAVE_UTIMES 1 +#define HAVE_GETPPID 1 +#define HAVE_GETEUID 1 +#define HAVE_GETPWUID 1 +#define HAVE_GETRLIMIT 1 +#define HAVE_SETRLIMIT 1 +#define HAVE_PIPE 1 +#define HAVE_SNPRINTF 1 +#define HAVE_BASENAME 1 +#define HAVE_FSEEKO 1 +#define HAVE_STRTOLL 1 +#define HAVE_SETLOCALE 1 + +/* ---- structs / types ---- */ +#define HAVE_STRUCT_SOCKADDR_STORAGE 1 +#define HAVE_STRUCT_TIMEVAL 1 +#define HAVE_SA_FAMILY_T 1 +#define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 +#define HAVE_SUSECONDS_T 1 + +/* ---- features ---- */ +#define USE_THREADS_POSIX 1 +#define HAVE_PTHREAD_H 1 +#define USE_UNIX_SOCKETS 1 +#define USE_IPV6 1 +#define ENABLE_IPV6 1 + +/* ---- per-OS deltas ---- */ +#ifdef __linux__ +#define HAVE_MSG_NOSIGNAL 1 +#define HAVE_CLOCK_GETTIME_MONOTONIC 1 +#define HAVE_CLOCK_GETTIME_MONOTONIC_RAW 1 +#define GETHOSTNAME_TYPE_ARG2 size_t +#endif + +#ifdef __APPLE__ +#define HAVE_MACH_ABSOLUTE_TIME 1 +#define HAVE_SO_NOSIGPIPE 1 +#define GETHOSTNAME_TYPE_ARG2 size_t +#endif + +/* The RETSIGTYPE / signal-handler return type. */ +#define RETSIGTYPE void + +/* CURL_DISABLE_* for the trimmed protocol set and USE_*SSL are passed on the + * command line so this header stays platform-only. */ + +#endif /* BADC_CURL_CONFIG_H */ diff --git a/demos/curl/curl_syslink.h b/demos/curl/curl_syslink.h new file mode 100644 index 000000000..94f3d2506 --- /dev/null +++ b/demos/curl/curl_syslink.h @@ -0,0 +1,62 @@ +/* curl_syslink.h -- bind the demo client to the platform's installed libcurl. + * + * badc's -l resolves only to a static archive, so a dynamic dependency on a + * system shared library is expressed in source: `#pragma dylib` names the + * library (install name / soname / DLL name) and each `#pragma binding` maps a + * called symbol to its exported name. badc turns these into LC_LOAD_DYLIB + * (Mach-O), DT_NEEDED (ELF), or a PE import descriptor, and the loader resolves + * them at run time -- proving a badc-compiled client is ABI-compatible with the + * OS libcurl. Force-include this header (-include) ahead of the client. + * + * Only the symbols the client actually calls are listed. macOS Mach-O prefixes + * exported symbols with an underscore; ELF and PE use the bare C name. Windows + * ships no redistributable libcurl, so the smoke skips this flavour there. + */ + +#ifndef BADC_CURL_SYSLINK_H +#define BADC_CURL_SYSLINK_H + +#if defined(__APPLE__) +#pragma dylib(libcurl, "/usr/lib/libcurl.4.dylib") +#pragma binding(libcurl::curl_global_init, "_curl_global_init") +#pragma binding(libcurl::curl_global_cleanup, "_curl_global_cleanup") +#pragma binding(libcurl::curl_version, "_curl_version") +#pragma binding(libcurl::curl_version_info, "_curl_version_info") +#pragma binding(libcurl::curl_free, "_curl_free") +#pragma binding(libcurl::curl_url, "_curl_url") +#pragma binding(libcurl::curl_url_set, "_curl_url_set") +#pragma binding(libcurl::curl_url_get, "_curl_url_get") +#pragma binding(libcurl::curl_url_cleanup, "_curl_url_cleanup") +#pragma binding(libcurl::curl_easy_init, "_curl_easy_init") +#pragma binding(libcurl::curl_easy_cleanup, "_curl_easy_cleanup") +#pragma binding(libcurl::curl_easy_setopt, "_curl_easy_setopt") +#pragma binding(libcurl::curl_easy_perform, "_curl_easy_perform") +#pragma binding(libcurl::curl_easy_strerror, "_curl_easy_strerror") +#pragma binding(libcurl::curl_easy_escape, "_curl_easy_escape") +#pragma binding(libcurl::curl_easy_unescape, "_curl_easy_unescape") +#pragma binding(libcurl::curl_slist_append, "_curl_slist_append") +#pragma binding(libcurl::curl_slist_free_all, "_curl_slist_free_all") + +#elif defined(__linux__) +#pragma dylib(libcurl, "libcurl.so.4") +#pragma binding(libcurl::curl_global_init, "curl_global_init") +#pragma binding(libcurl::curl_global_cleanup, "curl_global_cleanup") +#pragma binding(libcurl::curl_version, "curl_version") +#pragma binding(libcurl::curl_version_info, "curl_version_info") +#pragma binding(libcurl::curl_free, "curl_free") +#pragma binding(libcurl::curl_url, "curl_url") +#pragma binding(libcurl::curl_url_set, "curl_url_set") +#pragma binding(libcurl::curl_url_get, "curl_url_get") +#pragma binding(libcurl::curl_url_cleanup, "curl_url_cleanup") +#pragma binding(libcurl::curl_easy_init, "curl_easy_init") +#pragma binding(libcurl::curl_easy_cleanup, "curl_easy_cleanup") +#pragma binding(libcurl::curl_easy_setopt, "curl_easy_setopt") +#pragma binding(libcurl::curl_easy_perform, "curl_easy_perform") +#pragma binding(libcurl::curl_easy_strerror, "curl_easy_strerror") +#pragma binding(libcurl::curl_easy_escape, "curl_easy_escape") +#pragma binding(libcurl::curl_easy_unescape, "curl_easy_unescape") +#pragma binding(libcurl::curl_slist_append, "curl_slist_append") +#pragma binding(libcurl::curl_slist_free_all, "curl_slist_free_all") +#endif + +#endif /* BADC_CURL_SYSLINK_H */ diff --git a/demos/curl/setup.py b/demos/curl/setup.py new file mode 100644 index 000000000..b8bf49dd9 --- /dev/null +++ b/demos/curl/setup.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +"""Fetch the curl 8.11.1 source distribution for the badc demo. + +After this runs, ``demos/curl/src/`` holds curl's ``lib/`` tree (the +library sources compiled by badc) and ``demos/curl/include/curl/`` +holds the public API headers. The hand-written ``curl_config.h`` and +the driver/binding sources live beside this script and are not +touched. + +Pulls the pinned tarball from the ``kromych/badc`` vendor-deps mirror +first (verified against a sha256), falling back to ``curl.se`` when +the asset is not yet on the mirror. Idempotent: safe to call from CI +before each smoke run; ``-v`` prints every step. +""" + +from __future__ import annotations + +import argparse +import shutil +import sys +import tarfile +import urllib.request +from pathlib import Path + +CURL_DIR = Path(__file__).resolve().parent +SRC_DIR = CURL_DIR / "src" +INC_DIR = CURL_DIR / "include" / "curl" +CACHE_DIR = CURL_DIR / ".cache" + +REPO_ROOT = CURL_DIR.parents[1] +sys.path.insert(0, str(REPO_ROOT / "scripts" / "vendor_deps")) +import _fetch # noqa: E402 + +VERSION = "8.11.1" +UPSTREAM_SHA = "a889ac9dbba3644271bd9d1302b5c22a088893719b72be3487bc3d401e5c4e80" # tarball sha256 +ASSET = f"curl-{VERSION}-{UPSTREAM_SHA[:8]}.tar.gz" +RELEASE_TAG = "vendor-deps-v1" +SHA256 = UPSTREAM_SHA +# TODO: drop the direct fallback once the asset is uploaded to the +# vendor-deps mirror (see scripts/vendor_deps/README.md). +URL_FALLBACK = f"https://curl.se/download/curl-{VERSION}.tar.gz" + +PREFIX = f"curl-{VERSION}/" + + +def _fetch_tarball(tar_path: Path, log) -> None: + try: + _fetch.fetch_and_verify(RELEASE_TAG, ASSET, tar_path, SHA256, log) + return + except SystemExit: + log(f"vendor-deps mirror miss, falling back to {URL_FALLBACK}") + req = urllib.request.Request(URL_FALLBACK, headers={"User-Agent": "badc-demo"}) + with urllib.request.urlopen(req) as resp, tar_path.open("wb") as out: + shutil.copyfileobj(resp, out) + got = _fetch.sha256_of(tar_path) + if got != SHA256: + tar_path.unlink(missing_ok=True) + raise SystemExit(f"setup: sha256 mismatch for {ASSET}\n expected {SHA256}\n got {got}") + + +def _extract(tar_path: Path, log) -> None: + for d in (SRC_DIR, INC_DIR): + if d.exists(): + shutil.rmtree(d) + SRC_DIR.mkdir(parents=True) + INC_DIR.mkdir(parents=True) + lib_prefix = f"{PREFIX}lib/" + inc_prefix = f"{PREFIX}include/curl/" + with tarfile.open(tar_path, "r:gz") as tf: + for member in tf.getmembers(): + if member.name.startswith(lib_prefix): + rel = member.name[len(lib_prefix):] + dest = SRC_DIR / rel + elif member.name.startswith(inc_prefix) and member.name.endswith(".h"): + rel = member.name[len(inc_prefix):] + dest = INC_DIR / rel + else: + continue + if not rel: + continue + if member.isdir(): + dest.mkdir(parents=True, exist_ok=True) + continue + src = tf.extractfile(member) + if src is None: + continue + dest.parent.mkdir(parents=True, exist_ok=True) + with src as inp, dest.open("wb") as out: + shutil.copyfileobj(inp, out) + log(f"extracted curl {VERSION} lib -> {SRC_DIR}, headers -> {INC_DIR}") + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("-v", "--verbose", action="store_true") + args = parser.parse_args(argv) + + def log(msg: str) -> None: + if args.verbose: + print(msg, file=sys.stderr) + + CACHE_DIR.mkdir(parents=True, exist_ok=True) + tar_path = CACHE_DIR / ASSET + _fetch_tarball(tar_path, log) + _extract(tar_path, log) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/demos/curl/smoke.py b/demos/curl/smoke.py new file mode 100644 index 000000000..1ce188e3d --- /dev/null +++ b/demos/curl/smoke.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python3 +"""End-to-end smoke for badc against the curl 8.11.1 source. + +badc compiles the whole libcurl source tree (HTTP + file:// + WebSocket, +threaded resolver, IPv6, no external dependencies) and the demo builds it +three ways at -O0 and -O: + + * static -- ``badc --ar`` bundles the objects into ``libcurl.a``; the + driver links against it via ``-L/-l``. + * shared -- ``badc --shared --export-all`` produces ``libcurl.``; + the driver binds to it through a generated ``#pragma`` header. + * system -- the driver binds to the platform's installed libcurl through + ``curl_syslink.h`` (proves badc's frontend matches the OS ABI). + Skipped where no system libcurl is present. + +Each built driver runs the offline scenarios (version / URL API / escape / +string list), a ``file://`` transfer, and an ``http://`` transfer against a +hermetic loopback server this harness starts on 127.0.0.1 -- no external +network. + +Override the badc binary via ``BADC`` (default ``target/release/badc[.exe]``). +""" + +from __future__ import annotations + +import glob +import http.server +import os +import shutil +import ssl +import subprocess +import sys +import tempfile +import threading +from pathlib import Path + +CURL_DIR = Path(__file__).resolve().parent +REPO_ROOT = CURL_DIR.parents[1] +BEAR_DIR = CURL_DIR.parent / "bearssl" +SRC = CURL_DIR / "src" +INC = CURL_DIR / "include" +WIN = sys.platform == "win32" +MAC = sys.platform == "darwin" +LINUX = sys.platform.startswith("linux") +EXE = ".exe" if WIN else "" +SHLIB = ".dylib" if MAC else (".dll" if WIN else ".so") + +LOOPBACK_BODY = "hello-from-loopback" + +# HTTP-only trim: keep FILE + HTTP + WebSocket, drop the other protocols. +CURL_DISABLE = [ + "FTP", "TFTP", "TELNET", "DICT", "GOPHER", "POP3", "IMAP", "SMTP", + "RTSP", "MQTT", "SMB", "LDAP", "LDAPS", "NTLM", +] +# POSIX selects the hand-written curl_config.h via -DHAVE_CONFIG_H. Windows +# leaves it undefined so curl_setup.h pulls curl's own lib/config-win32.h, and +# defines __MINGW32__ so curl/system.h takes the LLP64 branch (correct +# curl_off_t) instead of the POSIX one. +_CONFIG_DEFINES = ["-D__MINGW32__"] if WIN else ["-DHAVE_CONFIG_H"] +# The client only includes the public , whose system.h keys the +# curl_off_t / curl_socket_t widths on __MINGW32__ on Windows. Without it the +# __GNUC__ branch is taken, which resolves the types for x86_64 but leaves them +# undefined on aarch64. +CLIENT_PLATFORM_DEFINES = ["-D__MINGW32__"] if WIN else [] +BASE_DEFINES = ( + ["--gnu", "-DBUILDING_LIBCURL", "-DCURL_STATICLIB"] + _CONFIG_DEFINES + + [f"-DCURL_DISABLE_{d}" for d in CURL_DISABLE] +) +# curl_config.h lives in the demo dir; the public headers under include/curl. +INCLUDE_PATHS = ["-I", str(SRC), "-I", str(INC), "-I", str(CURL_DIR)] + +# Symbols the driver calls; used to generate the shared/system binding headers. +CLIENT_SYMBOLS = [ + "curl_global_init", "curl_global_cleanup", "curl_version", + "curl_version_info", "curl_free", "curl_url", "curl_url_set", + "curl_url_get", "curl_url_cleanup", "curl_easy_init", "curl_easy_cleanup", + "curl_easy_setopt", "curl_easy_perform", "curl_easy_strerror", + "curl_easy_escape", "curl_easy_unescape", "curl_slist_append", + "curl_slist_free_all", +] + +EXPECTED_PREFIXES = ( + "version OK:", + "version_info OK:", + "url OK:", + "escape OK:", + "slist OK:", + "transfer OK: file://", + "transfer OK: {scheme}://", + "curl smoke: all scenarios green", +) + + +def resolve_badc() -> Path: + env = os.environ.get("BADC") + candidates = [Path(env)] if env else [] + default = REPO_ROOT / "target" / "release" / "badc" + candidates += [default, default.with_suffix(".exe")] + for c in candidates: + if c.is_file() and os.access(c, os.X_OK): + return c + print("smoke: BADC binary not found / not executable\n" + f" hint: cargo build --release --manifest-path={REPO_ROOT}/Cargo.toml", + file=sys.stderr) + sys.exit(2) + + +def run(cmd, **kw): + return subprocess.run(cmd, check=False, **kw) + + +def lib_sources() -> list[Path]: + return sorted(p for p in SRC.rglob("*.c")) + + +def compile_objects(badc: Path, work: Path, optimize: bool) -> list[Path] | None: + objs = [] + cmd_base = [str(badc)] + (["-O"] if optimize else []) + BASE_DEFINES + INCLUDE_PATHS + for src in lib_sources(): + obj = work / (src.stem + ".o") + r = run(cmd_base + ["-c", "-o", str(obj), str(src)], capture_output=True, text=True) + if r.returncode != 0: + print(f"smoke FAIL: compile {src.name}\n{r.stderr[:600]}", file=sys.stderr) + return None + objs.append(obj) + return objs + + +def write_binding_header(path: Path, lib_spec: str) -> None: + """Emit #pragma dylib + per-symbol bindings for a libcurl at lib_spec + (install name / soname / abspath / DLL name). Mach-O prefixes exports with + '_'; ELF and PE use the bare C name. A PE import is resolved by DLL name, + so on Windows lib_spec must be the basename and the DLL sit beside the exe.""" + us = "_" if MAC else "" + lines = [f'#pragma dylib(libcurl, "{lib_spec}")'] + lines += [f'#pragma binding(libcurl::{s}, "{us}{s}")' for s in CLIENT_SYMBOLS] + path.write_text("\n".join(lines) + "\n") + + +def link_client(badc: Path, work: Path, out: Path, optimize: bool, + *, archive: Path | None = None, binding_header: Path | None = None) -> bool: + cmd = [str(badc)] + (["-O"] if optimize else []) + cmd += ["--gnu", "-DCURL_STATICLIB", *CLIENT_PLATFORM_DEFINES, + "-I", str(INC), "-I", str(CURL_DIR)] + if binding_header is not None: + cmd += ["-include", str(binding_header)] + cmd += [str(CURL_DIR / "curl_client.c")] + if archive is not None: + cmd += ["-L", str(archive.parent), "-l", "curl"] + cmd += ["-o", str(out)] + r = run(cmd, capture_output=True, text=True) + if r.returncode != 0: + print(f"smoke FAIL: link {out.name}\n{r.stderr[:800]}", file=sys.stderr) + return False + return True + + +class LoopbackServer: + """Threaded HTTP(S) server on 127.0.0.1 serving a fixed body at any path. + + Passing a (certfile, keyfile) pair wraps the listener in TLS, so the same + harness drives both the plain-HTTP flavours and the HTTPS (BearSSL / + system-TLS) lane.""" + + def __init__(self, tls: tuple[str, str] | None = None) -> None: + body = LOOPBACK_BODY.encode() + + class Handler(http.server.BaseHTTPRequestHandler): + def do_GET(self): # noqa: N802 + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, *a): # silence + pass + + self.httpd = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler) + if tls is not None: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ctx.load_cert_chain(tls[0], tls[1]) + self.httpd.socket = ctx.wrap_socket(self.httpd.socket, server_side=True) + self.scheme = "https" if tls else "http" + self.port = self.httpd.socket.getsockname()[1] + self.thread = threading.Thread(target=self.httpd.serve_forever, daemon=True) + + @property + def base_url(self) -> str: + return f"{self.scheme}://127.0.0.1:{self.port}" + + def __enter__(self): + self.thread.start() + return self + + def __exit__(self, *a): + self.httpd.shutdown() + self.httpd.server_close() + + +def make_self_signed_cert(work: Path) -> tuple[str, str] | None: + """Generate a throwaway self-signed cert+key for 127.0.0.1 via openssl. + Returns (certfile, keyfile), or None when openssl is unavailable.""" + if not shutil.which("openssl"): + return None + cert = work / "loopback_cert.pem" + key = work / "loopback_key.pem" + r = run(["openssl", "req", "-x509", "-newkey", "rsa:2048", "-keyout", str(key), + "-out", str(cert), "-days", "2", "-nodes", "-subj", "/CN=127.0.0.1", + "-addext", "subjectAltName=IP:127.0.0.1"], capture_output=True, text=True) + if r.returncode != 0 or not cert.exists(): + return None + return (str(cert), str(key)) + + +def build_bearssl_archive(badc: Path, work: Path) -> Path | None: + """Compile the vendored BearSSL sources into libbearssl.a.""" + subprocess.run([sys.executable, str(BEAR_DIR / "setup.py")], check=True) + srcs = sorted(glob.glob(str(BEAR_DIR / "src" / "**" / "*.c"), recursive=True)) + if not srcs: + print("smoke FAIL: no BearSSL sources (setup.py)", file=sys.stderr) + return None + objs = [] + incs = ["-I", str(BEAR_DIR / "inc"), "-I", str(BEAR_DIR / "src")] + for i, src in enumerate(srcs): + obj = work / f"be_{i}.o" + # BearSSL auto-selects its RNG seeder: the Win32 CryptoAPI on Windows + # (needed for the TLS handshake), /dev/urandom on POSIX. + r = run([str(badc), "--gnu", *incs, + "-c", "-o", str(obj), src], capture_output=True, text=True) + if r.returncode != 0: + print(f"smoke FAIL: BearSSL {Path(src).name}\n{r.stderr[:500]}", file=sys.stderr) + return None + objs.append(obj) + archive = work / "libbearssl.a" + if run([str(badc), "--ar", "-o", str(archive)] + [str(o) for o in objs], + capture_output=True, text=True).returncode != 0: + print("smoke FAIL: libbearssl.a", file=sys.stderr) + return None + return archive + + +def build_tls_lane(badc: Path, work: Path, bearssl: Path, https_base: str) -> bool: + """Compile libcurl with USE_BEARSSL, link the driver against it plus + libbearssl, and run an HTTPS transfer against the TLS loopback.""" + od = work / "tls" + od.mkdir(exist_ok=True) + defs = BASE_DEFINES + ["-DUSE_BEARSSL", "-I", str(BEAR_DIR / "inc")] + objs = [] + for src in lib_sources(): + obj = od / (src.stem + ".o") + r = run([str(badc), *defs, *INCLUDE_PATHS, "-c", "-o", str(obj), str(src)], + capture_output=True, text=True) + if r.returncode != 0: + print(f"smoke FAIL: compile (TLS) {src.name}\n{r.stderr[:600]}", file=sys.stderr) + return False + objs.append(obj) + archive = od / "libcurl.a" + if run([str(badc), "--ar", "-o", str(archive)] + [str(o) for o in objs], + capture_output=True, text=True).returncode != 0: + print("smoke FAIL: libcurl.a (TLS)", file=sys.stderr) + return False + # Link against both archives (curl references BearSSL's br_* symbols). + exe = od / f"curl_client_tls{EXE}" + cmd = [str(badc), "--gnu", "-DCURL_STATICLIB", *CLIENT_PLATFORM_DEFINES, + "-I", str(INC), "-I", str(CURL_DIR), + str(CURL_DIR / "curl_client.c"), "-L", str(od), "-l", "curl", + "-L", str(bearssl.parent), "-l", "bearssl", "-o", str(exe)] + r = run(cmd, capture_output=True, text=True) + if r.returncode != 0: + print(f"smoke FAIL: link TLS client\n{r.stderr[:800]}", file=sys.stderr) + return False + return run_and_check("bearssl-tls", exe, https_base) + + +def run_and_check(label: str, exe: Path, base_url: str, env=None) -> bool: + # Run from the exe's directory so the client's temp file lands there and, + # on Windows, the loader finds a co-located libcurl.dll. + proc = run([str(exe), base_url], capture_output=True, text=True, env=env, + cwd=str(exe.parent)) + out = proc.stdout.replace("\r", "") + err = proc.stderr.replace("\r", "") + if proc.returncode != 0: + print(f"smoke FAIL [{label}]: exit {proc.returncode}\n--- stdout ---\n{out}\n" + f"--- stderr ---\n{err}", file=sys.stderr) + return False + scheme = base_url.split("://", 1)[0] + expected = [p.format(scheme=scheme) for p in EXPECTED_PREFIXES] + lines = [ln for ln in out.splitlines() if ln.strip()] + if len(lines) != len(expected): + print(f"smoke FAIL [{label}]: expected {len(expected)} lines, got " + f"{len(lines)}\n{out}", file=sys.stderr) + return False + for i, prefix in enumerate(expected): + if not lines[i].startswith(prefix): + print(f"smoke FAIL [{label}]: line {i+1} {lines[i]!r} != {prefix!r}", + file=sys.stderr) + return False + print(f"smoke OK [{label}]") + return True + + +def system_libcurl_present() -> bool: + if MAC: + return Path("/usr/lib/libcurl.4.dylib").exists() or _dyld_has("libcurl.4.dylib") + if LINUX: + for d in ("/usr/lib", "/lib", "/usr/lib/x86_64-linux-gnu", + "/usr/lib/aarch64-linux-gnu", "/lib64"): + if list(Path(d).glob("libcurl.so*")) if Path(d).is_dir() else []: + return True + return False + return False + + +def _dyld_has(name: str) -> bool: + # On modern macOS the dylib is in the shared cache, not on disk; probe it. + try: + import ctypes + return ctypes.CDLL(name) is not None + except OSError: + return False + + +def build_and_run(badc: Path, work: Path, base_url: str) -> bool: + ok = True + for optimize in (False, True): + tag = "O" if optimize else "O0" + od = work / tag + od.mkdir(exist_ok=True) + objs = compile_objects(badc, od, optimize) + if objs is None: + return False + + # static archive + archive = od / "libcurl.a" + if run([str(badc), "--ar", "-o", str(archive)] + [str(o) for o in objs], + capture_output=True, text=True).returncode != 0: + print(f"smoke FAIL: libcurl.a ({tag})", file=sys.stderr) + return False + static_exe = od / f"curl_client_static{EXE}" + if not link_client(badc, od, static_exe, optimize, archive=archive): + return False + ok &= run_and_check(f"static-{tag}", static_exe, base_url) + + # shared library + shared = od / f"libcurl{SHLIB}" + if run([str(badc), "--shared", "--export-all", "-o", str(shared)] + + [str(o) for o in objs], capture_output=True, text=True).returncode != 0: + print(f"smoke FAIL: libcurl{SHLIB} ({tag})", file=sys.stderr) + return False + bind = od / "curl_shared_link.h" + # PE imports resolve by DLL name (the loader searches the exe dir); + # ELF/Mach-O record the full path we pass. + write_binding_header(bind, shared.name if WIN else str(shared)) + shared_exe = od / f"curl_client_shared{EXE}" + if not link_client(badc, od, shared_exe, optimize, binding_header=bind): + return False + env = dict(os.environ) + env["LD_LIBRARY_PATH"] = str(od) + os.pathsep + env.get("LD_LIBRARY_PATH", "") + ok &= run_and_check(f"shared-{tag}", shared_exe, base_url, env=env) + + # system libcurl (opt-independent; POSIX with a system libcurl only) + if not WIN and system_libcurl_present(): + sys_exe = work / f"curl_client_system{EXE}" + if not link_client(badc, work, sys_exe, False, + binding_header=CURL_DIR / "curl_syslink.h"): + return False + ok &= run_and_check("system", sys_exe, base_url) + else: + print("smoke SKIP: no system libcurl for the ABI-interop check") + return ok + + +def main() -> int: + badc = resolve_badc() + subprocess.run([sys.executable, str(CURL_DIR / "setup.py")], check=True) + with tempfile.TemporaryDirectory(prefix="curl-smoke-") as work_str: + work = Path(work_str) + ok = True + with LoopbackServer() as srv: + ok &= build_and_run(badc, work, srv.base_url) + + # HTTPS lane: the whole stack (libcurl + BearSSL TLS) is badc-built and + # fetches over TLS from a loopback server with a self-signed cert. + cert = make_self_signed_cert(work) + if cert is None: + print("smoke SKIP: openssl not found -- HTTPS (BearSSL) lane not run") + else: + bearssl = build_bearssl_archive(badc, work) + if bearssl is None: + ok = False + else: + with LoopbackServer(tls=cert) as tsrv: + ok &= build_tls_lane(badc, work, bearssl, tsrv.base_url) + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/demos/nt_loader/nt_loader.c b/demos/nt_loader/nt_loader.c index 3bb06af35..547e96ed9 100644 --- a/demos/nt_loader/nt_loader.c +++ b/demos/nt_loader/nt_loader.c @@ -39,20 +39,26 @@ #define USE_UNICODE +// Select the wide-char surface in so its TCHAR is WCHAR-wide and +// matches the typedef below; the ANSI branch leaves both as char. +#ifdef USE_UNICODE +# define UNICODE +# define _UNICODE +#endif + #include #include #include #include #include -// TCHAR macros. +// Generic-text program macros. TCHAR itself comes from , keyed on +// UNICODE above. #ifdef USE_UNICODE -typedef WCHAR TCHAR; # define _T(x) L##x # define _tprintf wprintf # define _tmain wmain #else -typedef CHAR TCHAR; # define _T(x) x # define _tprintf printf # define _tmain main diff --git a/headers/include/netdb.h b/headers/include/netdb.h index ed3d9b014..2967d0e22 100644 --- a/headers/include/netdb.h +++ b/headers/include/netdb.h @@ -99,11 +99,33 @@ struct hostent { #define NI_NAMEREQD 0x08 #define NI_DGRAM 0x10 #endif -// EAI_SYSTEM: positive on macOS, negative on Linux; Windows does not define it. +// getaddrinfo error codes. macOS/BSD number them positively, Linux +// negatively; Windows maps them to WSA error values via . #ifdef __APPLE__ -#define EAI_SYSTEM 11 +#define EAI_AGAIN 2 +#define EAI_BADFLAGS 3 +#define EAI_FAIL 4 +#define EAI_FAMILY 5 +#define EAI_MEMORY 6 +#define EAI_NODATA 7 +#define EAI_NONAME 8 +#define EAI_SERVICE 9 +#define EAI_SOCKTYPE 10 +#define EAI_SYSTEM 11 +#define EAI_OVERFLOW 14 #elif defined(__linux__) -#define EAI_SYSTEM (-11) +#define EAI_BADFLAGS (-1) +#define EAI_NONAME (-2) +#define EAI_AGAIN (-3) +#define EAI_FAIL (-4) +#define EAI_NODATA (-5) +#define EAI_FAMILY (-6) +#define EAI_SOCKTYPE (-7) +#define EAI_SERVICE (-8) +#define EAI_ADDRFAMILY (-9) +#define EAI_MEMORY (-10) +#define EAI_SYSTEM (-11) +#define EAI_OVERFLOW (-12) #endif #ifdef __APPLE__ diff --git a/headers/include/netinet/in.h b/headers/include/netinet/in.h index 325677bf8..bd0789f67 100644 --- a/headers/include/netinet/in.h +++ b/headers/include/netinet/in.h @@ -7,9 +7,23 @@ #include +#define IPPROTO_IP 0 #define IPPROTO_IPV6 41 #define IPV6_V6ONLY 26 // Linux value; macOS=27 -- set via setsockopt only +// IP / IPv6 per-packet class-of-service option names for setsockopt. The +// numbering is BSD-derived on macOS and distinct on Linux. +#ifdef __linux__ +#define IP_TOS 1 +#define IPV6_TCLASS 67 +#else +#define IP_TOS 3 +#define IPV6_TCLASS 36 +#endif + +// Port number in network byte order. +typedef unsigned short in_port_t; + // Well-known IPv4 addresses in host byte order (uniform across targets). #define INADDR_ANY 0x00000000U #define INADDR_BROADCAST 0xffffffffU diff --git a/headers/include/netinet/tcp.h b/headers/include/netinet/tcp.h new file mode 100644 index 000000000..3529fc387 --- /dev/null +++ b/headers/include/netinet/tcp.h @@ -0,0 +1,20 @@ +// netinet/tcp.h -- TCP-level socket options (IPPROTO_TCP setsockopt names). +// TCP_NODELAY and TCP_MAXSEG share values across targets; the keep-alive +// tuning names are BSD-numbered on macOS and distinct on Linux. + +#pragma once + +#define TCP_NODELAY 1 +#define TCP_MAXSEG 2 + +#ifdef __linux__ +#define TCP_KEEPIDLE 4 +#define TCP_KEEPINTVL 5 +#define TCP_KEEPCNT 6 +#define TCP_FASTOPEN 23 +#elif defined(__APPLE__) +#define TCP_KEEPALIVE 0x10 +#define TCP_KEEPINTVL 0x101 +#define TCP_KEEPCNT 0x102 +#define TCP_FASTOPEN 0x105 +#endif diff --git a/headers/include/stdio.h b/headers/include/stdio.h index 419254f58..c15e99e3a 100644 --- a/headers/include/stdio.h +++ b/headers/include/stdio.h @@ -290,6 +290,8 @@ typedef struct __c5_fpos_t fpos_t; #pragma binding(msvcrt::perror, "perror") #pragma binding(msvcrt::fseek, "fseek") #pragma binding(msvcrt::ftell, "ftell") +#pragma binding(msvcrt::_fseeki64, "_fseeki64") +#pragma binding(msvcrt::_ftelli64, "_ftelli64") #pragma binding(msvcrt::fgetpos, "fgetpos") #pragma binding(msvcrt::fsetpos, "fsetpos") #pragma binding(msvcrt::rewind, "rewind") @@ -485,6 +487,9 @@ long ftell(FILE *stream); #ifndef _WIN32 int fseeko(FILE *stream, long offset, int whence); long ftello(FILE *stream); +#else +int _fseeki64(FILE *stream, long long offset, int whence); +long long _ftelli64(FILE *stream); #endif int fgetpos(FILE *stream, fpos_t *pos); int fsetpos(FILE *stream, const fpos_t *pos); diff --git a/headers/include/stdlib.h b/headers/include/stdlib.h index 86dc89b60..730b944a8 100644 --- a/headers/include/stdlib.h +++ b/headers/include/stdlib.h @@ -204,6 +204,9 @@ int *__doserrno(void); #define _doserrno (*__doserrno()) extern int _sys_nerr; extern char *_sys_errlist[]; +// Deprecated non-underscore aliases the msvcrt headers still expose. +#define sys_nerr _sys_nerr +#define sys_errlist _sys_errlist #if !defined(__aarch64__) extern unsigned short **_wenviron; #endif diff --git a/headers/include/string.h b/headers/include/string.h index d16959d1a..a247effe6 100644 --- a/headers/include/string.h +++ b/headers/include/string.h @@ -119,6 +119,9 @@ #pragma binding(msvcrt::strcspn, "strcspn") #pragma binding(msvcrt::strpbrk, "strpbrk") #pragma binding(msvcrt::strtok, "strtok") +// POSIX strtok_r is msvcrt's strtok_s -- identical (str, delim, context) +// signature and semantics. +#pragma binding(msvcrt::strtok_r, "strtok_s") // Case-insensitive compares: msvcrt's POSIX-style names live // behind a leading underscore. Expose both spellings so source // reaching for the underscored direct form resolves the same diff --git a/headers/include/sys/socket.h b/headers/include/sys/socket.h index 93ed94bad..bb6be88fa 100644 --- a/headers/include/sys/socket.h +++ b/headers/include/sys/socket.h @@ -51,6 +51,22 @@ #define INADDR_ANY 0 #define SOMAXCONN 128 +// Protocol families. POSIX PF_* mirror the AF_* address families; every +// current target defines them with equal values. +#define PF_UNSPEC AF_UNSPEC +#define PF_UNIX AF_UNIX +#define PF_LOCAL AF_UNIX +#define PF_INET AF_INET +#define PF_INET6 AF_INET6 + +// Address-family field type. macOS holds it in one byte (sin_len precedes +// it); Linux and Winsock use a 16-bit field. +#ifdef __APPLE__ +typedef unsigned char sa_family_t; +#else +typedef unsigned short sa_family_t; +#endif + // setsockopt / getsockopt levels and option names. The numeric values differ // between Linux and the BSD-derived set; Winsock inherited the BSD values, so // macOS and Windows share them. @@ -138,6 +154,7 @@ int sendfile(int fd, int s, long offset, long *len, struct sf_hdtr *hdtr, #define SO_REUSEADDR 0x0004 #define SO_KEEPALIVE 0x0008 #define SO_BROADCAST 0x0020 +#define SO_NOSIGPIPE 0x1022 // suppress SIGPIPE on writes to a broken socket #define SHUT_RD 0 #define SHUT_WR 1 #define SHUT_RDWR 2 @@ -169,6 +186,7 @@ int sendfile(int fd, int s, long offset, long *len, struct sf_hdtr *hdtr, #define SO_REUSEADDR 2 #define SO_KEEPALIVE 9 #define SO_BROADCAST 6 +#define MSG_NOSIGNAL 0x4000 // send() flag: do not raise SIGPIPE #define SHUT_RD 0 #define SHUT_WR 1 #define SHUT_RDWR 2 diff --git a/headers/include/sys/stat.h b/headers/include/sys/stat.h index 6c0f1a27a..2d1e63fc7 100644 --- a/headers/include/sys/stat.h +++ b/headers/include/sys/stat.h @@ -324,5 +324,32 @@ struct statvfs { #pragma binding(libc::fstatfs, "fstatfs") #endif +#ifndef _WIN32 int statfs(char *path, struct statfs *buf); int fstatfs(int fd, struct statfs *buf); +#endif + +#ifdef _WIN32 +// Windows CRT large-file stat. `struct _stati64` carries a 64-bit st_size; +// msvcrt exposes the ANSI (`_stati64`) and wide (`_wstati64`) forms. +struct _stati64 { + unsigned int st_dev; + unsigned short st_ino; + unsigned short st_mode; + short st_nlink; + short st_uid; + short st_gid; + unsigned int st_rdev; + long long st_size; + long long st_atime; + long long st_mtime; + long long st_ctime; +}; +#pragma dylib(msvcrt, "msvcrt.dll") +#pragma binding(msvcrt::_stati64, "_stati64") +#pragma binding(msvcrt::_wstati64, "_wstati64") +#pragma binding(msvcrt::_fstati64, "_fstati64") +int _stati64(const char *path, struct _stati64 *buf); +int _wstati64(const unsigned short *path, struct _stati64 *buf); +int _fstati64(int fd, struct _stati64 *buf); +#endif diff --git a/headers/include/tchar.h b/headers/include/tchar.h new file mode 100644 index 000000000..55f32d5b5 --- /dev/null +++ b/headers/include/tchar.h @@ -0,0 +1,29 @@ +// tchar.h -- generic-text routine mappings. c5 reaches for the ANSI (narrow) +// C runtime, so the _tcs* names resolve to the str* functions unless the +// program opts into UNICODE, where they would map to the wide wcs* forms. + +#pragma once + +#include + +#ifdef UNICODE +#include +#define _tcscpy wcscpy +#define _tcslen wcslen +#define _tcscmp wcscmp +#define _tcschr wcschr +#define _tcsstr wcsstr +#define _tcspbrk wcspbrk +#else +#define _tcscpy strcpy +#define _tcsncpy strncpy +#define _tcscat strcat +#define _tcslen strlen +#define _tcscmp strcmp +#define _tcsicmp _stricmp +#define _tcschr strchr +#define _tcsrchr strrchr +#define _tcsstr strstr +#define _tcspbrk strpbrk +#define _tcstol strtol +#endif diff --git a/headers/include/time.h b/headers/include/time.h index 16c3cf29f..d7689a1bb 100644 --- a/headers/include/time.h +++ b/headers/include/time.h @@ -30,6 +30,9 @@ #define CLOCK_REALTIME 0 #define CLOCK_MONOTONIC 1 #endif +// Raw hardware monotonic clock, unadjusted by NTP slewing. Same id on +// macOS and Linux. +#define CLOCK_MONOTONIC_RAW 4 // `clock_nanosleep` / `timer_settime` flag: the supplied time is // absolute, not a relative interval. Same value on every target. #define TIMER_ABSTIME 1 @@ -46,9 +49,12 @@ struct timespec { long long tv_nsec; }; +// Winsock's select() reads `struct timeval` as two 32-bit `long` fields +// (LLP64), so the whole object is 8 bytes. A 64-bit field would place +// tv_usec where select expects tv_sec's high half, zeroing the timeout. struct timeval { - long long tv_sec; - long long tv_usec; + long tv_sec; + long tv_usec; }; #elif defined(__APPLE__) struct timespec { diff --git a/headers/include/wincrypt.h b/headers/include/wincrypt.h new file mode 100644 index 000000000..27710e926 --- /dev/null +++ b/headers/include/wincrypt.h @@ -0,0 +1,57 @@ +// wincrypt.h -- the subset of the Windows CryptoAPI (advapi32) that the c5 +// demos reach: provider + hash objects for MD4 / MD5 / SHA-256, and the DES +// key path used by the legacy NTLM core. The handle types are pointer-width +// integers; the algorithm identifiers and provider/flag constants carry their +// documented values. + +#pragma once + +#include + +// HCRYPTPROV / HCRYPTKEY come from ; add the hash handle and the +// algorithm-id scalar here. +typedef void *HCRYPTHASH; +typedef unsigned int ALG_ID; + +#define PROV_RSA_FULL 1 +#define PROV_RSA_AES 24 +#define CRYPT_VERIFYCONTEXT 0xF0000000 +#define CRYPT_SILENT 0x00000040 +#define CRYPT_NEWKEYSET 0x00000008 + +#define CALG_MD4 0x00008002 +#define CALG_MD5 0x00008003 +#define CALG_SHA_256 0x0000800C +#define CALG_DES 0x00006601 + +#define HP_HASHVAL 0x0002 +#define HP_HASHSIZE 0x0004 + +#pragma binding(advapi32::CryptAcquireContextA, "CryptAcquireContextA") +#pragma binding(advapi32::CryptReleaseContext, "CryptReleaseContext") +#pragma binding(advapi32::CryptGenRandom, "CryptGenRandom") +#pragma binding(advapi32::CryptCreateHash, "CryptCreateHash") +#pragma binding(advapi32::CryptHashData, "CryptHashData") +#pragma binding(advapi32::CryptGetHashParam, "CryptGetHashParam") +#pragma binding(advapi32::CryptDestroyHash, "CryptDestroyHash") +#pragma binding(advapi32::CryptImportKey, "CryptImportKey") +#pragma binding(advapi32::CryptDestroyKey, "CryptDestroyKey") +#pragma binding(advapi32::CryptEncrypt, "CryptEncrypt") + +#define CryptAcquireContext CryptAcquireContextA + +int CryptAcquireContextA(HCRYPTPROV *prov, const char *container, + const char *provider, DWORD prov_type, DWORD flags); +int CryptReleaseContext(HCRYPTPROV prov, DWORD flags); +int CryptGenRandom(HCRYPTPROV prov, DWORD len, BYTE *buf); +int CryptCreateHash(HCRYPTPROV prov, ALG_ID algid, HCRYPTKEY key, DWORD flags, + HCRYPTHASH *hash); +int CryptHashData(HCRYPTHASH hash, const BYTE *data, DWORD len, DWORD flags); +int CryptGetHashParam(HCRYPTHASH hash, DWORD param, BYTE *data, DWORD *len, + DWORD flags); +int CryptDestroyHash(HCRYPTHASH hash); +int CryptImportKey(HCRYPTPROV prov, const BYTE *data, DWORD len, + HCRYPTKEY pubkey, DWORD flags, HCRYPTKEY *key); +int CryptDestroyKey(HCRYPTKEY key); +int CryptEncrypt(HCRYPTKEY key, HCRYPTHASH hash, int final, DWORD flags, + BYTE *data, DWORD *len, DWORD buflen); diff --git a/headers/include/windows.h b/headers/include/windows.h index 07c33ead4..7165c5fcd 100644 --- a/headers/include/windows.h +++ b/headers/include/windows.h @@ -48,8 +48,16 @@ #define _T(x) x #define __TEXT(x) x #define TEXT(x) x +// Generic-text character type. c5 reaches for the ANSI-flavoured calls, so +// TCHAR is `char` unless the program opts into UNICODE. +#ifdef UNICODE +typedef unsigned short TCHAR; +#else +typedef char TCHAR; +#endif #define LoadLibrary LoadLibraryA #define GetModuleHandle GetModuleHandleA +#define GetSystemDirectory GetSystemDirectoryA #define WINBASEAPI #define WINAPI #define WINAPI_INLINE @@ -117,6 +125,7 @@ typedef int GET_FILEEX_INFO_LEVELS; #pragma binding(kernel32::SetThreadPriority, "SetThreadPriority") #pragma binding(kernel32::GetCurrentThreadId, "GetCurrentThreadId") #pragma binding(kernel32::InitializeCriticalSection, "InitializeCriticalSection") +#pragma binding(kernel32::InitializeCriticalSectionEx, "InitializeCriticalSectionEx") #pragma binding(kernel32::EnterCriticalSection, "EnterCriticalSection") #pragma binding(kernel32::LeaveCriticalSection, "LeaveCriticalSection") #pragma binding(kernel32::DeleteCriticalSection, "DeleteCriticalSection") @@ -205,6 +214,7 @@ typedef int GET_FILEEX_INFO_LEVELS; #pragma binding(kernel32::CreateHardLinkW, "CreateHardLinkW") #pragma binding(kernel32::CreateSymbolicLinkW, "CreateSymbolicLinkW") #pragma binding(kernel32::MoveFileExW, "MoveFileExW") +#pragma binding(kernel32::MoveFileExA, "MoveFileExA") #pragma binding(kernel32::SetEnvironmentVariableW, "SetEnvironmentVariableW") #pragma binding(kernel32::GetDriveTypeW, "GetDriveTypeW") #pragma binding(kernel32::GetDiskFreeSpaceExW, "GetDiskFreeSpaceExW") @@ -276,12 +286,17 @@ typedef int GET_FILEEX_INFO_LEVELS; typedef void *HANDLE; typedef HANDLE *PHANDLE; typedef HANDLE *LPHANDLE; -typedef long long SIZE_T; -typedef long long ULONG_PTR; -typedef long long UINT_PTR; -typedef long long DWORD_PTR; +typedef unsigned long long SIZE_T; +typedef unsigned long long ULONG_PTR; +typedef unsigned long long UINT_PTR; +typedef unsigned long long DWORD_PTR; typedef long long LONG_PTR; typedef long long LONGLONG; +// basetsd.h pointer/integer conversions. HANDLE is pointer-width; a LONG +// sign-extends through LONG_PTR before becoming a HANDLE. +#define LongToHandle(h) ((HANDLE)(LONG_PTR)(long)(h)) +#define HandleToLong(h) ((long)(LONG_PTR)(h)) +#define ULongToHandle(h) ((HANDLE)(ULONG_PTR)(unsigned long)(h)) typedef unsigned long long ULONGLONG; typedef long long INT64; typedef long long LONG64; @@ -320,6 +335,17 @@ typedef unsigned short *LPCWSTR; typedef unsigned short WCHAR; typedef unsigned short *PWSTR; typedef unsigned short *PCWSTR; +// Generic-text pointer aliases. c5 builds ANSI, so the T-variants map to the +// narrow forms unless the program opts into UNICODE. +#ifdef UNICODE +typedef LPWSTR LPTSTR; +typedef LPCWSTR LPCTSTR; +#else +typedef LPSTR LPTSTR; +typedef LPCSTR LPCTSTR; +#endif +typedef LPCSTR PCSTR; +typedef LPSTR PSTR; typedef long long INT_PTR; typedef long long SSIZE_T; typedef long long LRESULT; @@ -333,6 +359,8 @@ typedef unsigned char BOOLEAN; // Slim reader/writer lock, condition variable, and one-time init // (Vista+). Pointer-sized opaque values per the Windows SDK. typedef struct _RTL_SRWLOCK { PVOID Ptr; } SRWLOCK; +// Static initialiser for an SRWLOCK (minwinbase.h RTL_SRWLOCK_INIT). +#define SRWLOCK_INIT {0} typedef struct _RTL_CONDITION_VARIABLE { PVOID Ptr; } CONDITION_VARIABLE; typedef struct _RTL_RUN_ONCE { PVOID Ptr; } INIT_ONCE; typedef SRWLOCK *PSRWLOCK; @@ -1501,6 +1529,9 @@ int GetExitCodeThread(HANDLE handle, int *exit_code); int SetThreadPriority(HANDLE thread, int priority); int GetCurrentThreadId(); int InitializeCriticalSection(char *cs); +// InitializeCriticalSectionEx(cs, spin, flags): the flag word selects debug +// info / no-dynamic-spin; c5 passes 0. +int InitializeCriticalSectionEx(char *cs, DWORD spin, DWORD flags); int EnterCriticalSection(char *cs); int LeaveCriticalSection(char *cs); int DeleteCriticalSection(char *cs); @@ -1569,6 +1600,8 @@ int DeviceIoControl(HANDLE device, DWORD code, void *in_buf, DWORD in_size, void int CreateHardLinkW(const unsigned short *link, const unsigned short *target, void *attrs); int CreateSymbolicLinkW(const unsigned short *symlink, const unsigned short *target, DWORD flags); int MoveFileExW(const unsigned short *from, const unsigned short *to, DWORD flags); +int MoveFileExA(const char *from, const char *to, DWORD flags); +#define MoveFileEx MoveFileExA int SetEnvironmentVariableW(const unsigned short *name, const unsigned short *value); unsigned int GetDriveTypeW(const unsigned short *root); int GetDiskFreeSpaceExW(const unsigned short *dir, void *avail, void *total, void *free_total); diff --git a/headers/include/winsock2.h b/headers/include/winsock2.h index 56b3db480..e9c7aeffa 100644 --- a/headers/include/winsock2.h +++ b/headers/include/winsock2.h @@ -58,6 +58,53 @@ struct sockaddr_in6 { // IN6ADDR_ANY_INIT is all zeros (in6.h); a zero-filled const matches. static const struct in6_addr in6addr_any; +// Address-family field type (ws2def.h): a 16-bit unsigned, same width as the +// sa_family field in every sockaddr here. +typedef unsigned short ADDRESS_FAMILY; + +// A Winsock event object is a HANDLE; the null handle is the invalid event. +typedef HANDLE WSAEVENT; +#define WSA_INVALID_EVENT ((WSAEVENT)0) + +// Pack two bytes into a WORD, low byte first (the WSAStartup version word). +#define MAKEWORD(a, b) \ + ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b)) << 8))) + +// Event-based socket notification (WSAEventSelect / WSAWaitForMultipleEvents). +// The ws2_32 dylib is declared in , included above. +#define WSAEWOULDBLOCK 10035 +#define FD_READ 0x01 +#define FD_WRITE 0x02 +#define FD_OOB 0x04 +#define FD_ACCEPT 0x08 +#define FD_CONNECT 0x10 +#define FD_CLOSE 0x20 +#define FD_MAX_EVENTS 10 +#define WSA_WAIT_FAILED 0xffffffff +#define WSA_WAIT_TIMEOUT 0x102 +#define WSA_INFINITE 0xffffffff + +typedef struct _WSANETWORKEVENTS { + long lNetworkEvents; + int iErrorCode[FD_MAX_EVENTS]; +} WSANETWORKEVENTS, *LPWSANETWORKEVENTS; + +#pragma binding(ws2_32::WSACreateEvent, "WSACreateEvent") +#pragma binding(ws2_32::WSACloseEvent, "WSACloseEvent") +#pragma binding(ws2_32::WSASetEvent, "WSASetEvent") +#pragma binding(ws2_32::WSAResetEvent, "WSAResetEvent") +#pragma binding(ws2_32::WSAEventSelect, "WSAEventSelect") +#pragma binding(ws2_32::WSAWaitForMultipleEvents, "WSAWaitForMultipleEvents") +#pragma binding(ws2_32::WSAEnumNetworkEvents, "WSAEnumNetworkEvents") +WSAEVENT WSACreateEvent(void); +int WSACloseEvent(WSAEVENT ev); +int WSASetEvent(WSAEVENT ev); +int WSAResetEvent(WSAEVENT ev); +int WSAEventSelect(int s, WSAEVENT ev, long net_events); +DWORD WSAWaitForMultipleEvents(DWORD count, const WSAEVENT *events, int wait_all, + DWORD timeout, int alertable); +int WSAEnumNetworkEvents(int s, WSAEVENT ev, WSANETWORKEVENTS *events); + // Uppercase aliases the SDK carries for the address structures. typedef struct sockaddr_in SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN; typedef struct sockaddr_in6 SOCKADDR_IN6, *PSOCKADDR_IN6, *LPSOCKADDR_IN6; diff --git a/scripts/validate_local_boxes.py b/scripts/validate_local_boxes.py index 8256d1a25..625eb6307 100755 --- a/scripts/validate_local_boxes.py +++ b/scripts/validate_local_boxes.py @@ -50,6 +50,7 @@ "demos/tweetnacl/smoke.py", "demos/quickjs/smoke.py", "demos/raylib/smoke.py", + "demos/curl/smoke.py", ) diff --git a/scripts/vendor_deps/README.md b/scripts/vendor_deps/README.md index 69d6830e8..7244dc023 100644 --- a/scripts/vendor_deps/README.md +++ b/scripts/vendor_deps/README.md @@ -2,7 +2,7 @@ Demos that pull a third-party library (`miniz`, `kissfft`, `bzip2`, `sqlite3`, `stb`, `chibicc`, `tinycc`, `tweetnacl`, -`monocypher`, `bearssl`, `lua`) fetch the upstream +`monocypher`, `bearssl`, `lua`, `curl`) fetch the upstream archive on first use. CI hitting the upstream hosts directly was flaky -- transient `RemoteDisconnected` failures from the GitHub release CDN and @@ -33,6 +33,7 @@ project's identifier for the release: | monocypher | github LoupVaillant/Monocypher tag | tarball-sha256 | | bearssl | bearssl.org release tarball | tarball-sha256 | | lua | lua.org source + test-suite tarballs | tarball-sha256 | +| curl | curl.se release tarball | tarball-sha256 | The full sha is recorded in `manifest.json` and in each demo's `setup.py` constants (`UPSTREAM_SHA`). diff --git a/scripts/vendor_deps/build_bundle.py b/scripts/vendor_deps/build_bundle.py index 855df9b76..abe7e822e 100755 --- a/scripts/vendor_deps/build_bundle.py +++ b/scripts/vendor_deps/build_bundle.py @@ -184,6 +184,17 @@ def asset_name(self) -> str: upstream_sha="57ccc32bbbd005cab75bcc52444052535af691789dba2b9016d5c50640d68b3d", sha_kind="tarball-sha256", ), + Source( + # curl 8.11.1 -- HTTP client library. Standalone release + # tarball from curl.se with no VCS identifier in the download + # name, so pinned by the tarball sha256. + name="curl", + version="8.11.1", + url="https://curl.se/download/curl-8.11.1.tar.gz", + ext=".tar.gz", + upstream_sha="a889ac9dbba3644271bd9d1302b5c22a088893719b72be3487bc3d401e5c4e80", + sha_kind="tarball-sha256", + ), Source( # Lua 5.5.0 official test suite -- published as a separate # tarball at www.lua.org/tests/. Same pin model as the diff --git a/src/c5/compiler/aggregate.rs b/src/c5/compiler/aggregate.rs index ffca6f3ba..dbce15ded 100644 --- a/src/c5/compiler/aggregate.rs +++ b/src/c5/compiler/aggregate.rs @@ -259,7 +259,12 @@ impl Compiler { self.parse_enum_decl()?; field_base_is_enum = true; Ty::Int as i64 - } else if self.is_lex_typedef_name() { + } else if !mods.saw_int_mod && self.is_lex_typedef_name() { + // Guarded by `!saw_int_mod`: C99 6.7.2p2 forbids + // combining a typedef-name with `unsigned`/`short`/ + // `long`/`signed`, so after an int-modifier the + // following typedef-name is the field's declarator + // identifier (a redeclared name), not a type-specifier. if self.symbols[self.lex.curr_id_idx].is_enum_typedef { field_base_is_enum = true; } diff --git a/src/c5/compiler/decl_base.rs b/src/c5/compiler/decl_base.rs index 6d579fb12..13f033028 100644 --- a/src/c5/compiler/decl_base.rs +++ b/src/c5/compiler/decl_base.rs @@ -537,9 +537,14 @@ impl Compiler { Ty::Int as i64 } else if self.lex.tk == Token::Struct || self.lex.tk == Token::Union { self.parse_aggregate_base_type()? - } else if self.is_lex_typedef_name() { + } else if !m.saw_int_mod && self.is_lex_typedef_name() { // Typedef-name as base type. Resolve to the aliased - // type and consume the identifier. + // type and consume the identifier. Guarded by + // `!saw_int_mod`: C99 6.7.2p2 forbids combining a + // typedef-name with `unsigned`/`short`/`long`/`signed`, + // so once an int-modifier is seen the following + // typedef-name is the declarator identifier (a redeclared + // name), not a second type-specifier. let aliased = self.symbols[self.lex.curr_id_idx].type_; // Carry the typedef's fn-pointer lineage forward (gh // #19) so a later `fn_t fp` declaration ends up with diff --git a/src/c5/compiler/function.rs b/src/c5/compiler/function.rs index 350b2ea8c..e31e4a0a2 100644 --- a/src/c5/compiler/function.rs +++ b/src/c5/compiler/function.rs @@ -103,13 +103,27 @@ impl Compiler { // Detect unnamed by counting `*` markers and then // peeking for `,` or `)`. let mut ty = base; + let mut leading_ptr_count = 0; while self.lex.tk == Token::MulOp { self.next()?; ty += Ty::Ptr as i64; + leading_ptr_count += 1; while self.lex.tk == Token::TypeQual { self.next()?; } } + // A parameter that is a pointer to a function-pointer typedef + // base (`curl_write_callback *p`) gains one fn-pointer + // indirection level per leading `*`, matching the general + // declarator path. Without it the fn-pointer decay no-op + // (`*fp == fp`) misfires on `*p`, landing the load/store one + // level too shallow (at the pointer's own slot). + if leading_ptr_count > 0 + && !self.pending.base_is_function_type + && let Some(fpi) = self.pending.fn_ptr_indirection + { + self.pending.fn_ptr_indirection = Some(fpi + leading_ptr_count); + } // Optional `[N]` / `[]` after an unnamed parameter // type ('int []' / 'char [16]'). Per C99 6.7.5.3p7, // a parameter declared with an array type is diff --git a/src/c5/headers.rs b/src/c5/headers.rs index 0a43e0712..c9067dd88 100644 --- a/src/c5/headers.rs +++ b/src/c5/headers.rs @@ -104,6 +104,10 @@ pub(super) const EMBEDDED_HEADERS: &[(&str, &str)] = &[ "netinet/in.h", include_str!("../../headers/include/netinet/in.h"), ), + ( + "netinet/tcp.h", + include_str!("../../headers/include/netinet/tcp.h"), + ), ( "arpa/inet.h", include_str!("../../headers/include/arpa/inet.h"), @@ -261,6 +265,11 @@ pub(super) const EMBEDDED_HEADERS: &[(&str, &str)] = &[ ), ("dlfcn.h", include_str!("../../headers/include/dlfcn.h")), ("windows.h", include_str!("../../headers/include/windows.h")), + ( + "wincrypt.h", + include_str!("../../headers/include/wincrypt.h"), + ), + ("tchar.h", include_str!("../../headers/include/tchar.h")), ( "winapifamily.h", include_str!("../../headers/include/winapifamily.h"), diff --git a/src/c5/tests/programs.rs b/src/c5/tests/programs.rs index c25f43339..f7aa2d084 100644 --- a/src/c5/tests/programs.rs +++ b/src/c5/tests/programs.rs @@ -1738,6 +1738,25 @@ fn array_typedef_dimensions_propagate() { assert_eq!(run_fixture("array_typedef_dimensions_propagate.c"), 0); } +#[test] +fn typedef_name_as_declarator() { + // C99 6.7.2p2: a typedef name cannot combine with + // `unsigned`/`short`/`long`/`signed`, so once an int-modifier + // is seen the following typedef name is the declarator + // identifier (a redeclared name), not a second type specifier. + // Exercises struct field, block-scope object, and parameter. + assert_eq!(run_fixture("typedef_name_as_declarator.c"), 0); +} + +#[test] +fn fnptr_param_indirection() { + // A parameter of type "pointer to function-pointer typedef" + // (`fn_t *p`) carries two levels of fn-pointer indirection, so + // `*p` is a real dereference rather than the `*fp == fp` decay + // no-op. Locks store and load through such a parameter. + assert_eq!(run_fixture("fnptr_param_indirection.c"), 0); +} + #[test] fn variadic_call_through_fnptr_delivers_all_args() { // C99 6.5.2.2: a call through a function pointer must diff --git a/tests/fixtures/c/fnptr_param_indirection.c b/tests/fixtures/c/fnptr_param_indirection.c new file mode 100644 index 000000000..d2f9ddc52 --- /dev/null +++ b/tests/fixtures/c/fnptr_param_indirection.c @@ -0,0 +1,41 @@ +// A parameter that is a pointer to a function-pointer typedef +// (`fn_t *out`) must carry two levels of function-pointer +// indirection: `*out` is a real dereference yielding a +// function-pointer lvalue, not the `*fp == fp` decay no-op that +// applies to a bare function pointer. +// +// The c5 parameter parser added the pointer level to the type but +// not to the fn-pointer indirection count, so `*out = v` and +// `return *in` on such a parameter mis-fired the decay no-op and +// read/wrote at the pointer's own slot instead of through it. This +// is the shape libcurl's setopt / write-callback plumbing uses. +// +// Returns 0 only when the store and load through the parameter +// pointer address the pointed-to function-pointer object. + +typedef int (*op_t)(int); + +static int inc(int x) { return x + 1; } +static int dbl(int x) { return x * 2; } + +static void store_through(op_t *out, op_t v) { *out = v; } +static op_t load_through(op_t *in) { return *in; } + +int main(void) { + op_t f = 0; + store_through(&f, inc); + if (f != inc) return 11; + if (f(10) != 11) return 12; + + op_t g = dbl; + op_t got = load_through(&g); + if (got != dbl) return 13; + if (got(10) != 20) return 14; + + // Local pointer-to-fn-ptr (already correct) as a control. + op_t h = 0; + op_t *p = &h; + *p = inc; + if (h != inc || h(3) != 4) return 15; + return 0; +} diff --git a/tests/fixtures/c/typedef_name_as_declarator.c b/tests/fixtures/c/typedef_name_as_declarator.c new file mode 100644 index 000000000..b9c0feeb5 --- /dev/null +++ b/tests/fixtures/c/typedef_name_as_declarator.c @@ -0,0 +1,40 @@ +// C99 6.7.2p2 + 6.7p3: a typedef name may be redeclared as an +// ordinary identifier once a complete type specifier precedes it. +// `unsigned short bit;` after `typedef unsigned int bit;` declares +// a member/object named `bit` of type `unsigned short` -- the +// typedef cannot combine with `unsigned`/`short`/`long`/`signed`, +// so the parser must treat the following name as the declarator. +// +// The c5 parser previously consumed the typedef name as a second +// type specifier, leaving no declarator ("identifier expected"). +// Covered positions: struct field, block-scope object, and a +// function parameter, each of the int-modifier-only forms. +// +// Returns 0 only when every declaration parses and lays out as the +// int-modifier type; distinct nonzero codes flag each failure. + +typedef unsigned int bit; + +struct S { + unsigned short bit; // member named after the typedef + long byte; +}; + +static int take(long bit) { // parameter named after the typedef + return (int)bit; +} + +int main(void) { + struct S s; + s.bit = 40000; // fits u16, would truncate if it were `bit`(u32) + s.byte = 5; + + unsigned short bit = 3; // block-scope object named after the typedef + + if (sizeof(s.bit) != sizeof(unsigned short)) return 11; + if (sizeof(bit) != sizeof(unsigned short)) return 12; + if (s.bit != 40000) return 13; + if (bit != 3) return 14; + if (take(7) != 7) return 15; + return 0; +} diff --git a/tests/snapshots/asm/fnptr_param_indirection.aarch64.asm b/tests/snapshots/asm/fnptr_param_indirection.aarch64.asm new file mode 100644 index 000000000..8b2cb04c5 --- /dev/null +++ b/tests/snapshots/asm/fnptr_param_indirection.aarch64.asm @@ -0,0 +1,130 @@ + +fnptr_param_indirection.aarch64: file format elf64-littleaarch64 + +Disassembly of section .text: + +<.text>: + mov x29, #0x0 // =0 + mov x0, sp + mov x1, #0x220 // =544 + movk x1, #0x0, lsl #16 + b + brk #: + add x0, x0, #0x1 + sxtw x0, w0 + ret + +: + lsl x0, x0, #1 + sxtw x0, w0 + ret + +: + str x1, [x0] + mov x0, #0x0 // =0 + ret + +: + ldr x0, [x0] + ret + +
: + stp x29, x30, [sp, #-0x10]! + mov x29, sp + sub sp, sp, #0x60 + str x20, [sp] + str x19, [sp, #0x10] + mov x0, #0x0 // =0 + stur x0, [x29, #-0x8] + sub x0, x29, #0x8 + adrp x20, + add x20, x20, + mov x1, x20 + bl + ldur x0, [x29, #-0x8] + adrp x1, + add x1, x1, + cmp x0, x1 + b.eq + mov x0, #0xb // =11 + ldr x20, [sp] + ldr x19, [sp, #0x10] + add sp, sp, #0x60 + ldp x29, x30, [sp], #0x10 + ret + mov x0, #0xa // =10 + ldur x1, [x29, #-0x8] + mov x9, x1 + str x0, [sp, #-0x10]! + ldr x0, [sp] + blr x9 + add sp, sp, #0x10 + sxtw x0, w0 + cmp x0, #0xb + b.eq + mov x0, #0xc // =12 + ldr x20, [sp] + ldr x19, [sp, #0x10] + add sp, sp, #0x60 + ldp x29, x30, [sp], #0x10 + ret + adrp x0, + add x0, x0, + stur x0, [x29, #-0x10] + sub x1, x29, #0x10 + ldr x1, [x1] + cmp x1, x0 + b.eq + mov x0, #0xd // =13 + ldr x20, [sp] + ldr x19, [sp, #0x10] + add sp, sp, #0x60 + ldp x29, x30, [sp], #0x10 + ret + mov x0, #0xa // =10 + mov x9, x1 + str x0, [sp, #-0x10]! + ldr x0, [sp] + blr x9 + add sp, sp, #0x10 + sxtw x0, w0 + cmp x0, #0x14 + b.eq + mov x0, #0xe // =14 + ldr x20, [sp] + ldr x19, [sp, #0x10] + add sp, sp, #0x60 + ldp x29, x30, [sp], #0x10 + ret + mov x0, #0x0 // =0 + stur x0, [x29, #-0x20] + sub x0, x29, #0x20 + str x20, [x0] + ldur x0, [x29, #-0x20] + cmp x0, x20 + cset x1, ne + cbnz x1, + mov x0, #0x3 // =3 + ldur x1, [x29, #-0x20] + mov x9, x1 + str x0, [sp, #-0x10]! + ldr x0, [sp] + blr x9 + add sp, sp, #0x10 + sxtw x0, w0 + cmp x0, #0x4 + cset x1, ne + cbz x1, + mov x0, #0xf // =15 + ldr x20, [sp] + ldr x19, [sp, #0x10] + add sp, sp, #0x60 + ldp x29, x30, [sp], #0x10 + ret + mov x0, #0x0 // =0 + ldr x20, [sp] + ldr x19, [sp, #0x10] + add sp, sp, #0x60 + ldp x29, x30, [sp], #0x10 + ret + b diff --git a/tests/snapshots/asm/fnptr_param_indirection.x64.asm b/tests/snapshots/asm/fnptr_param_indirection.x64.asm new file mode 100644 index 000000000..674dc336d --- /dev/null +++ b/tests/snapshots/asm/fnptr_param_indirection.x64.asm @@ -0,0 +1,116 @@ + +fnptr_param_indirection.x64: file format elf64-x86-64 + +Disassembly of section .text: + +<.text>: + xorl %ebp, %ebp + movq %rsp, %rdi + movl $, %esi + callq + ud2 + +: + leaq 0x1(%rdi), %rax + movslq %eax, %rax + retq + +: + movq %rdi, %rax + shlq $0x1, %rax + movslq %eax, %rax + retq + +: + movq %rsi, (%rdi) + xorq %rax, %rax + retq + +: + movq (%rdi), %rax + retq + +
: + pushq %rbp + movq %rsp, %rbp + subq $0x50, %rsp + movq %rbx, (%rsp) + xorq %rax, %rax + movq %rax, -0x8(%rbp) + leaq -0x8(%rbp), %rdi + leaq -, %rbx # + movq %rbx, %rsi + callq + movq -0x8(%rbp), %rax + leaq -, %rcx # + cmpq %rcx, %rax + je + movl $0xb, %eax + movq (%rsp), %rbx + addq $0x50, %rsp + popq %rbp + retq + movl $0xa, %edi + movq -0x8(%rbp), %rax + callq *%rax + movslq %eax, %rax + cmpq $0xb, %rax + je + movl $0xc, %eax + movq (%rsp), %rbx + addq $0x50, %rsp + popq %rbp + retq + leaq -, %rax # + movq %rax, -0x10(%rbp) + leaq -0x10(%rbp), %rcx + movq (%rcx), %rcx + cmpq %rax, %rcx + je + movl $0xd, %eax + movq (%rsp), %rbx + addq $0x50, %rsp + popq %rbp + retq + movl $0xa, %edi + movq %rcx, %rax + callq *%rax + movslq %eax, %rax + cmpq $0x14, %rax + je + movl $0xe, %eax + movq (%rsp), %rbx + addq $0x50, %rsp + popq %rbp + retq + xorq %rax, %rax + movq %rax, -0x20(%rbp) + leaq -0x20(%rbp), %rax + movq %rbx, (%rax) + movq -0x20(%rbp), %rax + cmpq %rbx, %rax + setne %cl + movzbq %cl, %rcx + testq %rcx, %rcx + jne + movl $0x3, %edi + movq -0x20(%rbp), %rax + callq *%rax + movslq %eax, %rax + cmpq $0x4, %rax + setne %cl + movzbq %cl, %rcx + testq %rcx, %rcx + je + movl $0xf, %eax + movq (%rsp), %rbx + addq $0x50, %rsp + popq %rbp + retq + xorq %rax, %rax + movq (%rsp), %rbx + addq $0x50, %rsp + popq %rbp + retq + jmp + addb %al, 0x41(%rdx) diff --git a/tests/snapshots/asm/typedef_in_function_body.aarch64.asm b/tests/snapshots/asm/typedef_in_function_body.aarch64.asm index bbe7ffde9..17ad37f64 100644 --- a/tests/snapshots/asm/typedef_in_function_body.aarch64.asm +++ b/tests/snapshots/asm/typedef_in_function_body.aarch64.asm @@ -14,6 +14,7 @@ Disassembly of section .text: mov x29, sp sub sp, sp, #0x10 mov x0, #0x7 // =7 + sxtw x0, w0 add sp, sp, #0x10 ldp x29, x30, [sp], #0x10 ret @@ -34,6 +35,7 @@ Disassembly of section .text: mov x0, #0x2 // =2 mov x1, #0x1 // =1 mov x2, #0x7 // =7 + sxtw x2, w2 cmp x2, #0x7 b.eq mov x0, #0x1 // =1 diff --git a/tests/snapshots/asm/typedef_in_function_body.x64.asm b/tests/snapshots/asm/typedef_in_function_body.x64.asm index bee01a94f..68ad97d5d 100644 --- a/tests/snapshots/asm/typedef_in_function_body.x64.asm +++ b/tests/snapshots/asm/typedef_in_function_body.x64.asm @@ -15,6 +15,7 @@ Disassembly of section .text: movq %rsp, %rbp subq $0x10, %rsp movl $0x7, %eax + movslq %eax, %rax addq $0x10, %rsp popq %rbp retq @@ -35,6 +36,7 @@ Disassembly of section .text: movl $0x2, %eax movl $0x1, %ecx movl $0x7, %edx + movslq %edx, %rdx cmpq $0x7, %rdx je movl $0x1, %eax @@ -69,3 +71,4 @@ Disassembly of section .text: addq $0x20, %rsp popq %rbp retq + addb %al, (%rax) diff --git a/tests/snapshots/asm/typedef_name_as_declarator.aarch64.asm b/tests/snapshots/asm/typedef_name_as_declarator.aarch64.asm new file mode 100644 index 000000000..70d242680 --- /dev/null +++ b/tests/snapshots/asm/typedef_name_as_declarator.aarch64.asm @@ -0,0 +1,68 @@ + +typedef_name_as_declarator.aarch64: file format elf64-littleaarch64 + +Disassembly of section .text: + +<.text>: + mov x29, #0x0 // =0 + mov x0, sp + mov x1, #0x220 // =544 + movk x1, #0x0, lsl #16 + b + brk #: + sxtw x0, w0 + ret + +
: + stp x29, x30, [sp, #-0x10]! + mov x29, sp + sub sp, sp, #0x20 + sub x0, x29, #0x10 + mov x1, #0x9c40 // =40000 + strh w1, [x0] + sub x0, x29, #0x10 + mov x1, #0x5 // =5 + str x1, [x0, #0x8] + mov x0, #0x3 // =3 + b + mov x0, #0xb // =11 + add sp, sp, #0x20 + ldp x29, x30, [sp], #0x10 + ret + b + mov x0, #0xc // =12 + add sp, sp, #0x20 + ldp x29, x30, [sp], #0x10 + ret + sub x1, x29, #0x10 + ldrh w1, [x1] + mov x17, #0x9c40 // =40000 + eor x1, x1, x17 + mov w1, w1 + cmp x1, #0x0 + b.eq + mov x0, #0xd // =13 + add sp, sp, #0x20 + ldp x29, x30, [sp], #0x10 + ret + mov x17, #0x3 // =3 + eor x0, x0, x17 + mov w0, w0 + cmp x0, #0x0 + b.eq + mov x0, #0xe // =14 + add sp, sp, #0x20 + ldp x29, x30, [sp], #0x10 + ret + mov x0, #0x7 // =7 + sxtw x0, w0 + cmp x0, #0x7 + b.eq + mov x0, #0xf // =15 + add sp, sp, #0x20 + ldp x29, x30, [sp], #0x10 + ret + mov x0, #0x0 // =0 + add sp, sp, #0x20 + ldp x29, x30, [sp], #0x10 + ret diff --git a/tests/snapshots/asm/typedef_name_as_declarator.x64.asm b/tests/snapshots/asm/typedef_name_as_declarator.x64.asm new file mode 100644 index 000000000..e294a4606 --- /dev/null +++ b/tests/snapshots/asm/typedef_name_as_declarator.x64.asm @@ -0,0 +1,67 @@ + +typedef_name_as_declarator.x64: file format elf64-x86-64 + +Disassembly of section .text: + +<.text>: + xorl %ebp, %ebp + movq %rsp, %rdi + movl $, %esi + callq + ud2 + +: + movslq %edi, %rax + retq + +
: + pushq %rbp + movq %rsp, %rbp + subq $0x20, %rsp + leaq -0x10(%rbp), %rax + movl $0x9c40, %ecx # imm = 0x9C40 + movw %cx, (%rax) + leaq -0x10(%rbp), %rax + movl $0x5, %ecx + movq %rcx, 0x8(%rax) + movl $0x3, %eax + jmp + movl $0xb, %eax + addq $0x20, %rsp + popq %rbp + retq + jmp + movl $0xc, %eax + addq $0x20, %rsp + popq %rbp + retq + leaq -0x10(%rbp), %rcx + movzwq (%rcx), %rcx + xorq $0x9c40, %rcx # imm = 0x9C40 + movl %ecx, %ecx + testq %rcx, %rcx + je + movl $0xd, %eax + addq $0x20, %rsp + popq %rbp + retq + xorq $0x3, %rax + movl %eax, %eax + testq %rax, %rax + je + movl $0xe, %eax + addq $0x20, %rsp + popq %rbp + retq + movl $0x7, %eax + movslq %eax, %rax + cmpq $0x7, %rax + je + movl $0xf, %eax + addq $0x20, %rsp + popq %rbp + retq + xorq %rax, %rax + addq $0x20, %rsp + popq %rbp + retq diff --git a/tests/snapshots/ssa/fnptr_param_indirection.ssa b/tests/snapshots/ssa/fnptr_param_indirection.ssa new file mode 100644 index 000000000..fa5ff2ee1 --- /dev/null +++ b/tests/snapshots/ssa/fnptr_param_indirection.ssa @@ -0,0 +1,186 @@ +; --- SSA dump (ok=true) ent_pc=0 --- +; name=inc +fn ent_pc=0 n_params=1 variadic=false locals=0 + spill_count=0 gpr_used=[] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I32) -> x7 + v2 Imm(0) -> x0 + v3 LoadLocal { off=2, kind=I32 } -> x0 + v4 BinopI { op=add, lhs=v1, rhs_imm=1 } -> x0 + v5 BinopI { op=shl, lhs=v4, rhs_imm=32 } -> x1 + v6 Extend { value=v4, kind=I32 } -> x0 + terminator Return(v6) (exit_acc=v6) +; --- SSA dump (ok=true) ent_pc=1 --- +; name=dbl +fn ent_pc=1 n_params=1 variadic=false locals=0 + spill_count=0 gpr_used=[] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I32) -> x7 + v2 Imm(0) -> x0 + v3 LoadLocal { off=2, kind=I32 } -> x0 + v4 BinopI { op=shl, lhs=v1, rhs_imm=1 } -> x0 + v5 BinopI { op=shl, lhs=v4, rhs_imm=32 } -> x1 + v6 Extend { value=v4, kind=I32 } -> x0 + terminator Return(v6) (exit_acc=v6) +; --- SSA dump (ok=true) ent_pc=2 --- +; name=store_through +fn ent_pc=2 n_params=2 variadic=false locals=0 + spill_count=0 gpr_used=[] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I64) -> x7 + v2 Imm(0) -> x0 + v3 ParamRef(1, kind=I64) -> x6 + v4 Imm(0) -> x0 + v5 LoadLocal { off=2, kind=I64 } -> x0 + v6 LoadLocal { off=3, kind=I64 } -> x0 + v7 Store { addr=v1, disp=0, value=v3, kind=I64 } -> - + v8 Imm(0) -> x0 + terminator Return(v8) (exit_acc=v8) +; --- SSA dump (ok=true) ent_pc=3 --- +; name=load_through +fn ent_pc=3 n_params=1 variadic=false locals=0 + spill_count=0 gpr_used=[] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I64) -> x7 + v2 Imm(0) -> x0 + v3 LoadLocal { off=2, kind=I64 } -> x0 + v4 Load { addr=v1, disp=0, kind=I64 } -> x0 + terminator Return(v4) (exit_acc=v4) +; --- SSA dump (ok=true) ent_pc=4 --- +; name=main +fn ent_pc=4 n_params=0 variadic=false locals=7 + spill_count=0 gpr_used=[3] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 Imm(0) -> x0 + v2 StoreLocal { off=-1, value=v1, kind=I64 } -> - + v3 LocalAddr(-1) -> x7 + v4 ImmCode(ent_pc=0) -> x3 + v5 Call { target_pc=2, args=[v3, v4], fixed_args=2, fp_return=false, fp_arg_mask=0x0 } -> x0 + v6 LoadLocal { off=-1, kind=I64 } -> x0 + v7 ImmCode(ent_pc=0) -> x1 + v8 Binop { op=ne, lhs=v6, rhs=v7 } -> x0 + terminator Bz { cond=v8, target=b2, fall=b1 } (exit_acc=v8) + block 1 start_pc=0 + v9 Imm(11) -> x0 + terminator Return(v9) (exit_acc=v9) + block 2 start_pc=0 + v10 Imm(10) -> x7 + v11 LoadLocal { off=-1, kind=I64 } -> x0 + v12 CallIndirect { target=v11, args=[v10], callee_variadic=false, fixed_args=1, fp_return=false, fp_arg_mask=0x0 } -> x0 + v13 BinopI { op=shl, lhs=v12, rhs_imm=32 } -> x1 + v14 Extend { value=v12, kind=I32 } -> x0 + v15 BinopI { op=ne, lhs=v14, rhs_imm=11 } -> x0 + terminator Bz { cond=v15, target=b4, fall=b3 } (exit_acc=v15) + block 3 start_pc=0 + v16 Imm(12) -> x0 + terminator Return(v16) (exit_acc=v16) + block 4 start_pc=0 + v17 ImmCode(ent_pc=1) -> x0 + v18 StoreLocal { off=-2, value=v17, kind=I64 } -> - + v19 LocalAddr(-2) -> x1 + v20 Imm(0) -> x2 + v21 Load { addr=v19, disp=0, kind=I64 } -> x1 + v22 Imm(0) -> x2 + v23 LoadLocal { off=-3, kind=I64 } -> x2 + v24 Binop { op=ne, lhs=v21, rhs=v17 } -> x0 + terminator Bz { cond=v24, target=b6, fall=b5 } (exit_acc=v24) + block 5 start_pc=0 + v25 Imm(13) -> x0 + terminator Return(v25) (exit_acc=v25) + block 6 start_pc=0 + v26 Imm(10) -> x7 + v27 LoadLocal { off=-3, kind=I64 } -> x0 + v28 CallIndirect { target=v21, args=[v26], callee_variadic=false, fixed_args=1, fp_return=false, fp_arg_mask=0x0 } -> x0 + v29 BinopI { op=shl, lhs=v28, rhs_imm=32 } -> x1 + v30 Extend { value=v28, kind=I32 } -> x0 + v31 BinopI { op=ne, lhs=v30, rhs_imm=20 } -> x0 + terminator Bz { cond=v31, target=b8, fall=b7 } (exit_acc=v31) + block 7 start_pc=0 + v32 Imm(14) -> x0 + terminator Return(v32) (exit_acc=v32) + block 8 start_pc=0 + v33 Imm(0) -> x0 + v34 StoreLocal { off=-4, value=v33, kind=I64 } -> - + v35 LocalAddr(-4) -> x0 + v36 Imm(0) -> x1 + v37 LoadLocal { off=-5, kind=I64 } -> x1 + v38 ImmCode(ent_pc=0) -> x1 + v39 Store { addr=v35, disp=0, value=v4, kind=I64 } -> - + v40 LoadLocal { off=-4, kind=I64 } -> x0 + v41 ImmCode(ent_pc=0) -> x1 + v42 Binop { op=ne, lhs=v40, rhs=v4 } -> x1 + v43 Imm(0) -> x0 + terminator Bnz { cond=v42, target=b13, fall=b9 } (exit_acc=v42) + block 9 start_pc=0 + v44 Imm(3) -> x7 + v45 LoadLocal { off=-4, kind=I64 } -> x0 + v46 CallIndirect { target=v45, args=[v44], callee_variadic=false, fixed_args=1, fp_return=false, fp_arg_mask=0x0 } -> x0 + v47 BinopI { op=shl, lhs=v46, rhs_imm=32 } -> x1 + v48 Extend { value=v46, kind=I32 } -> x0 + v49 BinopI { op=ne, lhs=v48, rhs_imm=4 } -> x1 + v50 Imm(0) -> x0 + terminator Jmp(b10) (exit_acc=v49) + block 10 start_pc=0 + v51 Phi { incoming=[b13:v42, b9:v49], kind=I64 } -> x1 + v52 LoadLocal { off=-7, kind=I64 } -> x0 + terminator Bz { cond=v51, target=b12, fall=b11 } (exit_acc=v51) + block 11 start_pc=0 + v53 Imm(15) -> x0 + terminator Return(v53) (exit_acc=v53) + block 12 start_pc=0 + v54 Imm(0) -> x0 + terminator Return(v54) (exit_acc=v54) + block 13 start_pc=0 + terminator Jmp(b10) +; --- SSA dump (ok=true) ent_pc=0 --- +; name=__c5_exit +fn ent_pc=0 n_params=1 variadic=false locals=1 + spill_count=0 gpr_used=[] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I32) -> x7 + v2 Imm(0) -> x0 + v3 LoadLocal { off=2, kind=I32 } -> x0 + v4 CallExt { binding_idx=0, args=[v1], fp_arg_mask=0x0 } -> x0 + v5 Imm(0) -> x0 + terminator Return(v5) (exit_acc=v5) +; --- SSA dump (ok=true) ent_pc=1 --- +; name=__c5_entry +fn ent_pc=1 n_params=2 variadic=false locals=6 + spill_count=0 gpr_used=[3] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I64) -> x7 + v2 Imm(0) -> x0 + v3 ParamRef(1, kind=I64) -> x6 + v4 Imm(0) -> x0 + v5 LoadLocal { off=3, kind=I64 } -> x0 + v6 BinopI { op=and, lhs=v3, rhs_imm=255 } -> x0 + v7 LoadLocal { off=2, kind=I64 } -> x0 + v8 Imm(0) -> x0 + v9 LoadLocal { off=-1, kind=I64 } -> x0 + v10 Imm(0) -> x3 + v11 Load { addr=v1, disp=0, kind=I64 } -> x0 + v12 BinopI { op=shl, lhs=v11, rhs_imm=32 } -> x1 + v13 Extend { value=v11, kind=I32 } -> x0 + v14 Imm(0) -> x1 + v15 Imm(8) -> x1 + v16 BinopI { op=add, lhs=v1, rhs_imm=8 } -> x6 + v17 Imm(0) -> x1 + v18 ImmData(24) -> x1 + v19 LoadLocal { off=-3, kind=I64 } -> x2 + v20 LoadLocal { off=-2, kind=I32 } -> x2 + v21 BinopI { op=shl, lhs=v13, rhs_imm=3 } -> x2 + v22 Binop { op=add, lhs=v16, rhs=v21 } -> x2 + v23 BinopI { op=add, lhs=v22, rhs_imm=8 } -> x2 + v24 Store { addr=v18, disp=0, value=v23, kind=I64 } -> - + v25 LoadLocal { off=-2, kind=I32 } -> x1 + v26 LoadLocal { off=-3, kind=I64 } -> x1 + v27 Call { target_pc=3, args=[v13, v16], fixed_args=2, fp_return=false, fp_arg_mask=0x0 } -> x7 + v28 Call { target_pc=0, args=[v27], fixed_args=1, fp_return=false, fp_arg_mask=0x0 } -> x0 + terminator Return(v10) (exit_acc=v10) diff --git a/tests/snapshots/ssa/typedef_in_function_body.ssa b/tests/snapshots/ssa/typedef_in_function_body.ssa index 12d6b0530..76a0d6261 100644 --- a/tests/snapshots/ssa/typedef_in_function_body.ssa +++ b/tests/snapshots/ssa/typedef_in_function_body.ssa @@ -6,8 +6,10 @@ fn ent_pc=0 n_params=0 variadic=false locals=1 v0 AllocaInit(0) -> - v1 Imm(7) -> x0 v2 Imm(0) -> x1 - v3 LoadLocal { off=-1, kind=I32 } -> x1 - terminator Return(v1) (exit_acc=v1) + v3 LoadLocal { off=-1, kind=I64 } -> x1 + v4 BinopI { op=shl, lhs=v1, rhs_imm=32 } -> x1 + v5 Extend { value=v1, kind=I32 } -> x0 + terminator Return(v5) (exit_acc=v5) ; --- SSA dump (ok=true) ent_pc=1 --- ; name=k fn ent_pc=1 n_params=0 variadic=false locals=1 @@ -31,48 +33,50 @@ fn ent_pc=2 n_params=0 variadic=false locals=3 v5 Imm(0) -> x2 v6 Imm(7) -> x2 v7 Imm(0) -> x6 - v8 BinopI { op=ne, lhs=v6, rhs_imm=7 } -> x2 - terminator Bz { cond=v8, target=b2, fall=b1 } (exit_acc=v8) + v8 BinopI { op=shl, lhs=v6, rhs_imm=32 } -> x6 + v9 Extend { value=v6, kind=I32 } -> x2 + v10 BinopI { op=ne, lhs=v9, rhs_imm=7 } -> x2 + terminator Bz { cond=v10, target=b2, fall=b1 } (exit_acc=v10) block 1 start_pc=0 - v9 Imm(1) -> x0 - terminator Return(v9) (exit_acc=v9) + v11 Imm(1) -> x0 + terminator Return(v11) (exit_acc=v11) block 2 start_pc=0 - v10 Imm(5) -> x2 - v11 Imm(0) -> x6 - v12 BinopI { op=ne, lhs=v10, rhs_imm=5 } -> x2 - terminator Bz { cond=v12, target=b4, fall=b3 } (exit_acc=v12) + v12 Imm(5) -> x2 + v13 Imm(0) -> x6 + v14 BinopI { op=ne, lhs=v12, rhs_imm=5 } -> x2 + terminator Bz { cond=v14, target=b4, fall=b3 } (exit_acc=v14) block 3 start_pc=0 - v13 Imm(2) -> x0 - terminator Return(v13) (exit_acc=v13) + v15 Imm(2) -> x0 + terminator Return(v15) (exit_acc=v15) block 4 start_pc=0 - v14 ImmData(8) -> x2 - v15 Load { addr=v14, disp=0, kind=I32 } -> x2 - v16 BinopI { op=ne, lhs=v15, rhs_imm=100 } -> x2 - terminator Bz { cond=v16, target=b6, fall=b5 } (exit_acc=v16) + v16 ImmData(8) -> x2 + v17 Load { addr=v16, disp=0, kind=I32 } -> x2 + v18 BinopI { op=ne, lhs=v17, rhs_imm=100 } -> x2 + terminator Bz { cond=v18, target=b6, fall=b5 } (exit_acc=v18) block 5 start_pc=0 - v17 Imm(3) -> x0 - terminator Return(v17) (exit_acc=v17) + v19 Imm(3) -> x0 + terminator Return(v19) (exit_acc=v19) block 6 start_pc=0 - v18 LoadLocal { off=-1, kind=I32 } -> x2 - v19 BinopI { op=ne, lhs=v1, rhs_imm=2 } -> x2 - terminator Bz { cond=v19, target=b8, fall=b7 } (exit_acc=v19) + v20 LoadLocal { off=-1, kind=I32 } -> x2 + v21 BinopI { op=ne, lhs=v1, rhs_imm=2 } -> x2 + terminator Bz { cond=v21, target=b8, fall=b7 } (exit_acc=v21) block 7 start_pc=0 - v20 Imm(4) -> x0 - terminator Return(v20) (exit_acc=v20) + v22 Imm(4) -> x0 + terminator Return(v22) (exit_acc=v22) block 8 start_pc=0 - v21 LoadLocal { off=-1, kind=I32 } -> x2 - v22 LoadLocal { off=-2, kind=I32 } -> x2 - v23 Binop { op=add, lhs=v1, rhs=v3 } -> x1 - v24 BinopI { op=shl, lhs=v23, rhs_imm=32 } -> x2 - v25 Extend { value=v23, kind=I32 } -> x2 - v26 LoadLocal { off=-3, kind=I32 } -> x2 - v27 Binop { op=add, lhs=v23, rhs=v1 } -> x0 - v28 BinopI { op=shl, lhs=v27, rhs_imm=32 } -> x1 - v29 Extend { value=v27, kind=I32 } -> x1 - v30 BinopI { op=sub, lhs=v27, rhs_imm=5 } -> x0 - v31 BinopI { op=shl, lhs=v30, rhs_imm=32 } -> x1 - v32 Extend { value=v30, kind=I32 } -> x0 - terminator Return(v32) (exit_acc=v32) + v23 LoadLocal { off=-1, kind=I32 } -> x2 + v24 LoadLocal { off=-2, kind=I32 } -> x2 + v25 Binop { op=add, lhs=v1, rhs=v3 } -> x1 + v26 BinopI { op=shl, lhs=v25, rhs_imm=32 } -> x2 + v27 Extend { value=v25, kind=I32 } -> x2 + v28 LoadLocal { off=-3, kind=I32 } -> x2 + v29 Binop { op=add, lhs=v25, rhs=v1 } -> x0 + v30 BinopI { op=shl, lhs=v29, rhs_imm=32 } -> x1 + v31 Extend { value=v29, kind=I32 } -> x1 + v32 BinopI { op=sub, lhs=v29, rhs_imm=5 } -> x0 + v33 BinopI { op=shl, lhs=v32, rhs_imm=32 } -> x1 + v34 Extend { value=v32, kind=I32 } -> x0 + terminator Return(v34) (exit_acc=v34) ; --- SSA dump (ok=true) ent_pc=0 --- ; name=__c5_exit fn ent_pc=0 n_params=1 variadic=false locals=1 diff --git a/tests/snapshots/ssa/typedef_name_as_declarator.ssa b/tests/snapshots/ssa/typedef_name_as_declarator.ssa new file mode 100644 index 000000000..2b7fdc1a5 --- /dev/null +++ b/tests/snapshots/ssa/typedef_name_as_declarator.ssa @@ -0,0 +1,117 @@ +; --- SSA dump (ok=true) ent_pc=0 --- +; name=take +fn ent_pc=0 n_params=1 variadic=false locals=0 + spill_count=0 gpr_used=[] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I64) -> x7 + v2 Imm(0) -> x0 + v3 LoadLocal { off=2, kind=I64 } -> x0 + v4 BinopI { op=shl, lhs=v1, rhs_imm=32 } -> x0 + v5 Extend { value=v1, kind=I32 } -> x0 + terminator Return(v5) (exit_acc=v5) +; --- SSA dump (ok=true) ent_pc=1 --- +; name=main +fn ent_pc=1 n_params=0 variadic=false locals=4 + spill_count=0 gpr_used=[] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 LocalAddr(-2) -> x0 + v2 Imm(40000) -> x1 + v3 Store { addr=v1, disp=0, value=v2, kind=I16 } -> - + v4 LocalAddr(-2) -> x0 + v5 BinopI { op=add, lhs=v4, rhs_imm=8 } -> x1 + v6 Imm(5) -> x1 + v7 Store { addr=v4, disp=8, value=v6, kind=I64 } -> - + v8 Imm(3) -> x0 + v9 Imm(0) -> x1 + v10 Imm(0) -> x1 + terminator Jmp(b2) (exit_acc=v10) + block 1 start_pc=0 + v11 Imm(11) -> x0 + terminator Return(v11) (exit_acc=v11) + block 2 start_pc=0 + v12 Imm(0) -> x1 + terminator Jmp(b4) (exit_acc=v12) + block 3 start_pc=0 + v13 Imm(12) -> x0 + terminator Return(v13) (exit_acc=v13) + block 4 start_pc=0 + v14 LocalAddr(-2) -> x1 + v15 Load { addr=v14, disp=0, kind=U16 } -> x1 + v16 BinopI { op=xor, lhs=v15, rhs_imm=40000 } -> x1 + v17 BinopI { op=and, lhs=v16, rhs_imm=4294967295 } -> x1 + v18 BinopI { op=ne, lhs=v17, rhs_imm=0 } -> x1 + terminator Bz { cond=v18, target=b6, fall=b5 } (exit_acc=v18) + block 5 start_pc=0 + v19 Imm(13) -> x0 + terminator Return(v19) (exit_acc=v19) + block 6 start_pc=0 + v20 LoadLocal { off=-3, kind=U16 } -> x1 + v21 BinopI { op=xor, lhs=v8, rhs_imm=3 } -> x0 + v22 BinopI { op=and, lhs=v21, rhs_imm=4294967295 } -> x0 + v23 BinopI { op=ne, lhs=v22, rhs_imm=0 } -> x0 + terminator Bz { cond=v23, target=b8, fall=b7 } (exit_acc=v23) + block 7 start_pc=0 + v24 Imm(14) -> x0 + terminator Return(v24) (exit_acc=v24) + block 8 start_pc=0 + v25 Imm(7) -> x0 + v26 Imm(0) -> x1 + v27 BinopI { op=shl, lhs=v25, rhs_imm=32 } -> x1 + v28 Extend { value=v25, kind=I32 } -> x0 + v29 BinopI { op=ne, lhs=v28, rhs_imm=7 } -> x0 + terminator Bz { cond=v29, target=b10, fall=b9 } (exit_acc=v29) + block 9 start_pc=0 + v30 Imm(15) -> x0 + terminator Return(v30) (exit_acc=v30) + block 10 start_pc=0 + v31 Imm(0) -> x0 + terminator Return(v31) (exit_acc=v31) +; --- SSA dump (ok=true) ent_pc=0 --- +; name=__c5_exit +fn ent_pc=0 n_params=1 variadic=false locals=1 + spill_count=0 gpr_used=[] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I32) -> x7 + v2 Imm(0) -> x0 + v3 LoadLocal { off=2, kind=I32 } -> x0 + v4 CallExt { binding_idx=0, args=[v1], fp_arg_mask=0x0 } -> x0 + v5 Imm(0) -> x0 + terminator Return(v5) (exit_acc=v5) +; --- SSA dump (ok=true) ent_pc=1 --- +; name=__c5_entry +fn ent_pc=1 n_params=2 variadic=false locals=6 + spill_count=0 gpr_used=[3] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I64) -> x7 + v2 Imm(0) -> x0 + v3 ParamRef(1, kind=I64) -> x6 + v4 Imm(0) -> x0 + v5 LoadLocal { off=3, kind=I64 } -> x0 + v6 BinopI { op=and, lhs=v3, rhs_imm=255 } -> x0 + v7 LoadLocal { off=2, kind=I64 } -> x0 + v8 Imm(0) -> x0 + v9 LoadLocal { off=-1, kind=I64 } -> x0 + v10 Imm(0) -> x3 + v11 Load { addr=v1, disp=0, kind=I64 } -> x0 + v12 BinopI { op=shl, lhs=v11, rhs_imm=32 } -> x1 + v13 Extend { value=v11, kind=I32 } -> x0 + v14 Imm(0) -> x1 + v15 Imm(8) -> x1 + v16 BinopI { op=add, lhs=v1, rhs_imm=8 } -> x6 + v17 Imm(0) -> x1 + v18 ImmData(24) -> x1 + v19 LoadLocal { off=-3, kind=I64 } -> x2 + v20 LoadLocal { off=-2, kind=I32 } -> x2 + v21 BinopI { op=shl, lhs=v13, rhs_imm=3 } -> x2 + v22 Binop { op=add, lhs=v16, rhs=v21 } -> x2 + v23 BinopI { op=add, lhs=v22, rhs_imm=8 } -> x2 + v24 Store { addr=v18, disp=0, value=v23, kind=I64 } -> - + v25 LoadLocal { off=-2, kind=I32 } -> x1 + v26 LoadLocal { off=-3, kind=I64 } -> x1 + v27 Call { target_pc=3, args=[v13, v16], fixed_args=2, fp_return=false, fp_arg_mask=0x0 } -> x7 + v28 Call { target_pc=0, args=[v27], fixed_args=1, fp_return=false, fp_arg_mask=0x0 } -> x0 + terminator Return(v10) (exit_acc=v10)