Repository: nodejs/node
Pull request #57706: src: improve StringBytes error handling
Author: login: jasnell
Labels: name: c++
description: Issues and PRs that require attention from people who are familiar with C++.
name: lib / src
description: Issues and PRs related to general changes in the lib or src directory.
name: author ready
description: PRs that have at least one approval, no pending requests for changes, and a CI started.
name: needs-ci
description: PRs that need a full CI run.
name: backport-requested-v22.x
description: PRs awaiting manual backport to the v22.x-staging branch.
Created: 2025-04-01T13:41:23Z | Merged: 2025-04-06T17:19:44Z

Improve error handling and the signature of `StringBytes::Encode(...)`.

Also, mark the `node::Encode(...)` C++ APIs as deprecated since those do not propagate errors correctly. Added `node::TryEncode(...)` replacements that properly return `MaybeLocal<String>` and propagate errors.


Diff:
diff --git a/src/api/encoding.cc b/src/api/encoding.cc
index 0bc3b23a344fa8..52f41980507bcb 100644
--- a/src/api/encoding.cc
+++ b/src/api/encoding.cc
@@ -8,6 +8,8 @@ namespace node {
 using v8::HandleScope;
 using v8::Isolate;
 using v8::Local;
+using v8::MaybeLocal;
+using v8::TryCatch;
 using v8::Value;
 
 enum encoding ParseEncoding(const char* encoding,
@@ -133,20 +135,30 @@ enum encoding ParseEncoding(Isolate* isolate,
   return ParseEncoding(*encoding, default_encoding);
 }
 
+MaybeLocal<Value> TryEncode(Isolate* isolate,
+                            const char* buf,
+                            size_t len,
+                            enum encoding encoding) {
+  CHECK_NE(encoding, UCS2);
+  return StringBytes::Encode(isolate, buf, len, encoding);
+}
+
+MaybeLocal<Value> TryEncode(Isolate* isolate, const uint16_t* buf, size_t len) {
+  return StringBytes::Encode(isolate, buf, len).ToLocalChecked();
+}
+
 Local<Value> Encode(Isolate* isolate,
                     const char* buf,
                     size_t len,
                     enum encoding encoding) {
   CHECK_NE(encoding, UCS2);
-  Local<Value> error;
-  return StringBytes::Encode(isolate, buf, len, encoding, &error)
-      .ToLocalChecked();
+  TryCatch try_catch(isolate);
+  return StringBytes::Encode(isolate, buf, len, encoding).ToLocalChecked();
 }
 
 Local<Value> Encode(Isolate* isolate, const uint16_t* buf, size_t len) {
-  Local<Value> error;
-  return StringBytes::Encode(isolate, buf, len, &error)
-      .ToLocalChecked();
+  TryCatch try_catch(isolate);
+  return StringBytes::Encode(isolate, buf, len).ToLocalChecked();
 }
 
 // Returns -1 if the handle was not valid for decoding
diff --git a/src/crypto/crypto_ec.cc b/src/crypto/crypto_ec.cc
index 6c4ef342425a74..1d879f569a8055 100644
--- a/src/crypto/crypto_ec.cc
+++ b/src/crypto/crypto_ec.cc
@@ -800,17 +800,11 @@ bool ExportJWKEdKey(Environment* env,
                                        Local<Object> target,
                                        Local<String> key) {
     Local<Value> encoded;
-    Local<Value> error;
     if (!data) return false;
     const ncrypto::Buffer<const char> out = data;
-    if (!StringBytes::Encode(
-             env->isolate(), out.data, out.len, BASE64URL, &error)
-             .ToLocal(&encoded) ||
-        target->Set(env->context(), key, encoded).IsNothing()) {
-      if (!error.IsEmpty()) env->isolate()->ThrowException(error);
-      return false;
-    }
-    return true;
+    return StringBytes::Encode(env->isolate(), out.data, out.len, BASE64URL)
+               .ToLocal(&encoded) &&
+           target->Set(env->context(), key, encoded).IsJust();
   };
 
   return !(
diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc
index 52cda485aaa8da..590b343dbf6728 100644
--- a/src/crypto/crypto_hash.cc
+++ b/src/crypto/crypto_hash.cc
@@ -252,19 +252,14 @@ void Hash::OneShotDigest(const FunctionCallbackInfo<Value>& args) {
     return ThrowCryptoError(env, ERR_get_error());
   }
 
-  Local<Value> error;
-  MaybeLocal<Value> rc =
-      StringBytes::Encode(env->isolate(),
+  Local<Value> ret;
+  if (StringBytes::Encode(env->isolate(),
                           static_cast<const char*>(output.get()),
                           output.size(),
-                          output_enc,
-                          &error);
-  if (rc.IsEmpty()) [[unlikely]] {
-    CHECK(!error.IsEmpty());
-    env->isolate()->ThrowException(error);
-    return;
+                          output_enc)
+          .ToLocal(&ret)) {
+    args.GetReturnValue().Set(ret);
   }
-  args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
 }
 
 void Hash::Initialize(Environment* env, Local<Object> target) {
@@ -410,15 +405,12 @@ void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
     hash->digest_ = ByteSource::Allocated(data.release());
   }
 
-  Local<Value> error;
-  MaybeLocal<Value> rc = StringBytes::Encode(
-      env->isolate(), hash->digest_.data<char>(), len, encoding, &error);
-  if (rc.IsEmpty()) [[unlikely]] {
-    CHECK(!error.IsEmpty());
-    env->isolate()->ThrowException(error);
-    return;
+  Local<Value> ret;
+  if (StringBytes::Encode(
+          env->isolate(), hash->digest_.data<char>(), len, encoding)
+          .ToLocal(&ret)) {
+    args.GetReturnValue().Set(ret);
   }
-  args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
 }
 
 HashConfig::HashConfig(HashConfig&& other) noexcept
@@ -541,19 +533,14 @@ void InternalVerifyIntegrity(const v8::FunctionCallbackInfo<v8::Value>& args) {
 
   if (digest_size != expected.size() ||
       CRYPTO_memcmp(digest, expected.data(), digest_size) != 0) {
-    Local<Value> error;
-    MaybeLocal<Value> rc =
-        StringBytes::Encode(env->isolate(),
+    Local<Value> ret;
+    if (StringBytes::Encode(env->isolate(),
                             reinterpret_cast<const char*>(digest),
                             digest_size,
-                            BASE64,
-                            &error);
-    if (rc.IsEmpty()) [[unlikely]] {
-      CHECK(!error.IsEmpty());
-      env->isolate()->ThrowException(error);
-      return;
+                            BASE64)
+            .ToLocal(&ret)) {
+      args.GetReturnValue().Set(ret);
     }
-    args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
   }
 }
 }  // namespace crypto
diff --git a/src/crypto/crypto_hmac.cc b/src/crypto/crypto_hmac.cc
index e37d919808c994..f804e6ef3a8a9c 100644
--- a/src/crypto/crypto_hmac.cc
+++ b/src/crypto/crypto_hmac.cc
@@ -145,19 +145,14 @@ void Hmac::HmacDigest(const FunctionCallbackInfo<Value>& args) {
     hmac->ctx_.reset();
   }
 
-  Local<Value> error;
-  MaybeLocal<Value> rc =
-      StringBytes::Encode(env->isolate(),
+  Local<Value> ret;
+  if (StringBytes::Encode(env->isolate(),
                           reinterpret_cast<const char*>(md_value),
                           buf.len,
-                          encoding,
-                          &error);
-  if (rc.IsEmpty()) [[unlikely]] {
-    CHECK(!error.IsEmpty());
-    env->isolate()->ThrowException(error);
-    return;
+                          encoding)
+          .ToLocal(&ret)) {
+    args.GetReturnValue().Set(ret);
   }
-  args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
 }
 
 HmacConfig::HmacConfig(HmacConfig&& other) noexcept
diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc
index db002875d2657b..190975d9a19d86 100644
--- a/src/crypto/crypto_keys.cc
+++ b/src/crypto/crypto_keys.cc
@@ -130,20 +130,13 @@ bool ExportJWKSecretKey(Environment* env,
                         Local<Object> target) {
   CHECK_EQ(key.GetKeyType(), kKeyTypeSecret);
 
-  Local<Value> error;
   Local<Value> raw;
-  if (!StringBytes::Encode(env->isolate(),
-                           key.GetSymmetricKey(),
-                           key.GetSymmetricKeySize(),
-                           BASE64URL,
-                           &error)
-           .ToLocal(&raw)) {
-    DCHECK(!error.IsEmpty());
-    env->isolate()->ThrowException(error);
-    return false;
-  }
-
-  return target
+  return StringBytes::Encode(env->isolate(),
+                             key.GetSymmetricKey(),
+                             key.GetSymmetricKeySize(),
+                             BASE64URL)
+             .ToLocal(&raw) &&
+         target
              ->Set(env->context(), env->jwk_kty_string(), env->jwk_oct_string())
              .IsJust() &&
          target->Set(env->context(), env->jwk_k_string(), raw).IsJust();
diff --git a/src/crypto/crypto_spkac.cc b/src/crypto/crypto_spkac.cc
index ba085226984767..e33f24b46d6d4f 100644
--- a/src/crypto/crypto_spkac.cc
+++ b/src/crypto/crypto_spkac.cc
@@ -5,6 +5,7 @@
 #include "memory_tracker-inl.h"
 #include "ncrypto.h"
 #include "node.h"
+#include "string_bytes.h"
 #include "v8.h"
 
 namespace node {
@@ -52,18 +53,22 @@ void ExportChallenge(const FunctionCallbackInfo<Value>& args) {
   ArrayBufferOrViewContents<char> input(args[0]);
   if (input.empty()) return args.GetReturnValue().SetEmptyString();
 
-  if (!input.CheckSizeInt32()) [[unlikely]]
+  if (!input.CheckSizeInt32()) [[unlikely]] {
     return THROW_ERR_OUT_OF_RANGE(env, "spkac is too large");
+  }
 
   auto cert = ByteSource::Allocated(
       ncrypto::ExportChallenge(input.data(), input.size()));
-  if (!cert)
+  if (!cert) {
     return args.GetReturnValue().SetEmptyString();
-
-  Local<Value> outString =
-      Encode(env->isolate(), cert.data<char>(), cert.size(), BUFFER);
-
-  args.GetReturnValue().Set(outString);
+  }
+
+  Local<Value> outString;
+  if (StringBytes::Encode(
+          env->isolate(), cert.data<char>(), cert.size(), BUFFER)
+          .ToLocal(&outString)) {
+    args.GetReturnValue().Set(outString);
+  }
 }
 
 void Initialize(Environment* env, Local<Object> target) {
diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc
index 55cd7382eb7802..44036dd6b788c3 100644
--- a/src/crypto/crypto_util.cc
+++ b/src/crypto/crypto_util.cc
@@ -36,6 +36,7 @@ using v8::ArrayBuffer;
 using v8::BackingStore;
 using v8::BigInt;
 using v8::Context;
+using v8::EscapableHandleScope;
 using v8::Exception;
 using v8::FunctionCallbackInfo;
 using v8::HandleScope;
@@ -605,21 +606,18 @@ void SetEngine(const FunctionCallbackInfo<Value>& args) {
 #endif  // !OPENSSL_NO_ENGINE
 
 MaybeLocal<Value> EncodeBignum(Environment* env, const BIGNUM* bn, int size) {
+  EscapableHandleScope scope(env->isolate());
   auto buf = BignumPointer::EncodePadded(bn, size);
   CHECK_EQ(buf.size(), static_cast<size_t>(size));
   Local<Value> ret;
-  Local<Value> error;
   if (!StringBytes::Encode(env->isolate(),
                            reinterpret_cast<const char*>(buf.get()),
                            buf.size(),
-                           BASE64URL,
-                           &error)
+                           BASE64URL)
            .ToLocal(&ret)) {
-    CHECK(!error.IsEmpty());
-    env->isolate()->ThrowException(error);
-    return MaybeLocal<Value>();
+    return {};
   }
-  return ret;
+  return scope.Escape(ret);
 }
 
 Maybe<void> SetEncodedValue(Environment* env,
diff --git a/src/encoding_binding.cc b/src/encoding_binding.cc
index 61d692dfe5ffa0..3cba0e932ed6c4 100644
--- a/src/encoding_binding.cc
+++ b/src/encoding_binding.cc
@@ -183,17 +183,10 @@ void BindingData::DecodeUTF8(const FunctionCallbackInfo<Value>& args) {
 
   if (length == 0) return args.GetReturnValue().SetEmptyString();
 
-  Local<Value> error;
   Local<Value> ret;
-
-  if (!StringBytes::Encode(env->isolate(), data, length, UTF8, &error)
-           .ToLocal(&ret)) {
-    CHECK(!error.IsEmpty());
-    env->isolate()->ThrowException(error);
-    return;
+  if (StringBytes::Encode(env->isolate(), data, length, UTF8).ToLocal(&ret)) {
+    args.GetReturnValue().Set(ret);
   }
-
-  args.GetReturnValue().Set(ret);
 }
 
 void BindingData::ToASCII(const FunctionCallbackInfo<Value>& args) {
diff --git a/src/fs_event_wrap.cc b/src/fs_event_wrap.cc
index 400a84af1ff3c3..6d954e3de17c86 100644
--- a/src/fs_event_wrap.cc
+++ b/src/fs_event_wrap.cc
@@ -44,6 +44,7 @@ using v8::PropertyAttribute;
 using v8::ReadOnly;
 using v8::Signature;
 using v8::String;
+using v8::TryCatch;
 using v8::Value;
 
 namespace {
@@ -217,18 +218,19 @@ void FSEventWrap::OnEvent(uv_fs_event_t* handle, const char* filename,
   };
 
   if (filename != nullptr) {
-    Local<Value> error;
-    MaybeLocal<Value> fn = StringBytes::Encode(env->isolate(),
-                                               filename,
-                                               wrap->encoding_,
-                                               &error);
+    // TODO(@jasnell): Historically, this code has failed to correctly
+    // propagate any error returned by the StringBytes::Encode method,
+    // and would instead just crash the process. That behavior is preserved
+    // here but should be looked at. Preferrably errors would be handled
+    // correctly here.
+    TryCatch try_catch(env->isolate());
+    MaybeLocal<Value> fn =
+        StringBytes::Encode(env->isolate(), filename, wrap->encoding_);
     if (fn.IsEmpty()) {
       argv[0] = Integer::New(env->isolate(), UV_EINVAL);
-      argv[2] = StringBytes::Encode(env->isolate(),
-                                    filename,
-                                    strlen(filename),
-                                    BUFFER,
-                                    &error).ToLocalChecked();
+      argv[2] = StringBytes::Encode(
+                    env->isolate(), filename, strlen(filename), BUFFER)
+                    .ToLocalChecked();
     } else {
       argv[2] = fn.ToLocalChecked();
     }
diff --git a/src/node.h b/src/node.h
index d0163523f08d84..6031012a04f29e 100644
--- a/src/node.h
+++ b/src/node.h
@@ -1123,16 +1123,37 @@ NODE_EXTERN enum encoding ParseEncoding(
 NODE_EXTERN void FatalException(v8::Isolate* isolate,
                                 const v8::TryCatch& try_catch);
 
-NODE_EXTERN v8::Local<v8::Value> Encode(v8::Isolate* isolate,
-                                        const char* buf,
-                                        size_t len,
-                                        enum encoding encoding = LATIN1);
+NODE_EXTERN v8::MaybeLocal<v8::Value> TryEncode(
+    v8::Isolate* isolate,
+    const char* buf,
+    size_t len,
+    enum encoding encoding = LATIN1);
+
+// Warning: This reverses endianness on Big Endian platforms, even though the
+// signature using uint16_t implies that it should not.
+NODE_EXTERN v8::MaybeLocal<v8::Value> TryEncode(v8::Isolate* isolate,
+                                                const uint16_t* buf,
+                                                size_t len);
+
+// The original Encode(...) functions are deprecated because they do not
+// appropriately propagate exceptions and instead rely on ToLocalChecked()
+// which crashes the process if an exception occurs. We cannot just remove
+// these as it would break ABI compatibility, so we keep them around but
+// deprecate them in favor of the TryEncode(...) variations which return
+// a MaybeLocal<> and do not crash the process if an exception occurs.
+NODE_DEPRECATED(
+    "Use TryEncode(...) instead",
+    NODE_EXTERN v8::Local<v8::Value> Encode(v8::Isolate* isolate,
+                                            const char* buf,
+                                            size_t len,
+                                            enum encoding encoding = LATIN1));
 
 // Warning: This reverses endianness on Big Endian platforms, even though the
 // signature using uint16_t implies that it should not.
-NODE_EXTERN v8::Local<v8::Value> Encode(v8::Isolate* isolate,
-                                        const uint16_t* buf,
-                                        size_t len);
+NODE_DEPRECATED("Use TryEncode(...) instead",
+                NODE_EXTERN v8::Local<v8::Value> Encode(v8::Isolate* isolate,
+                                                        const uint16_t* buf,
+                                                        size_t len));
 
 // Returns -1 if the handle was not valid for decoding
 NODE_EXTERN ssize_t DecodeBytes(v8::Isolate* isolate,
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
index 0f249f1947a393..36af659a5339a1 100644
--- a/src/node_buffer.cc
+++ b/src/node_buffer.cc
@@ -544,20 +544,11 @@ void StringSlice(const FunctionCallbackInfo<Value>& args) {
   THROW_AND_RETURN_IF_OOB(Just(end <= buffer.length()));
   size_t length = end - start;
 
-  Local<Value> error;
-  MaybeLocal<Value> maybe_ret =
-      StringBytes::Encode(isolate,
-                          buffer.data() + start,
-                          length,
-                          encoding,
-                          &error);
   Local<Value> ret;
-  if (!maybe_ret.ToLocal(&ret)) {
-    CHECK(!error.IsEmpty());
-    isolate->ThrowException(error);
-    return;
+  if (StringBytes::Encode(isolate, buffer.data() + start, length, encoding)
+          .ToLocal(&ret)) {
+    args.GetReturnValue().Set(ret);
   }
-  args.GetReturnValue().Set(ret);
 }
 
 // Assume caller has properly validated args.
diff --git a/src/node_dir.cc b/src/node_dir.cc
index 2dfb1305a18909..c9173d404c79a6 100644
--- a/src/node_dir.cc
+++ b/src/node_dir.cc
@@ -42,6 +42,7 @@ using v8::Null;
 using v8::Number;
 using v8::Object;
 using v8::ObjectTemplate;
+using v8::TryCatch;
 using v8::Value;
 
 static const char* get_dir_func_name_by_type(uv_fs_type req_type) {
@@ -206,27 +207,20 @@ void DirHandle::Close(const FunctionCallbackInfo<Value>& args) {
   }
 }
 
-static MaybeLocal<Array> DirentListToArray(
-    Environment* env,
-    uv_dirent_t* ents,
-    int num,
-    enum encoding encoding,
-    Local<Value>* err_out) {
+static MaybeLocal<Array> DirentListToArray(Environment* env,
+                                           uv_dirent_t* ents,
+                                           int num,
+                                           enum encoding encoding) {
   MaybeStackBuffer<Local<Value>, 64> entries(num * 2);
 
   // Return an array of all read filenames.
   int j = 0;
   for (int i = 0; i < num; i++) {
     Local<Value> filename;
-    Local<Value> error;
     const size_t namelen = strlen(ents[i].name);
-    if (!StringBytes::Encode(env->isolate(),
-                             ents[i].name,
-                             namelen,
-                             encoding,
-                             &error).ToLocal(&filename)) {
-      *err_out = error;
-      return MaybeLocal<Array>();
+    if (!StringBytes::Encode(env->isolate(), ents[i].name, namelen, encoding)
+             .ToLocal(&filename)) {
+      return {};
     }
 
     entries[j++] = filename;
@@ -258,18 +252,18 @@ static void AfterDirRead(uv_fs_t* req) {
 
   uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
 
-  Local<Value> error;
+  TryCatch try_catch(isolate);
   Local<Array> js_array;
   if (!DirentListToArray(env,
                          dir->dirents,
                          static_cast<int>(req->result),
-                         req_wrap->encoding(),
-                         &error)
+                         req_wrap->encoding())
            .ToLocal(&js_array)) {
     // Clear libuv resources *before* delivering results to JS land because
     // that can schedule another operation on the same uv_dir_t. Ditto below.
     after.Clear();
-    return req_wrap->Reject(error);
+    CHECK(try_catch.CanContinue());
+    return req_wrap->Reject(try_catch.Exception());
   }
 
   after.Clear();
@@ -320,16 +314,16 @@ void DirHandle::Read(const FunctionCallbackInfo<Value>& args) {
 
     CHECK_GE(req_wrap_sync.req.result, 0);
 
-    Local<Value> error;
+    TryCatch try_catch(isolate);
     Local<Array> js_array;
     if (!DirentListToArray(env,
                            dir->dir()->dirents,
                            static_cast<int>(req_wrap_sync.req.result),
-                           encoding,
-                           &error)
+                           encoding)
              .ToLocal(&js_array)) {
       // TODO(anonrig): Initializing BufferValue here is wasteful.
-      BufferValue error_payload(isolate, error);
+      CHECK(try_catch.CanContinue());
+      BufferValue error_payload(isolate, try_catch.Exception());
       env->ThrowError(error_payload.out());
       return;
     }
diff --git a/src/node_file.cc b/src/node_file.cc
index d7cca08521c1c6..fc8aa6101a45ad 100644
--- a/src/node_file.cc
+++ b/src/node_file.cc
@@ -82,6 +82,7 @@ using v8::Object;
 using v8::ObjectTemplate;
 using v8::Promise;
 using v8::String;
+using v8::TryCatch;
 using v8::Undefined;
 using v8::Value;
 
@@ -845,11 +846,13 @@ void AfterMkdirp(uv_fs_t* req) {
     if (first_path.empty())
       return req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
     Local<Value> path;
-    Local<Value> error;
-    if (!StringBytes::Encode(req_wrap->env()->isolate(), first_path.c_str(),
-                             req_wrap->encoding(),
-                             &error).ToLocal(&path)) {
-      return req_wrap->Reject(error);
+    TryCatch try_catch(req_wrap->env()->isolate());
+    if (!StringBytes::Encode(req_wrap->env()->isolate(),
+                             first_path.c_str(),
+                             req_wrap->encoding())
+             .ToLocal(&path)) {
+      CHECK(try_catch.CanContinue());
+      return req_wrap->Reject(try_catch.Exception());
     }
     return req_wrap->Resolve(path);
   }
@@ -861,15 +864,14 @@ void AfterStringPath(uv_fs_t* req) {
   FS_ASYNC_TRACE_END1(
       req->fs_type, req_wrap, "result", static_cast<int>(req->result))
   MaybeLocal<Value> link;
-  Local<Value> error;
 
   if (after.Proceed()) {
-    link = StringBytes::Encode(req_wrap->env()->isolate(),
-                               req->path,
-                               req_wrap->encoding(),
-                               &error);
+    TryCatch try_catch(req_wrap->env()->isolate());
+    link = StringBytes::Encode(
+        req_wrap->env()->isolate(), req->path, req_wrap->encoding());
     if (link.IsEmpty()) {
-      req_wrap->Reject(error);
+      CHECK(try_catch.CanContinue());
+      req_wrap->Reject(try_catch.Exception());
     } else {
       Local<Value> val;
       if (link.ToLocal(&val)) req_wrap->Resolve(val);
@@ -883,15 +885,15 @@ void AfterStringPtr(uv_fs_t* req) {
   FS_ASYNC_TRACE_END1(
       req->fs_type, req_wrap, "result", static_cast<int>(req->result))
   MaybeLocal<Value> link;
-  Local<Value> error;
 
   if (after.Proceed()) {
+    TryCatch try_catch(req_wrap->env()->isolate());
     link = StringBytes::Encode(req_wrap->env()->isolate(),
                                static_cast<const char*>(req->ptr),
-                               req_wrap->encoding(),
-                               &error);
+                               req_wrap->encoding());
     if (link.IsEmpty()) {
-      req_wrap->Reject(error);
+      CHECK(try_catch.CanContinue());
+      req_wrap->Reject(try_catch.Exception());
     } else {
       Local<Value> val;
       if (link.ToLocal(&val)) req_wrap->Resolve(val);
@@ -910,7 +912,6 @@ void AfterScanDir(uv_fs_t* req) {
 
   Environment* env = req_wrap->env();
   Isolate* isolate = env->isolate();
-  Local<Value> error;
   int r;
 
   LocalVector<Value> name_v(isolate);
@@ -930,9 +931,11 @@ void AfterScanDir(uv_fs_t* req) {
     }
 
     Local<Value> filename;
-    if (!StringBytes::Encode(isolate, ent.name, req_wrap->encoding(), &error)
+    TryCatch try_catch(isolate);
+    if (!StringBytes::Encode(isolate, ent.name, req_wrap->encoding())
              .ToLocal(&filename)) {
-      return req_wrap->Reject(error);
+      CHECK(try_catch.CanContinue());
+      return req_wrap->Reject(try_catch.Exception());
     }
     name_v.push_back(filename);
 
@@ -1422,16 +1425,10 @@ static void ReadLink(const FunctionCallbackInfo<Value>& args) {
     }
     const char* link_path = static_cast<const char*>(req_wrap_sync.req.ptr);
 
-    Local<Value> error;
     Local<Value> ret;
-    if (!StringBytes::Encode(isolate, link_path, encoding, &error)
-             .ToLocal(&ret)) {
-      DCHECK(!error.IsEmpty());
-      env->isolate()->ThrowException(error);
-      return;
+    if (StringBytes::Encode(isolate, link_path, encoding).ToLocal(&ret)) {
+      args.GetReturnValue().Set(ret);
     }
-
-    args.GetReturnValue().Set(ret);
   }
 }
 
@@ -1932,17 +1929,12 @@ static void MKDir(const FunctionCallbackInfo<Value>& args) {
         return;
       }
       if (!req_wrap_sync.continuation_data()->first_path().empty()) {
-        Local<Value> error;
         Local<Value> ret;
         std::string first_path(req_wrap_sync.continuation_data()->first_path());
-        if (!StringBytes::Encode(
-                 env->isolate(), first_path.c_str(), UTF8, &error)
-                 .ToLocal(&ret)) {
-          DCHECK(!error.IsEmpty());
-          env->isolate()->ThrowException(error);
-          return;
+        if (StringBytes::Encode(env->isolate(), first_path.c_str(), UTF8)
+                .ToLocal(&ret)) {
+          args.GetReturnValue().Set(ret);
         }
-        args.GetReturnValue().Set(ret);
       }
     } else {
       SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_mkdir, *path, mode);
@@ -1982,16 +1974,10 @@ static void RealPath(const FunctionCallbackInfo<Value>& args) {
 
     const char* link_path = static_cast<const char*>(req_wrap_sync.req.ptr);
 
-    Local<Value> error;
     Local<Value> ret;
-    if (!StringBytes::Encode(isolate, link_path, encoding, &error)
-             .ToLocal(&ret)) {
-      DCHECK(!error.IsEmpty());
-      env->isolate()->ThrowException(error);
-      return;
+    if (StringBytes::Encode(isolate, link_path, encoding).ToLocal(&ret)) {
+      args.GetReturnValue().Set(ret);
     }
-
-    args.GetReturnValue().Set(ret);
   }
 }
 
@@ -2077,12 +2063,8 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
         return;
       }
 
-      Local<Value> error;
       Local<Value> fn;
-      if (!StringBytes::Encode(isolate, ent.name, encoding, &error)
-               .ToLocal(&fn)) {
-        DCHECK(!error.IsEmpty());
-        isolate->ThrowException(error);
+      if (!StringBytes::Encode(isolate, ent.name, encoding).ToLocal(&fn)) {
         return;
       }
 
@@ -3106,15 +3088,11 @@ static void Mkdtemp(const FunctionCallbackInfo<Value>& args) {
     if (is_uv_error(result)) {
       return;
     }
-    Local<Value> error;
     Local<Value> ret;
-    if (!StringBytes::Encode(isolate, req_wrap_sync.req.path, encoding, &error)
-             .ToLocal(&ret)) {
-      DCHECK(!error.IsEmpty());
-      env->isolate()->ThrowException(error);
-      return;
+    if (StringBytes::Encode(isolate, req_wrap_sync.req.path, encoding)
+            .ToLocal(&ret)) {
+      args.GetReturnValue().Set(ret);
     }
-    args.GetReturnValue().Set(ret);
   }
 }
 
diff --git a/src/node_i18n.cc b/src/node_i18n.cc
index dbda7d2f653b38..9f3ae18dcfe2b4 100644
--- a/src/node_i18n.cc
+++ b/src/node_i18n.cc
@@ -500,7 +500,6 @@ void ConverterObject::Decode(const FunctionCallbackInfo<Value>& args) {
       }
     }
 
-    Local<Value> error;
     UChar* output = result.out();
     size_t beginning = 0;
     size_t length = result.length() * sizeof(UChar);
@@ -517,11 +516,9 @@ void ConverterObject::Decode(const FunctionCallbackInfo<Value>& args) {
       CHECK(nbytes::SwapBytes16(value, length));
     }
 
-    MaybeLocal<Value> encoded =
-        StringBytes::Encode(env->isolate(), value, length, UCS2, &error);
-
     Local<Value> ret;
-    if (encoded.ToLocal(&ret)) {
+    if (StringBytes::Encode(env->isolate(), value, length, UCS2)
+            .ToLocal(&ret)) {
       args.GetReturnValue().Set(ret);
       return;
     }
diff --git a/src/node_os.cc b/src/node_os.cc
index 8da7e7f9eb78c8..e46d32cd9e50f5 100644
--- a/src/node_os.cc
+++ b/src/node_os.cc
@@ -307,8 +307,6 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
 
   auto free_passwd = OnScopeLeave([&] { uv_os_free_passwd(&pwd); });
 
-  Local<Value> error;
-
 #ifdef _WIN32
   Local<Value> uid = Number::New(
       env->isolate(),
@@ -322,27 +320,21 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
 #endif
 
   Local<Value> username;
-  if (!StringBytes::Encode(env->isolate(), pwd.username, encoding, &error)
+  if (!StringBytes::Encode(env->isolate(), pwd.username, encoding)
            .ToLocal(&username)) {
-    CHECK(!error.IsEmpty());
-    env->isolate()->ThrowException(error);
     return;
   }
 
   Local<Value> homedir;
-  if (!StringBytes::Encode(env->isolate(), pwd.homedir, encoding, &error)
+  if (!StringBytes::Encode(env->isolate(), pwd.homedir, encoding)
            .ToLocal(&homedir)) {
-    CHECK(!error.IsEmpty());
-    env->isolate()->ThrowException(error);
     return;
   }
 
   Local<Value> shell = Null(env->isolate());
   if (pwd.shell != nullptr &&
-      !StringBytes::Encode(env->isolate(), pwd.shell, encoding, &error)
+      !StringBytes::Encode(env->isolate(), pwd.shell, encoding)
            .ToLocal(&shell)) {
-    CHECK(!error.IsEmpty());
-    env->isolate()->ThrowException(error);
     return;
   }
 
diff --git a/src/string_bytes.cc b/src/string_bytes.cc
index 2cfbaf7253baef..29abd45685316e 100644
--- a/src/string_bytes.cc
+++ b/src/string_bytes.cc
@@ -74,37 +74,34 @@ class ExternString: public ResourceType {
 
   static MaybeLocal<Value> NewFromCopy(Isolate* isolate,
                                        const TypeName* data,
-                                       size_t length,
-                                       Local<Value>* error) {
-    if (length == 0)
+                                       size_t length) {
+    if (length == 0) {
       return String::Empty(isolate);
+    }
 
-    if (length < EXTERN_APEX)
-      return NewSimpleFromCopy(isolate, data, length, error);
+    if (length < EXTERN_APEX) {
+      return NewSimpleFromCopy(isolate, data, length);
+    }
 
     TypeName* new_data = node::UncheckedMalloc<TypeName>(length);
     if (new_data == nullptr) {
-      *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
+      isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate));
       return MaybeLocal<Value>();
     }
     memcpy(new_data, data, length * sizeof(*new_data));
 
-    return ExternString<ResourceType, TypeName>::New(isolate,
-                                                     new_data,
-                                                     length,
-                                                     error);
+    return ExternString<ResourceType, TypeName>::New(isolate, new_data, length);
   }
 
   // uses "data" for external resource, and will be free'd on gc
   static MaybeLocal<Value> New(Isolate* isolate,
                                TypeName* data,
-                               size_t length,
-                               Local<Value>* error) {
+                               size_t length) {
     if (length == 0)
       return String::Empty(isolate);
 
     if (length < EXTERN_APEX) {
-      MaybeLocal<Value> str = NewSimpleFromCopy(isolate, data, length, error);
+      MaybeLocal<Value> str = NewSimpleFromCopy(isolate, data, length);
       free(data);
       return str;
     }
@@ -116,7 +113,7 @@ class ExternString: public ResourceType {
 
     if (!NewExternal(isolate, h_str).ToLocal(&str)) {
       delete h_str;
-      *error = node::ERR_STRING_TOO_LONG(isolate);
+      isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate));
       return MaybeLocal<Value>();
     }
 
@@ -136,8 +133,7 @@ class ExternString: public ResourceType {
   // This method does not actually create ExternString instances.
   static MaybeLocal<Value> NewSimpleFromCopy(Isolate* isolate,
                                              const TypeName* data,
-                                             size_t length,
-                                             Local<Value>* error);
+                                             size_t length);
 
   Isolate* isolate_;
   const TypeName* data_;
@@ -167,30 +163,27 @@ MaybeLocal<Value> ExternTwoByteString::NewExternal(
 template <>
 MaybeLocal<Value> ExternOneByteString::NewSimpleFromCopy(Isolate* isolate,
                                                          const char* data,
-                                                         size_t length,
-                                                         Local<Value>* error) {
+                                                         size_t length) {
   Local<String> str;
   if (!String::NewFromOneByte(isolate,
                               reinterpret_cast<const uint8_t*>(data),
                               v8::NewStringType::kNormal,
                               length)
            .ToLocal(&str)) {
-    *error = node::ERR_STRING_TOO_LONG(isolate);
+    isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate));
     return MaybeLocal<Value>();
   }
   return str;
 }
 
-
 template <>
 MaybeLocal<Value> ExternTwoByteString::NewSimpleFromCopy(Isolate* isolate,
                                                          const uint16_t* data,
-                                                         size_t length,
-                                                         Local<Value>* error) {
+                                                         size_t length) {
   Local<String> str;
   if (!String::NewFromTwoByte(isolate, data, v8::NewStringType::kNormal, length)
            .ToLocal(&str)) {
-    *error = node::ERR_STRING_TOO_LONG(isolate);
+    isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate));
     return MaybeLocal<Value>();
   }
   return str;
@@ -489,20 +482,18 @@ Maybe<size_t> StringBytes::Size(Isolate* isolate,
   UNREACHABLE();
 }
 
-#define CHECK_BUFLEN_IN_RANGE(len)                                    \
-  do {                                                                \
-    if ((len) > Buffer::kMaxLength) {                                 \
-      *error = node::ERR_BUFFER_TOO_LARGE(isolate);                   \
-      return MaybeLocal<Value>();                                     \
-    }                                                                 \
+#define CHECK_BUFLEN_IN_RANGE(len)                                             \
+  do {                                                                         \
+    if ((len) > Buffer::kMaxLength) {                                          \
+      isolate->ThrowException(node::ERR_BUFFER_TOO_LARGE(isolate));            \
+      return MaybeLocal<Value>();                                              \
+    }                                                                          \
   } while (0)
 
-
 MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
                                       const char* buf,
                                       size_t buflen,
-                                      enum encoding encoding,
-                                      Local<Value>* error) {
+                                      enum encoding encoding) {
   CHECK_BUFLEN_IN_RANGE(buflen);
 
   if (!buflen && encoding != BUFFER) {
@@ -517,7 +508,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
         auto maybe_buf = Buffer::Copy(isolate, buf, buflen);
         Local<v8::Object> buf;
         if (!maybe_buf.ToLocal(&buf)) {
-          *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
+          isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate));
         }
         return buf;
       }
@@ -528,13 +519,13 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
         // The input contains non-ASCII bytes.
         char* out = node::UncheckedMalloc(buflen);
         if (out == nullptr) {
-          *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
+          isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate));
           return MaybeLocal<Value>();
         }
         nbytes::ForceAscii(buf, out, buflen);
-        return ExternOneByteString::New(isolate, out, buflen, error);
+        return ExternOneByteString::New(isolate, out, buflen);
       } else {
-        return ExternOneByteString::NewFromCopy(isolate, buf, buflen, error);
+        return ExternOneByteString::NewFromCopy(isolate, buf, buflen);
       }
 
     case UTF8: {
@@ -543,28 +534,28 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
           String::NewFromUtf8(isolate, buf, v8::NewStringType::kNormal, buflen);
       Local<String> str;
       if (!val.ToLocal(&str)) {
-        *error = node::ERR_STRING_TOO_LONG(isolate);
+        isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate));
       }
       return str;
     }
 
     case LATIN1:
       buflen = keep_buflen_in_range(buflen);
-      return ExternOneByteString::NewFromCopy(isolate, buf, buflen, error);
+      return ExternOneByteString::NewFromCopy(isolate, buf, buflen);
 
     case BASE64: {
       buflen = keep_buflen_in_range(buflen);
       size_t dlen = simdutf::base64_length_from_binary(buflen);
       char* dst = node::UncheckedMalloc(dlen);
       if (dst == nullptr) {
-        *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
+        isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate));
         return MaybeLocal<Value>();
       }
 
       size_t written = simdutf::binary_to_base64(buf, buflen, dst);
       CHECK_EQ(written, dlen);
 
-      return ExternOneByteString::New(isolate, dst, dlen, error);
+      return ExternOneByteString::New(isolate, dst, dlen);
     }
 
     case BASE64URL: {
@@ -573,7 +564,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
           simdutf::base64_length_from_binary(buflen, simdutf::base64_url);
       char* dst = node::UncheckedMalloc(dlen);
       if (dst == nullptr) {
-        *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
+        isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate));
         return MaybeLocal<Value>();
       }
 
@@ -581,7 +572,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
           simdutf::binary_to_base64(buf, buflen, dst, simdutf::base64_url);
       CHECK_EQ(written, dlen);
 
-      return ExternOneByteString::New(isolate, dst, dlen, error);
+      return ExternOneByteString::New(isolate, dst, dlen);
     }
 
     case HEX: {
@@ -589,13 +580,13 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
       size_t dlen = buflen * 2;
       char* dst = node::UncheckedMalloc(dlen);
       if (dst == nullptr) {
-        *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
+        isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate));
         return MaybeLocal<Value>();
       }
       size_t written = nbytes::HexEncode(buf, buflen, dst, dlen);
       CHECK_EQ(written, dlen);
 
-      return ExternOneByteString::New(isolate, dst, dlen, error);
+      return ExternOneByteString::New(isolate, dst, dlen);
     }
 
     case UCS2: {
@@ -604,7 +595,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
       if constexpr (IsBigEndian()) {
         uint16_t* dst = node::UncheckedMalloc<uint16_t>(str_len);
         if (str_len != 0 && dst == nullptr) {
-          *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
+          isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate));
           return MaybeLocal<Value>();
         }
         for (size_t i = 0, k = 0; k < str_len; i += 2, k += 1) {
@@ -614,21 +605,21 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
           const uint8_t lo = static_cast<uint8_t>(buf[i + 0]);
           dst[k] = static_cast<uint16_t>(hi) << 8 | lo;
         }
-        return ExternTwoByteString::New(isolate, dst, str_len, error);
+        return ExternTwoByteString::New(isolate, dst, str_len);
       }
       if (reinterpret_cast<uintptr_t>(buf) % 2 != 0) {
         // Unaligned data still means we can't directly pass it to V8.
         char* dst = node::UncheckedMalloc(buflen);
         if (dst == nullptr) {
-          *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
+          isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate));
           return MaybeLocal<Value>();
         }
         memcpy(dst, buf, buflen);
         return ExternTwoByteString::New(
-            isolate, reinterpret_cast<uint16_t*>(dst), str_len, error);
+            isolate, reinterpret_cast<uint16_t*>(dst), str_len);
       }
       return ExternTwoByteString::NewFromCopy(
-          isolate, reinterpret_cast<const uint16_t*>(buf), str_len, error);
+          isolate, reinterpret_cast<const uint16_t*>(buf), str_len);
     }
 
     default:
@@ -636,11 +627,9 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
   }
 }
 
-
 MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
                                       const uint16_t* buf,
-                                      size_t buflen,
-                                      Local<Value>* error) {
+                                      size_t buflen) {
   if (buflen == 0) return String::Empty(isolate);
   CHECK_BUFLEN_IN_RANGE(buflen);
 
@@ -651,24 +640,23 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
   if constexpr (IsBigEndian()) {
     uint16_t* dst = node::UncheckedMalloc<uint16_t>(buflen);
     if (dst == nullptr) {
-      *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
+      isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate));
       return MaybeLocal<Value>();
     }
     size_t nbytes = buflen * sizeof(uint16_t);
     memcpy(dst, buf, nbytes);
     CHECK(nbytes::SwapBytes16(reinterpret_cast<char*>(dst), nbytes));
-    return ExternTwoByteString::New(isolate, dst, buflen, error);
+    return ExternTwoByteString::New(isolate, dst, buflen);
   } else {
-    return ExternTwoByteString::NewFromCopy(isolate, buf, buflen, error);
+    return ExternTwoByteString::NewFromCopy(isolate, buf, buflen);
   }
 }
 
 MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
                                       const char* buf,
-                                      enum encoding encoding,
-                                      Local<Value>* error) {
+                                      enum encoding encoding) {
   const size_t len = strlen(buf);
-  return Encode(isolate, buf, len, encoding, error);
+  return Encode(isolate, buf, len, encoding);
 }
 
 }  // namespace node
diff --git a/src/string_bytes.h b/src/string_bytes.h
index 53bc003fbda436..a52cc5148269dd 100644
--- a/src/string_bytes.h
+++ b/src/string_bytes.h
@@ -81,8 +81,7 @@ class StringBytes {
   static v8::MaybeLocal<v8::Value> Encode(v8::Isolate* isolate,
                                           const char* buf,
                                           size_t buflen,
-                                          enum encoding encoding,
-                                          v8::Local<v8::Value>* error);
+                                          enum encoding encoding);
 
   // Warning: This reverses endianness on BE platforms, even though the
   // signature using uint16_t implies that it should not.
@@ -90,13 +89,11 @@ class StringBytes {
   // be changed easily.
   static v8::MaybeLocal<v8::Value> Encode(v8::Isolate* isolate,
                                           const uint16_t* buf,
-                                          size_t buflen,
-                                          v8::Local<v8::Value>* error);
+                                          size_t buflen);
 
   static v8::MaybeLocal<v8::Value> Encode(v8::Isolate* isolate,
                                           const char* buf,
-                                          enum encoding encoding,
-                                          v8::Local<v8::Value>* error);
+                                          enum encoding encoding);
 
  private:
   static size_t WriteUCS2(v8::Isolate* isolate,
diff --git a/src/string_decoder.cc b/src/string_decoder.cc
index c7a6f5e8e58d24..4787a83aafe3eb 100644
--- a/src/string_decoder.cc
+++ b/src/string_decoder.cc
@@ -28,7 +28,6 @@ MaybeLocal<String> MakeString(Isolate* isolate,
                               const char* data,
                               size_t length,
                               enum encoding encoding) {
-  Local<Value> error;
   MaybeLocal<Value> ret;
   if (encoding == UTF8) {
     MaybeLocal<String> utf8_string;
@@ -43,17 +42,11 @@ MaybeLocal<String> MakeString(Isolate* isolate,
       return utf8_string;
     }
   } else {
-    ret = StringBytes::Encode(
-        isolate,
-        data,
-        length,
-        encoding,
-        &error);
+    ret = StringBytes::Encode(isolate, data, length, encoding);
   }
 
   if (ret.IsEmpty()) {
-    CHECK(!error.IsEmpty());
-    isolate->ThrowException(error);
+    return {};
   }
 
   DCHECK(ret.IsEmpty() || ret.ToLocalChecked()->IsString());


Reviews:
user: login: anonrig
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T15:05:06Z
user: login: jasnell
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T16:56:31Z
user: login: jasnell
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T16:57:46Z
user: login: jasnell
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T17:05:19Z
user: login: anonrig
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-03T19:10:27Z
user: login: aduh95
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T13:42:36Z
user: login: aduh95
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-06T17:13:50Z

Review comments:
diff_hunk: @@ -133,20 +135,30 @@ enum encoding ParseEncoding(Isolate* isolate,
   return ParseEncoding(*encoding, default_encoding);
 }
 
+MaybeLocal<Value> TryEncode(Isolate* isolate,
+                            const char* buf,
+                            size_t len,
+                            enum encoding encoding) {
+  CHECK_NE(encoding, UCS2);
path: src/api/encoding.cc
user: login: anonrig
body: ```suggestion
  DCHECK_NE(encoding, UCS2);
```
created_at: 2025-04-01T15:02:49Z
author_association: MEMBER
diff_hunk: @@ -883,15 +885,15 @@ void AfterStringPtr(uv_fs_t* req) {
   FS_ASYNC_TRACE_END1(
       req->fs_type, req_wrap, "result", static_cast<int>(req->result))
   MaybeLocal<Value> link;
-  Local<Value> error;
 
   if (after.Proceed()) {
+    TryCatch try_catch(req_wrap->env()->isolate());
     link = StringBytes::Encode(req_wrap->env()->isolate(),
                                static_cast<const char*>(req->ptr),
-                               req_wrap->encoding(),
-                               &error);
+                               req_wrap->encoding());
     if (link.IsEmpty()) {
-      req_wrap->Reject(error);
+      CHECK(try_catch.CanContinue());
path: src/node_file.cc
user: login: anonrig
body: ```suggestion
      DCHECK(try_catch.CanContinue());
```
created_at: 2025-04-01T15:04:03Z
author_association: MEMBER
diff_hunk: @@ -116,7 +113,7 @@ class ExternString: public ResourceType {
 
     if (!NewExternal(isolate, h_str).ToLocal(&str)) {
       delete h_str;
-      *error = node::ERR_STRING_TOO_LONG(isolate);
+      isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate));
path: src/string_bytes.cc
user: login: anonrig
body: why not use the THROW_ERR_STRING_TOO_LONG macro?
created_at: 2025-04-01T15:04:52Z
author_association: MEMBER
diff_hunk: @@ -133,20 +135,30 @@ enum encoding ParseEncoding(Isolate* isolate,
   return ParseEncoding(*encoding, default_encoding);
 }
 
+MaybeLocal<Value> TryEncode(Isolate* isolate,
+                            const char* buf,
+                            size_t len,
+                            enum encoding encoding) {
+  CHECK_NE(encoding, UCS2);
path: src/api/encoding.cc
user: login: jasnell
body: This part was taken from the existiing impl and I'd prefer to keep it the same. We can change this separately if appropriate.
created_at: 2025-04-01T16:56:30Z
author_association: MEMBER
diff_hunk: @@ -883,15 +885,15 @@ void AfterStringPtr(uv_fs_t* req) {
   FS_ASYNC_TRACE_END1(
       req->fs_type, req_wrap, "result", static_cast<int>(req->result))
   MaybeLocal<Value> link;
-  Local<Value> error;
 
   if (after.Proceed()) {
+    TryCatch try_catch(req_wrap->env()->isolate());
     link = StringBytes::Encode(req_wrap->env()->isolate(),
                                static_cast<const char*>(req->ptr),
-                               req_wrap->encoding(),
-                               &error);
+                               req_wrap->encoding());
     if (link.IsEmpty()) {
-      req_wrap->Reject(error);
+      CHECK(try_catch.CanContinue());
path: src/node_file.cc
user: login: jasnell
body: I'd prefer these to stay as `CHECK(...)` because this will catch certain definitely fatal runtime errors that *should* cause a crash.
created_at: 2025-04-01T16:57:46Z
author_association: MEMBER
diff_hunk: @@ -116,7 +113,7 @@ class ExternString: public ResourceType {
 
     if (!NewExternal(isolate, h_str).ToLocal(&str)) {
       delete h_str;
-      *error = node::ERR_STRING_TOO_LONG(isolate);
+      isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate));
path: src/string_bytes.cc
user: login: jasnell
body: There actually seems to be a bug in the macro for `THROW_ERR_STRING_TOO_LONG` that ends up requiring a message argument when `node::ERR_STRING_TOO_LONG` does not need it. I'd note that `THROW_ERR_STRING_TOO_LONG` is not used anywhere else. For now, let's keep this as is and we can revisit `THROW_ERR_STRING_TOO_LONG` separately.
created_at: 2025-04-01T17:05:19Z
author_association: MEMBER
diff_hunk: @@ -183,17 +183,10 @@ void BindingData::DecodeUTF8(const FunctionCallbackInfo<Value>& args) {
 
   if (length == 0) return args.GetReturnValue().SetEmptyString();
 
-  Local<Value> error;
   Local<Value> ret;
-
-  if (!StringBytes::Encode(env->isolate(), data, length, UTF8, &error)
-           .ToLocal(&ret)) {
-    CHECK(!error.IsEmpty());
-    env->isolate()->ThrowException(error);
-    return;
+  if (StringBytes::Encode(env->isolate(), data, length, UTF8).ToLocal(&ret)) {
+    args.GetReturnValue().Set(ret);
   }
-
-  args.GetReturnValue().Set(ret);
path: src/encoding_binding.cc
user: login: aduh95
body: nit: I'd find it more readable to `return` on the error path, possibly with a comment, and keep the `.Set()` call on the top-level of the body of the function
created_at: 2025-04-05T13:42:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: nodejs-github-bot
created_at: 2025-04-01T13:41:28Z
body: Review requested:

- [ ] @nodejs/crypto
author_association: COLLABORATOR
user: login: codecov[bot]
created_at: 2025-04-01T16:53:20Z
body: ## [Codecov](https://app.codecov.io/gh/nodejs/node/pull/57706?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) Report
Attention: Patch coverage is `54.13534%` with `61 lines` in your changes missing coverage. Please review.
> Project coverage is 90.25%. Comparing base [(`0d91070`)](https://app.codecov.io/gh/nodejs/node/commit/0d910706ef4efa3a20e86d320ac95dd79e2dc624?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) to head [(`715f9ae`)](https://app.codecov.io/gh/nodejs/node/commit/715f9ae35845cf28304d10391efef71e68011e2e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs).
> Report is 301 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/nodejs/node/pull/57706?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) | Patch % | Lines |
|---|---|---|
| [src/node\_file.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fnode_file.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL25vZGVfZmlsZS5jYw==) | 51.61% | [8 Missing and 7 partials :warning: ](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) |
| [src/string\_bytes.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fstring_bytes.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL3N0cmluZ19ieXRlcy5jYw==) | 57.69% | [10 Missing and 1 partial :warning: ](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) |
| [src/api/encoding.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fapi%2Fencoding.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2FwaS9lbmNvZGluZy5jYw==) | 0.00% | [9 Missing :warning: ](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) |
| [src/crypto/crypto\_hash.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fcrypto%2Fcrypto_hash.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2NyeXB0by9jcnlwdG9faGFzaC5jYw==) | 57.14% | [4 Missing and 2 partials :warning: ](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) |
| [src/node\_dir.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fnode_dir.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL25vZGVfZGlyLmNj) | 50.00% | [5 Missing and 1 partial :warning: ](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) |
| [src/fs\_event\_wrap.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Ffs_event_wrap.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2ZzX2V2ZW50X3dyYXAuY2M=) | 40.00% | [3 Missing :warning: ](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) |
| [src/node\_os.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fnode_os.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL25vZGVfb3MuY2M=) | 33.33% | [0 Missing and 2 partials :warning: ](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) |
| [src/crypto/crypto\_ec.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fcrypto%2Fcrypto_ec.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2NyeXB0by9jcnlwdG9fZWMuY2M=) | 66.66% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) |
| [src/crypto/crypto\_hmac.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fcrypto%2Fcrypto_hmac.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2NyeXB0by9jcnlwdG9faG1hYy5jYw==) | 80.00% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) |
| [src/crypto/crypto\_keys.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fcrypto%2Fcrypto_keys.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2NyeXB0by9jcnlwdG9fa2V5cy5jYw==) | 80.00% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) |
| ... and [6 more](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) | |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main   #57706      +/-   ##
==========================================
+ Coverage   90.23%   90.25%   +0.02%     
==========================================
  Files         630      630              
  Lines      185055   185021      -34     
  Branches    36223    36213      -10     
==========================================
+ Hits       166980   166995      +15     
+ Misses      11033    11013      -20     
+ Partials     7042     7013      -29     
```

| [Files with missing lines](https://app.codecov.io/gh/nodejs/node/pull/57706?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) | Coverage Δ | |
|---|---|---|
| [src/node.h](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fnode.h&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL25vZGUuaA==) | `92.15% <ø> (ø)` | |
| [src/string\_bytes.h](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fstring_bytes.h&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL3N0cmluZ19ieXRlcy5o) | `80.00% <ø> (ø)` | |
| [src/crypto/crypto\_ec.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fcrypto%2Fcrypto_ec.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2NyeXB0by9jcnlwdG9fZWMuY2M=) | `67.92% <66.66%> (+0.60%)` | :arrow_up: |
| [src/crypto/crypto\_hmac.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fcrypto%2Fcrypto_hmac.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2NyeXB0by9jcnlwdG9faG1hYy5jYw==) | `68.23% <80.00%> (+1.37%)` | :arrow_up: |
| [src/crypto/crypto\_keys.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fcrypto%2Fcrypto_keys.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2NyeXB0by9jcnlwdG9fa2V5cy5jYw==) | `72.30% <80.00%> (-0.16%)` | :arrow_down: |
| [src/crypto/crypto\_spkac.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fcrypto%2Fcrypto_spkac.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2NyeXB0by9jcnlwdG9fc3BrYWMuY2M=) | `87.75% <87.50%> (-1.14%)` | :arrow_down: |
| [src/crypto/crypto\_util.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fcrypto%2Fcrypto_util.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2NyeXB0by9jcnlwdG9fdXRpbC5jYw==) | `72.33% <66.66%> (+0.36%)` | :arrow_up: |
| [src/encoding\_binding.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fencoding_binding.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL2VuY29kaW5nX2JpbmRpbmcuY2M=) | `79.66% <50.00%> (+1.20%)` | :arrow_up: |
| [src/node\_buffer.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fnode_buffer.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL25vZGVfYnVmZmVyLmNj) | `67.37% <66.66%> (-0.07%)` | :arrow_down: |
| [src/node\_i18n.cc](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree&filepath=src%2Fnode_i18n.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs#diff-c3JjL25vZGVfaTE4bi5jYw==) | `72.62% <50.00%> (-0.08%)` | :arrow_down: |
| ... and [8 more](https://app.codecov.io/gh/nodejs/node/pull/57706?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs) | |

... and [23 files with indirect coverage changes](https://app.codecov.io/gh/nodejs/node/pull/57706/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nodejs)

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: nodejs-github-bot
created_at: 2025-04-01T21:54:29Z
body: CI: https://ci.nodejs.org/job/node-test-pull-request/65953/
author_association: COLLABORATOR
user: login: nodejs-github-bot
created_at: 2025-04-02T10:44:42Z
body: CI: https://ci.nodejs.org/job/node-test-pull-request/65969/
author_association: COLLABORATOR
user: login: nodejs-github-bot
created_at: 2025-04-02T14:48:26Z
body: CI: https://ci.nodejs.org/job/node-test-pull-request/65981/
author_association: COLLABORATOR
user: login: nodejs-github-bot
created_at: 2025-04-03T15:17:46Z
body: CI: https://ci.nodejs.org/job/node-test-pull-request/66002/
author_association: COLLABORATOR
user: login: nodejs-github-bot
created_at: 2025-04-03T16:16:14Z
body: CI: https://ci.nodejs.org/job/node-test-pull-request/66007/ 💛 
author_association: COLLABORATOR
user: login: nodejs-github-bot
created_at: 2025-04-06T17:19:45Z
body: Landed in 93644d512694fd9f4a3b25f50c918fff455e42fd
author_association: COLLABORATOR
user: login: aduh95
created_at: 2025-05-06T16:43:33Z
body: Could you open a backport PR to v22.x-staging please?
author_association: CONTRIBUTOR