diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 93bc99229..521331ed7 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -73,6 +73,9 @@ jobs: - name: Run cargo clippy (Cloudflare — wasm32-unknown-unknown) run: cargo clippy-cloudflare-wasm + - name: Check wasm-safe clock usage in core (wasm32-unknown-unknown) + run: scripts/lint-wasm-clock.sh + - name: Run cargo clippy (Spin — native) run: cargo clippy-spin-native diff --git a/AGENTS.md b/AGENTS.md index 738fcab34..34dc5a7b2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -346,6 +346,7 @@ Every PR must pass: 1. `cargo fmt --all -- --check` 2. `cargo clippy-fastly && cargo clippy-axum && cargo clippy-cloudflare && cargo clippy-cloudflare-wasm && cargo clippy-spin-native && cargo clippy-spin-wasm && cargo clippy-cli && cargo clippy-codegen` + Also `scripts/lint-wasm-clock.sh` (core must use `web_time`, not `std::time::{Instant,SystemTime}::now`, which panic on Cloudflare). 3. `cargo test-fastly && cargo test-axum && cargo test-cloudflare && cargo test-spin` 4. `cargo test --manifest-path crates/trusted-server-integration-tests/Cargo.toml --test parity` 5. JS build and test (`cd crates/trusted-server-js/lib && npx vitest run`) diff --git a/crates/trusted-server-core/src/auction/telemetry.rs b/crates/trusted-server-core/src/auction/telemetry.rs index c6e37d02e..ab7de3c32 100644 --- a/crates/trusted-server-core/src/auction/telemetry.rs +++ b/crates/trusted-server-core/src/auction/telemetry.rs @@ -4,12 +4,12 @@ //! adapters provide the concrete sink implementation. use std::collections::HashSet; -use std::time::Instant; use chrono::Utc; use error_stack::Report; use serde::Serialize; use uuid::Uuid; +use web_time::Instant; use crate::auction::orchestrator::OrchestrationResult; use crate::auction::types::{AuctionRequest, AuctionResponse, Bid, BidStatus, MediaType}; diff --git a/crates/trusted-server-core/src/integrations/datadome/protection_scope.rs b/crates/trusted-server-core/src/integrations/datadome/protection_scope.rs index a83f73217..1d3646063 100644 --- a/crates/trusted-server-core/src/integrations/datadome/protection_scope.rs +++ b/crates/trusted-server-core/src/integrations/datadome/protection_scope.rs @@ -2,11 +2,12 @@ use std::collections::{HashMap, HashSet}; use std::net::IpAddr; use std::str::FromStr; use std::sync::{LazyLock, Mutex}; -use std::time::{Duration, Instant}; +use std::time::Duration; use error_stack::Report; use regex::Regex; use serde::Deserialize; +use web_time::Instant; use crate::error::TrustedServerError; use crate::platform::{RuntimeServices, StoreName}; diff --git a/crates/trusted-server-core/src/publisher.rs b/crates/trusted-server-core/src/publisher.rs index 61bc7efe5..01fb04daa 100644 --- a/crates/trusted-server-core/src/publisher.rs +++ b/crates/trusted-server-core/src/publisher.rs @@ -22,7 +22,7 @@ use std::borrow::Cow; use std::io::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant, SystemTime}; +use std::time::{Duration, SystemTime}; use brotli::Decompressor; use brotli::enc::BrotliEncoderParams; @@ -34,6 +34,7 @@ use flate2::read::ZlibDecoder; use flate2::write::{GzEncoder, ZlibEncoder}; use futures::StreamExt as _; use http::{HeaderValue, Method, Request, Response, StatusCode, Uri, header}; +use web_time::Instant; use crate::auction::endpoints::{ merge_auction_eids, resolve_auction_eids, resolve_client_auction_eids, @@ -6462,7 +6463,15 @@ fn origin_shared_ttl( headers: &edgezero_core::http::HeaderMap, max_age: Duration, ) -> Result { - origin_shared_ttl_at(headers, SystemTime::now(), max_age) + // `httpdate` parses `Date`/`Expires` into `std::time::SystemTime`, but + // `std::time::SystemTime::now()` panics on `wasm32-unknown-unknown` + // (Cloudflare Workers). Build the equivalent `std` value from the wasm-safe + // `web_time` clock: `UNIX_EPOCH + elapsed` never calls the panicking `now()`. + let now = SystemTime::UNIX_EPOCH + + web_time::SystemTime::now() + .duration_since(web_time::UNIX_EPOCH) + .unwrap_or_default(); + origin_shared_ttl_at(headers, now, max_age) } fn origin_shared_ttl_at( diff --git a/scripts/clippy-wasm-clock/clippy.toml b/scripts/clippy-wasm-clock/clippy.toml new file mode 100644 index 000000000..eed3f09e5 --- /dev/null +++ b/scripts/clippy-wasm-clock/clippy.toml @@ -0,0 +1,6 @@ +# Dedicated clippy config for scripts/lint-wasm-clock.sh. See that script for +# why this rule cannot live in the workspace-wide clippy.toml. +disallowed-methods = [ + { path = "std::time::Instant::now", reason = "panics on wasm32-unknown-unknown; use `web_time::Instant::now`" }, + { path = "std::time::SystemTime::now", reason = "panics on wasm32-unknown-unknown; use `web_time::SystemTime::now`" }, +] diff --git a/scripts/lint-wasm-clock.sh b/scripts/lint-wasm-clock.sh new file mode 100755 index 000000000..2efc4a223 --- /dev/null +++ b/scripts/lint-wasm-clock.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Fail if trusted-server-core calls std::time::Instant::now or +# std::time::SystemTime::now. Both panic with `RuntimeError: unreachable` on +# wasm32-unknown-unknown (Cloudflare Workers); use `web_time` instead. +# +# This cannot live in the workspace clippy.toml: on native and wasm32-wasip1, +# `web_time::Instant` and `web_time::SystemTime` are re-exports of the std +# types, so clippy would flag the compliant `web_time` calls too. On +# wasm32-unknown-unknown the types are distinct, so only that target can tell +# them apart. The dedicated config dir holds just the disallowed-methods rule. +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +CLIPPY_CONF_DIR="$root/scripts/clippy-wasm-clock" \ + cargo clippy -p trusted-server-core --lib --target wasm32-unknown-unknown -- \ + -A clippy::all -D clippy::disallowed_methods