From f689d2c8f56310238bec8f29c740a42f964da6a1 Mon Sep 17 00:00:00 2001 From: Ryan Pope Date: Sun, 1 Nov 2026 17:43:45 -0600 Subject: [PATCH] build: update toolchains and dependencies - C++: Clang 23 from LLVM's apt repository (was Debian's Clang 19); the repository's signing key is pinned by checksum. - Node 26 LTS for the web build and CI (was 22). - Gson 2.15, TanStack Query 5.105.1, globals 17.14. - Regenerated the excluded test cases with the new toolchain (2,087 cases in 308 problems; +2, -1). TypeScript stays on 5.9: openapi-typescript and typescript-eslint do not support 7 yet. --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 6 +++++- CONTRIBUTING.md | 2 +- README.md | 4 ++-- SECURITY.md | 2 +- backend/Dockerfile | 2 +- backend/localcode/data/excluded_cases.json | 9 ++++---- docs/architecture.md | 2 +- runner/Dockerfile | 19 +++++++++++++---- web/package-lock.json | 24 +++++++++++----------- web/package.json | 4 ++-- 11 files changed, 46 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e276205..d8fdea1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: - node-version: "22" + node-version: "26" cache: npm cache-dependency-path: web/package-lock.json - run: npm ci diff --git a/CHANGELOG.md b/CHANGELOG.md index 05caf1d..fac0a21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,17 @@ All notable changes are documented here. The format follows [Keep a Changelog](h ### Added - First public version: about 4,100 problems (2,830 runnable against 274k hidden tests), company frequency lists for 423 companies, reference solutions with explanations. -- Judging in Python 3.14, Java 25, C++23 (Clang 19) and Kotlin 2.4 in a sandboxed runner with no network access. +- Judging in Python 3.14, Java 25, C++23 (Clang 23) and Kotlin 2.4 in a sandboxed runner with no network access. - Problem list, company pages, workspace with Run/Submit and custom cases, submission history, progress dashboard and export. - One-command setup with Docker Compose; content imported from open datasets at first run, with mirror fallbacks. - Repo-kept content in `content/`: new problems and extra solutions, tests or corrections for existing ones, checked in CI by `make validate-content` and marked "local" in the app. - The Progress page lists every solved problem, with when it was first accepted and in which languages; the solved counts link to the filtered problem list. - **Refresh content** never removes problems, solutions or test cases that an earlier import had. +### Changed + +- Dependencies brought up to date: Clang 23 from LLVM's apt repository (was Debian's Clang 19), Node 26 LTS for the web build and CI (was 22), Gson 2.15, TanStack Query 5.105.1. The excluded test cases were regenerated with the new toolchain. TypeScript stays on 5.9 until openapi-typescript and typescript-eslint support 7. + ### Fixed - `LOCALCODE_ALLOWED_HOSTS` set when starting Docker Compose now reaches the app, so it can be served under another host name as the README describes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d362c8..1acf0a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Everyone taking part is expected to follow the [Code of Conduct](CODE_OF_CONDUCT ## Getting set up -You need Docker (with Compose v2), Python 3.14, Node 22 and `make`. +You need Docker (with Compose v2), Python 3.14, Node 26 and `make`. ```bash git clone https://github.com/RyPope/localcode.git && cd localcode diff --git a/README.md b/README.md index 4237231..19e107b 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ flowchart LR B["Browser
localhost:4173"] --> A subgraph Docker["Docker Compose"] A["app
FastAPI + React UI"] - R["runner
Python 3.14 · JDK 25 · Clang 19 · Kotlin 2.4
no network"] + R["runner
Python 3.14 · JDK 25 · Clang 23 · Kotlin 2.4
no network"] D[("localcode-data volume
content.db · user.db")] A -- "code + test inputs
(Unix socket)" --> R R -- "raw outputs" --> A @@ -162,7 +162,7 @@ docker compose cp app:/data/user.db ./localcode-user-backup.db - **Kotlin reference solutions are sparse upstream**, so the Solutions tab shows Java for most Kotlin problems. Kotlin submissions are judged normally. - **Problems with several valid answers** are judged by checking the answer, not by matching one expected output: 75 problems ("return any valid arrangement", course orders, Gray codes, CIDR covers, …) have a checker that validates the answer against the input, and "in any order" answers are compared ignoring order (at every level where the problem allows it). A multi-answer problem without a checker yet is still compared against one expected answer; please report it. - **Design, SQL, shell and concurrency problems** can be read but not run. -- **C++ is compiled with Clang 19 and `-std=c++23 -O2`** but without AddressSanitizer, so some out-of-bounds bugs may go unnoticed instead of failing with a runtime error. +- **C++ is compiled with Clang 23 and `-std=c++23 -O2`** but without AddressSanitizer, so some out-of-bounds bugs may go unnoticed instead of failing with a runtime error. - **Cold-start compile times** are about 1 to 2 s for Java and C++ and about 4 s for Kotlin. ## Troubleshooting diff --git a/SECURITY.md b/SECURITY.md index fd820d8..e674725 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -33,7 +33,7 @@ The app has no login, so it defends against other web pages instead: ## Supply chain - Dependencies are pinned, and Dependabot proposes updates. -- Toolchain downloads in the runner image (Kotlin, Gson) are verified against pinned SHA-256 checksums. +- Toolchain downloads in the runner image (Kotlin, Gson) are verified against pinned SHA-256 checksums, and the LLVM apt repository that provides Clang is trusted only through its signing key, itself pinned by checksum. - CI actions are pinned to commit SHAs. - Problem content is downloaded at runtime from the configured sources over HTTPS. It is rendered only after sanitisation, and the reference solutions it contains are only ever executed inside the sandbox. diff --git a/backend/Dockerfile b/backend/Dockerfile index c1d68e0..7e44058 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,5 +1,5 @@ # Build context: repository root. -FROM node:22.23.4-slim AS web +FROM node:26.13.0-trixie-slim AS web WORKDIR /web COPY web/package.json web/package-lock.json ./ RUN npm ci --no-audit --no-fund diff --git a/backend/localcode/data/excluded_cases.json b/backend/localcode/data/excluded_cases.json index ec49939..a89f457 100644 --- a/backend/localcode/data/excluded_cases.json +++ b/backend/localcode/data/excluded_cases.json @@ -553,9 +553,7 @@ ], "construct-string-with-minimum-cost-easy": [], "construct-string-with-repeat-limit": [], -"construct-target-array-with-multiple-sums": [ -"e87c1970e8e58b6d" -], +"construct-target-array-with-multiple-sums": [], "construct-the-lexicographically-largest-valid-sequence": [], "construct-the-longest-new-string": [], "construct-the-minimum-bitwise-array-i": [], @@ -752,7 +750,9 @@ "count-pairs-of-connectable-servers-in-a-weighted-tree-network": [], "count-pairs-of-equal-substrings-with-minimum-difference": [], "count-pairs-of-nodes": [], -"count-pairs-of-points-with-distance-k": [], +"count-pairs-of-points-with-distance-k": [ +"72a3d1416e865fc1" +], "count-pairs-of-similar-strings": [], "count-pairs-that-form-a-complete-day-i": [], "count-pairs-that-form-a-complete-day-ii": [], @@ -5154,6 +5154,7 @@ "verify-preorder-sequence-in-binary-search-tree": [], "verify-preorder-serialization-of-a-binary-tree": [], "verifying-an-alien-dictionary": [ +"2fbe222adff16def", "3ce6e99bf3f83090", "63c7a7a504c252b0", "6574a41a9623530d", diff --git a/docs/architecture.md b/docs/architecture.md index 37d0232..7a2d68d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -62,7 +62,7 @@ Each runner replica serves HTTP on `/run/localcode/runner-.sock` (mode 066 |---|---|---| | Python | `harness/py_driver.py` | Converts arguments using `params` types; per-case stdout capture; 128 MB thread stack | | Java / Kotlin | `harness/jvm/Driver.java` | Reflection over the `Solution` method's generic types; Gson; `javafx.util.Pair` provided | -| C++ | `harness/cpp_gen.py` + `cpp/lc.hpp` | Clang 19, `-std=c++23 -O2` (as LeetCode); generated `main` from the C++ signature; precompiled prelude and JSON I/O object; stdout captured at the file-descriptor level | +| C++ | `harness/cpp_gen.py` + `cpp/lc.hpp` | Clang 23, `-std=c++23 -O2`; generated `main` from the C++ signature; precompiled prelude and JSON I/O object; stdout captured at the file-descriptor level | Limits are defined once in `config.Limits` and sent with every request: Run gets 2 s per case and Submit 10 s in total, plus a per-language start-up allowance; memory is 1 GiB, applied as an address-space limit (Python, C++) or a heap of 75% of it (JVM). Output files are capped and the sandbox user is limited to 256 processes. diff --git a/runner/Dockerfile b/runner/Dockerfile index 14bd41e..f8046c4 100644 --- a/runner/Dockerfile +++ b/runner/Dockerfile @@ -1,8 +1,19 @@ # Pinned toolchains: every localcode user judges against exactly these versions. FROM python:3.14.10-slim-trixie AS runtime +# Clang comes from LLVM's own apt repository (Debian's is a few majors behind). Its signing key is +# pinned by checksum; the fingerprint is 6084F3CF814B57C1CF12EFD515CF4D18AF4F7421. +ARG CLANG_MAJOR=23 +ARG LLVM_KEY_SHA256=8b2a587ffd672c4687e7581dad4b2f6c1bb2ad6b480cd9771ba2ff48e0b8c75d RUN apt-get update \ - && apt-get install -y --no-install-recommends clang g++ nlohmann-json3-dev curl unzip ca-certificates \ + && apt-get install -y --no-install-recommends curl ca-certificates \ + && curl -fsSL -o /etc/apt/keyrings/llvm.asc https://apt.llvm.org/llvm-snapshot.gpg.key \ + && echo "${LLVM_KEY_SHA256} /etc/apt/keyrings/llvm.asc" | sha256sum -c - \ + && echo "deb [signed-by=/etc/apt/keyrings/llvm.asc] https://apt.llvm.org/trixie/ llvm-toolchain-trixie-${CLANG_MAJOR} main" \ + > /etc/apt/sources.list.d/llvm.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends clang-${CLANG_MAJOR} g++ nlohmann-json3-dev unzip \ + && ln -s /usr/bin/clang++-${CLANG_MAJOR} /usr/local/bin/clang++ \ && rm -rf /var/lib/apt/lists/* COPY --from=eclipse-temurin:25.0.5_8-jdk /opt/java/openjdk /opt/java @@ -15,8 +26,8 @@ RUN curl -fsSL -o /tmp/k.zip https://github.com/JetBrains/kotlin/releases/downlo && echo "${KOTLIN_SHA256} /tmp/k.zip" | sha256sum -c - \ && unzip -q /tmp/k.zip -d /opt && rm /tmp/k.zip -ARG GSON_VERSION=2.11.0 -ARG GSON_SHA256=57928d6e5a6edeb2abd3770a8f95ba44dce45f3b23b7a9dc2b309c581552a78b +ARG GSON_VERSION=2.15.0 +ARG GSON_SHA256=96d3dd843dc2911de01cdabcd50064cb9e860353102a0a78c96fd833d396fc7d RUN mkdir -p /opt/lib \ && curl -fsSL -o /opt/lib/gson.jar https://repo1.maven.org/maven2/com/google/code/gson/gson/${GSON_VERSION}/gson-${GSON_VERSION}.jar \ && echo "${GSON_SHA256} /opt/lib/gson.jar" | sha256sum -c - @@ -31,7 +42,7 @@ RUN mkdir -p /tmp/drv \ && javac -cp /opt/lib/gson.jar -d /tmp/drv harness/jvm/Driver.java harness/jvm/javafx/util/Pair.java \ && jar cf /opt/lib/driver.jar -C /tmp/drv . && rm -rf /tmp/drv -# C++ is compiled with Clang (as LeetCode does) against libstdc++ (installed with g++). +# C++ is compiled with Clang against libstdc++ (installed with g++). # Precompiled prelude; flags must match CPP_FLAGS in languages.py. RUN mkdir -p /opt/lc && cp harness/cpp/lc.hpp harness/cpp/lc_io.cpp /opt/lc/ \ && clang++ -std=c++23 -O2 -w -x c++-header /opt/lc/lc.hpp -o /opt/lc/lc.hpp.pch \ diff --git a/web/package-lock.json b/web/package-lock.json index 03c1ece..bc7878b 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -11,7 +11,7 @@ "@fontsource/geist-mono": "^5.3.0", "@fontsource/geist-sans": "^5.3.0", "@monaco-editor/react": "4.7.0", - "@tanstack/react-query": "5.105.0", + "@tanstack/react-query": "5.105.1", "dompurify": "^3.4.17", "katex": "^0.19.1", "marked": "18.1.2", @@ -29,7 +29,7 @@ "eslint": "^10.14.0", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.7", - "globals": "^17.13.0", + "globals": "^17.14.0", "openapi-typescript": "^7.13.0", "playwright": "^1.64.1", "prettier": "^3.9.10", @@ -961,9 +961,9 @@ "license": "MIT" }, "node_modules/@tanstack/query-core": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.105.0.tgz", - "integrity": "sha512-7p56vRmT/z4UJ6sctGIdl5WRUQyUv53IUpUwEKi2g97zMbO7zh8ZsCLffGAvCPBR4xD72e3VXsDyLwXHhoNTMQ==", + "version": "5.105.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.105.1.tgz", + "integrity": "sha512-B/rNlZoZ54w1dlddiUt9oEe1SLM+37c00SKLJEg9YH+7as3Y54P81o9Fh+2ujZ36tXPnQJ5oVe7AcyEWkyFhPg==", "license": "MIT", "funding": { "type": "github", @@ -971,12 +971,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.105.0.tgz", - "integrity": "sha512-y+hMuDERvgg6ay1da0V1a6ZC0ZONt7dELxSFkqRZuwm7p1TTZs0MoXrPlBRflIpRoYjvzQVYxycnLImpFLhWrw==", + "version": "5.105.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.105.1.tgz", + "integrity": "sha512-qcu+/+PtBOjW8qU4/0l47QVmNG7hPOWsCLzP0bTRhE1X7HYa757C4732wXTUH0QhCjTV1yedX8onsCiEwlNYrA==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.105.0" + "@tanstack/query-core": "5.105.1" }, "funding": { "type": "github", @@ -2050,9 +2050,9 @@ } }, "node_modules/globals": { - "version": "17.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz", - "integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==", + "version": "17.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.14.0.tgz", + "integrity": "sha512-UtdkTVyAJePQv0J2M9Xe4L0ruNSDV314XiIJsADI2Zf40bcdAonE/D/OhAU8AT99oij9mhLByb+NvA3QWZYgCg==", "dev": true, "license": "MIT", "engines": { diff --git a/web/package.json b/web/package.json index 6490d15..af9e7ff 100644 --- a/web/package.json +++ b/web/package.json @@ -19,7 +19,7 @@ "@fontsource/geist-mono": "^5.3.0", "@fontsource/geist-sans": "^5.3.0", "@monaco-editor/react": "4.7.0", - "@tanstack/react-query": "5.105.0", + "@tanstack/react-query": "5.105.1", "dompurify": "^3.4.17", "katex": "^0.19.1", "marked": "18.1.2", @@ -37,7 +37,7 @@ "eslint": "^10.14.0", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.7", - "globals": "^17.13.0", + "globals": "^17.14.0", "openapi-typescript": "^7.13.0", "playwright": "^1.64.1", "prettier": "^3.9.10",