diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp index 14e7880547..a391ee04ac 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp @@ -286,6 +286,16 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_replace) if (vm.exception()) return {}; + if (!replace_value.is_function()) { + auto replace_string = replace_value.to_string(global_object); + if (vm.exception()) + return {}; + + replace_value = js_string(vm, move(replace_string)); + if (vm.exception()) + return {}; + } + auto global_value = rx->get(vm.names.global); if (vm.exception()) return {}; diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp index d94161723a..3c89d74ec7 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -800,6 +800,17 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace) auto search_string = search_value.to_string(global_object); if (vm.exception()) return {}; + + if (!replace_value.is_function()) { + auto replace_string = replace_value.to_string(global_object); + if (vm.exception()) + return {}; + + replace_value = js_string(vm, move(replace_string)); + if (vm.exception()) + return {}; + } + Optional position = string.find(search_string); if (!position.has_value()) return js_string(vm, string); @@ -877,6 +888,16 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all) if (vm.exception()) return {}; + if (!replace_value.is_function()) { + auto replace_string = replace_value.to_string(global_object); + if (vm.exception()) + return {}; + + replace_value = js_string(vm, move(replace_string)); + if (vm.exception()) + return {}; + } + Vector match_positions; size_t advance_by = max(1u, search_string.length()); auto position = string.find(search_string); diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replace.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replace.js index 90e306bccb..46530ce135 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replace.js +++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replace.js @@ -158,3 +158,21 @@ test("replacement with substitution and 'groups' coerced to an object", () => { expect(r[Symbol.replace]("ab", "[$]")).toBe("a[3]"); }); + +test("replacement value is evaluated before searching the source string", () => { + var calls = 0; + var replaceValue = { + toString: function () { + calls += 1; + return "b"; + }, + }; + + var newString = "".replace("a", replaceValue); + expect(newString).toBe(""); + expect(calls).toBe(1); + + newString = "".replace(/a/g, replaceValue); + expect(newString).toBe(""); + expect(calls).toBe(2); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replaceAll.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replaceAll.js index 11aaab2371..7358c05f42 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replaceAll.js +++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replaceAll.js @@ -104,3 +104,21 @@ test("functional regex replacement", () => { }) ).toBe("xd"); }); + +test("replacement value is evaluated before searching the source string", () => { + var calls = 0; + var replaceValue = { + toString: function () { + calls += 1; + return "b"; + }, + }; + + var newString = "".replaceAll("a", replaceValue); + expect(newString).toBe(""); + expect(calls).toBe(1); + + newString = "".replaceAll(/a/g, replaceValue); + expect(newString).toBe(""); + expect(calls).toBe(2); +});