From 1e23b1420bb6ded4791d6f2d9b7116a6ad30eebb Mon Sep 17 00:00:00 2001 From: Adrian Niculescu <15037449+adrian-niculescu@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:57:45 +0300 Subject: [PATCH] fix(worker): throw when an environment-data key cannot be converted to a string setEnvironmentData and getEnvironmentData converted the key with a helper that swallows a throwing toString() and returns an empty string, so such a key silently read, overwrote or deleted the unrelated empty-string entry. The conversion is checked now and the exception reaches the caller. --- .../main/assets/app/tests/testMessaging.js | 46 +++++++++++++++++++ test-app/runtime/src/main/cpp/Messaging.cpp | 21 ++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/test-app/app/src/main/assets/app/tests/testMessaging.js b/test-app/app/src/main/assets/app/tests/testMessaging.js index c06a46037..375f8aadf 100644 --- a/test-app/app/src/main/assets/app/tests/testMessaging.js +++ b/test-app/app/src/main/assets/app/tests/testMessaging.js @@ -204,6 +204,52 @@ describe("Messaging runtime edges", function () { }); }); + describe("environment data keys", function () { + it("keeps keys containing NUL distinct from their prefixes", function () { + var wt = require("node:worker_threads"); + var prefix = "environment-key"; + var key = prefix + "\0suffix"; + try { + wt.setEnvironmentData(prefix, "prefix"); + wt.setEnvironmentData(key, "full key"); + expect(wt.getEnvironmentData(prefix)).toBe("prefix"); + expect(wt.getEnvironmentData(key)).toBe("full key"); + wt.setEnvironmentData(key); + expect(wt.getEnvironmentData(key)).toBeUndefined(); + expect(wt.getEnvironmentData(prefix)).toBe("prefix"); + } finally { + wt.setEnvironmentData(key); + wt.setEnvironmentData(prefix); + } + }); + + function thrownMessage(fn) { + try { + fn(); + } catch (e) { + return e && e.message; + } + return "nothing thrown"; + } + + it("throws what a key's toString throws instead of using the empty-string key", function () { + var wt = require("node:worker_threads"); + var key = { toString: function () { throw new Error("key toString failed"); } }; + wt.setEnvironmentData("", "empty"); + try { + expect(thrownMessage(function () { wt.setEnvironmentData(key, "other"); })) + .toBe("key toString failed"); + expect(thrownMessage(function () { wt.getEnvironmentData(key); })) + .toBe("key toString failed"); + expect(thrownMessage(function () { wt.setEnvironmentData(key); })) + .toBe("key toString failed"); + expect(wt.getEnvironmentData("")).toBe("empty"); + } finally { + wt.setEnvironmentData(""); + } + }); + }); + describe("worker error reporting", function () { // A worker boots on its own thread, so the first error arrives whenever // the runner gets to it; specs wait for it and only then settle for diff --git a/test-app/runtime/src/main/cpp/Messaging.cpp b/test-app/runtime/src/main/cpp/Messaging.cpp index 0d0f873f1..311f7d757 100644 --- a/test-app/runtime/src/main/cpp/Messaging.cpp +++ b/test-app/runtime/src/main/cpp/Messaging.cpp @@ -935,13 +935,27 @@ void SetEmitMessageCallback(const FunctionCallbackInfo& info) { state->emitMessage.Reset(isolate, info[0].As()); } +// The key as the string the store files it under. A key whose conversion throws +// leaves that exception pending for the caller rather than standing in for "". +static bool EnvironmentDataKey(Isolate* isolate, Local value, std::string& key) { + Local str; + if (!value->ToString(isolate->GetCurrentContext()).ToLocal(&str)) { + return false; + } + key = ArgConverter::ToString(isolate, str); + return true; +} + void SetEnvironmentDataCallback(const FunctionCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); if (info.Length() < 1) { return; } Local context = isolate->GetCurrentContext(); - std::string key = ArgConverter::ToString(isolate, info[0]); + std::string key; + if (!EnvironmentDataKey(isolate, info[0], key)) { + return; + } if (info.Length() < 2 || info[1]->IsUndefined()) { std::lock_guard lock(g_environmentDataMutex); g_environmentData.erase(key); @@ -964,7 +978,10 @@ void GetEnvironmentDataCallback(const FunctionCallbackInfo& info) { if (info.Length() < 1) { return; } - std::string key = ArgConverter::ToString(isolate, info[0]); + std::string key; + if (!EnvironmentDataKey(isolate, info[0], key)) { + return; + } std::shared_ptr stored; { std::lock_guard lock(g_environmentDataMutex);