diff --git a/lib/internal/util.js b/lib/internal/util.js index 64f2eec98d9094..1c8a02bd1811d8 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -69,8 +69,16 @@ const experimentalWarnings = new SafeSet(); const colorRegExp = /\u001b\[\d\d?m/g; // eslint-disable-line no-control-regex +const unpairedSurrogateRe = + /(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/; function toUSVString(val) { - return _toUSVString(`${val}`); + const str = `${val}`; + // As of V8 5.5, `str.search()` (and `unpairedSurrogateRe[@@search]()`) are + // slower than `unpairedSurrogateRe.exec()`. + const match = RegExpPrototypeExec(unpairedSurrogateRe, str); + if (!match) + return str; + return _toUSVString(str, match.index); } let uvBinding; diff --git a/src/node_util.cc b/src/node_util.cc index 86563677a29f34..8bb647877a9f2e 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -319,12 +319,16 @@ static void GuessHandleType(const FunctionCallbackInfo& args) { static void ToUSVString(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - CHECK_GE(args.Length(), 1); + CHECK_GE(args.Length(), 2); CHECK(args[0]->IsString()); + CHECK(args[1]->IsNumber()); TwoByteValue value(env->isolate(), args[0]); - for (size_t i = 0; i < value.length(); i++) { + int64_t start = args[1]->IntegerValue(env->context()).FromJust(); + CHECK_GE(start, 0); + + for (size_t i = start; i < value.length(); i++) { char16_t c = value[i]; if (!IsUnicodeSurrogate(c)) { continue;