6013fdbac9
Make several changes to template object caching: * Key the cache on Script rather than SFI, so that entries stay alive even if the SFI dies (e.g. because its parent is code flushed) but can be resurrected (because other functions from the same script can recreate it) * With the above change, identify the required template object by comparing both function literal id and feedback slot id. * Change the cache from a linked list of CachedTemplateObjects into an ArrayList pointing directly to the template object JSArrays. * With CachedTemplateObjects being gone, store the function literal id and slot id directly on the JSArray behind private symbols. Fast path access to them in the case where the template object has the expected map, and look them up in a slow path if the map changed (e.g. because the template object was used as a prototype and transitioned to a dictionary map). Change-Id: Id715cb2fd38b9605b8e6ddf5e35336bb4f0300d2 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3900376 Commit-Queue: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Reviewed-by: Toon Verwaest <verwaest@chromium.org> Cr-Commit-Position: refs/heads/main@{#83693}
900 lines
26 KiB
JavaScript
900 lines
26 KiB
JavaScript
// Copyright 2014 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
//
|
|
// Flags: --expose-gc
|
|
|
|
var num = 5;
|
|
var str = "str";
|
|
function fn() { return "result"; }
|
|
var obj = {
|
|
num: num,
|
|
str: str,
|
|
fn: function() { return "result"; }
|
|
};
|
|
|
|
(function testBasicExpressions() {
|
|
assertEquals("foo 5 bar", `foo ${num} bar`);
|
|
assertEquals("foo str bar", `foo ${str} bar`);
|
|
assertEquals("foo [object Object] bar", `foo ${obj} bar`);
|
|
assertEquals("foo result bar", `foo ${fn()} bar`);
|
|
assertEquals("foo 5 bar", `foo ${obj.num} bar`);
|
|
assertEquals("foo str bar", `foo ${obj.str} bar`);
|
|
assertEquals("foo result bar", `foo ${obj.fn()} bar`);
|
|
})();
|
|
|
|
(function testExpressionsContainingTemplates() {
|
|
assertEquals("foo bar 5", `foo ${`bar ${num}`}`);
|
|
})();
|
|
|
|
(function testMultilineTemplates() {
|
|
assertEquals("foo\n bar\n baz", `foo
|
|
bar
|
|
baz`);
|
|
|
|
assertEquals("foo\n bar\n baz", eval("`foo\r\n bar\r baz`"));
|
|
})();
|
|
|
|
(function testLineContinuation() {
|
|
assertEquals("\n", `\
|
|
|
|
`);
|
|
})();
|
|
|
|
(function testTaggedTemplates() {
|
|
var calls = 0;
|
|
(function(s) {
|
|
calls++;
|
|
})`test`;
|
|
assertEquals(1, calls);
|
|
|
|
calls = 0;
|
|
// assert tag is invoked in right context
|
|
obj = {
|
|
fn: function() {
|
|
calls++;
|
|
assertEquals(obj, this);
|
|
}
|
|
};
|
|
|
|
obj.fn`test`;
|
|
assertEquals(1, calls);
|
|
|
|
calls = 0;
|
|
// Simple templates only have a callSiteObj
|
|
(function(s) {
|
|
calls++;
|
|
assertEquals(1, arguments.length);
|
|
})`test`;
|
|
assertEquals(1, calls);
|
|
|
|
// Templates containing expressions have the values of evaluated expressions
|
|
calls = 0;
|
|
(function(site, n, s, o, f, r) {
|
|
calls++;
|
|
assertEquals(6, arguments.length);
|
|
assertEquals("number", typeof n);
|
|
assertEquals("string", typeof s);
|
|
assertEquals("object", typeof o);
|
|
assertEquals("function", typeof f);
|
|
assertEquals("result", r);
|
|
})`${num}${str}${obj}${fn}${fn()}`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV and TRV of NoSubstitutionTemplate :: `` is the empty code unit
|
|
// sequence.
|
|
calls = 0;
|
|
(function(s) {
|
|
calls++;
|
|
assertEquals(1, s.length);
|
|
assertEquals(1, s.raw.length);
|
|
assertEquals("", s[0]);
|
|
|
|
// Failure: expected <""> found <"foo barfoo barfoo foo foo foo testtest">
|
|
assertEquals("", s.raw[0]);
|
|
})``;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV and TRV of TemplateHead :: `${ is the empty code unit sequence.
|
|
calls = 0;
|
|
(function(s) {
|
|
calls++;
|
|
assertEquals(2, s.length);
|
|
assertEquals(2, s.raw.length);
|
|
assertEquals("", s[0]);
|
|
assertEquals("", s.raw[0]);
|
|
})`${1}`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV and TRV of TemplateMiddle :: }${ is the empty code unit sequence.
|
|
calls = 0;
|
|
(function(s) {
|
|
calls++;
|
|
assertEquals(3, s.length);
|
|
assertEquals(3, s.raw.length);
|
|
assertEquals("", s[1]);
|
|
assertEquals("", s.raw[1]);
|
|
})`${1}${2}`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV and TRV of TemplateTail :: }` is the empty code unit sequence.
|
|
calls = 0;
|
|
(function(s) {
|
|
calls++;
|
|
assertEquals(2, s.length);
|
|
assertEquals(2, s.raw.length);
|
|
assertEquals("", s[1]);
|
|
assertEquals("", s.raw[1]);
|
|
})`${1}`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV of NoSubstitutionTemplate :: ` TemplateCharacters ` is the TV of
|
|
// TemplateCharacters.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("foo", s[0]); })`foo`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV of TemplateHead :: ` TemplateCharacters ${ is the TV of
|
|
// TemplateCharacters.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("foo", s[0]); })`foo${1}`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV of TemplateMiddle :: } TemplateCharacters ${ is the TV of
|
|
// TemplateCharacters.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("foo", s[1]); })`${1}foo${2}`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV of TemplateTail :: } TemplateCharacters ` is the TV of
|
|
// TemplateCharacters.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("foo", s[1]); })`${1}foo`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV of TemplateCharacters :: TemplateCharacter is the TV of
|
|
// TemplateCharacter.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("f", s[0]); })`f`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV of TemplateCharacter :: $ is the code unit value 0x0024.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("$", s[0]); })`$`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TV of TemplateCharacter :: \ EscapeSequence is the CV of
|
|
// EscapeSequence.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("안녕", s[0]); })`\uc548\uB155`;
|
|
(function(s) { calls++; assertEquals("\xff", s[0]); })`\xff`;
|
|
(function(s) { calls++; assertEquals("\n", s[0]); })`\n`;
|
|
assertEquals(3, calls);
|
|
|
|
// The TV of TemplateCharacter :: LineContinuation is the TV of
|
|
// LineContinuation. The TV of LineContinuation :: \ LineTerminatorSequence is
|
|
// the empty code unit sequence.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("", s[0]); })`\
|
|
`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of NoSubstitutionTemplate :: ` TemplateCharacters ` is the TRV of
|
|
// TemplateCharacters.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("test", s.raw[0]); })`test`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of TemplateHead :: ` TemplateCharacters ${ is the TRV of
|
|
// TemplateCharacters.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("test", s.raw[0]); })`test${1}`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of TemplateMiddle :: } TemplateCharacters ${ is the TRV of
|
|
// TemplateCharacters.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("test", s.raw[1]); })`${1}test${2}`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of TemplateTail :: } TemplateCharacters ` is the TRV of
|
|
// TemplateCharacters.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("test", s.raw[1]); })`${1}test`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of TemplateCharacters :: TemplateCharacter is the TRV of
|
|
// TemplateCharacter.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("f", s.raw[0]); })`f`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of TemplateCharacter :: $ is the code unit value 0x0024.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("\u0024", s.raw[0]); })`$`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of EscapeSequence :: 0 is the code unit value 0x0030.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("\u005C\u0030", s.raw[0]); })`\0`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of TemplateCharacter :: \ EscapeSequence is the sequence consisting
|
|
// of the code unit value 0x005C followed by the code units of TRV of
|
|
// EscapeSequence.
|
|
|
|
// The TRV of EscapeSequence :: HexEscapeSequence is the TRV of the
|
|
// HexEscapeSequence.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("\u005Cxff", s.raw[0]); })`\xff`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of EscapeSequence :: UnicodeEscapeSequence is the TRV of the
|
|
// UnicodeEscapeSequence.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("\u005Cuc548", s.raw[0]); })`\uc548`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of CharacterEscapeSequence :: SingleEscapeCharacter is the TRV of
|
|
// the SingleEscapeCharacter.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("\u005C\u0027", s.raw[0]); })`\'`;
|
|
(function(s) { calls++; assertEquals("\u005C\u0022", s.raw[0]); })`\"`;
|
|
(function(s) { calls++; assertEquals("\u005C\u005C", s.raw[0]); })`\\`;
|
|
(function(s) { calls++; assertEquals("\u005Cb", s.raw[0]); })`\b`;
|
|
(function(s) { calls++; assertEquals("\u005Cf", s.raw[0]); })`\f`;
|
|
(function(s) { calls++; assertEquals("\u005Cn", s.raw[0]); })`\n`;
|
|
(function(s) { calls++; assertEquals("\u005Cr", s.raw[0]); })`\r`;
|
|
(function(s) { calls++; assertEquals("\u005Ct", s.raw[0]); })`\t`;
|
|
(function(s) { calls++; assertEquals("\u005Cv", s.raw[0]); })`\v`;
|
|
(function(s) { calls++; assertEquals("\u005C`", s.raw[0]); })`\``;
|
|
assertEquals(10, calls);
|
|
|
|
// The TRV of CharacterEscapeSequence :: NonEscapeCharacter is the CV of the
|
|
// NonEscapeCharacter.
|
|
calls = 0;
|
|
(function(s) { calls++; assertEquals("\u005Cz", s.raw[0]); })`\z`;
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of LineTerminatorSequence :: <LF> is the code unit value 0x000A.
|
|
// The TRV of LineTerminatorSequence :: <CR> is the code unit value 0x000A.
|
|
// The TRV of LineTerminatorSequence :: <CR><LF> is the sequence consisting of
|
|
// the code unit value 0x000A.
|
|
calls = 0;
|
|
function testRawLineNormalization(cs) {
|
|
calls++;
|
|
assertEquals(cs.raw[0], "\n\n\n");
|
|
assertEquals(cs.raw[1], "\n\n\n");
|
|
}
|
|
eval("testRawLineNormalization`\r\n\n\r${1}\r\n\n\r`");
|
|
assertEquals(1, calls);
|
|
|
|
// The TRV of LineContinuation :: \ LineTerminatorSequence is the sequence
|
|
// consisting of the code unit value 0x005C followed by the code units of TRV
|
|
// of LineTerminatorSequence.
|
|
calls = 0;
|
|
function testRawLineContinuation(cs) {
|
|
calls++;
|
|
assertEquals(cs.raw[0], "\u005C\n\u005C\n\u005C\n");
|
|
assertEquals(cs.raw[1], "\u005C\n\u005C\n\u005C\n");
|
|
}
|
|
eval("testRawLineContinuation`\\\r\n\\\n\\\r${1}\\\r\n\\\n\\\r`");
|
|
assertEquals(1, calls);
|
|
})();
|
|
|
|
|
|
(function testCallSiteObj() {
|
|
var calls = 0;
|
|
function tag(cs) {
|
|
calls++;
|
|
assertTrue(cs.hasOwnProperty("raw"));
|
|
assertTrue(Object.isFrozen(cs));
|
|
assertTrue(Object.isFrozen(cs.raw));
|
|
var raw = Object.getOwnPropertyDescriptor(cs, "raw");
|
|
assertFalse(raw.writable);
|
|
assertFalse(raw.configurable);
|
|
assertFalse(raw.enumerable);
|
|
assertEquals(Array.prototype, Object.getPrototypeOf(cs.raw));
|
|
assertTrue(Array.isArray(cs.raw));
|
|
assertEquals(Array.prototype, Object.getPrototypeOf(cs));
|
|
assertTrue(Array.isArray(cs));
|
|
|
|
var cooked0 = Object.getOwnPropertyDescriptor(cs, "0");
|
|
assertFalse(cooked0.writable);
|
|
assertFalse(cooked0.configurable);
|
|
assertTrue(cooked0.enumerable);
|
|
|
|
var raw0 = Object.getOwnPropertyDescriptor(cs.raw, "0");
|
|
assertFalse(raw0.writable);
|
|
assertFalse(raw0.configurable);
|
|
assertTrue(raw0.enumerable);
|
|
|
|
var length = Object.getOwnPropertyDescriptor(cs, "length");
|
|
assertFalse(length.writable);
|
|
assertFalse(length.configurable);
|
|
assertFalse(length.enumerable);
|
|
|
|
length = Object.getOwnPropertyDescriptor(cs.raw, "length");
|
|
assertFalse(length.writable);
|
|
assertFalse(length.configurable);
|
|
assertFalse(length.enumerable);
|
|
}
|
|
tag`${1}`;
|
|
assertEquals(1, calls);
|
|
})();
|
|
|
|
|
|
(function testUTF16ByteOrderMark() {
|
|
assertEquals("\uFEFFtest", `\uFEFFtest`);
|
|
assertEquals("\uFEFFtest", eval("`\uFEFFtest`"));
|
|
})();
|
|
|
|
|
|
(function testStringRawAsTagFn() {
|
|
assertEquals("\\u0065\\`\\r\\r\\n\\ntestcheck",
|
|
String.raw`\u0065\`\r\r\n\n${"test"}check`);
|
|
assertEquals("\\\n\\\n\\\n", eval("String.raw`\\\r\\\r\n\\\n`"));
|
|
assertEquals("", String.raw``);
|
|
})();
|
|
|
|
|
|
(function testCallSiteCaching() {
|
|
var callSites = [];
|
|
function tag(cs) { callSites.push(cs); }
|
|
var a = 1;
|
|
var b = 2;
|
|
|
|
// Call-sites are cached by ParseNode. Same tag call in a loop
|
|
// means same template object
|
|
for (var i = 0; i < 2; ++i) {
|
|
tag`head${i == 0 ? a : b}tail`;
|
|
}
|
|
assertEquals(2, callSites.length);
|
|
assertSame(callSites[0], callSites[1]);
|
|
|
|
// Tag calls within eval() never have the same ParseNode as the same tag
|
|
// call from a different eval() invocation.
|
|
for (var i = 0; i < 2; ++i) {
|
|
eval("tag`head${i == 0 ? a : b}tail`");
|
|
}
|
|
assertEquals(4, callSites.length);
|
|
assertTrue(callSites[1] !== callSites[2]);
|
|
assertTrue(callSites[2] !== callSites[3]);
|
|
|
|
(new Function("tag", "a", "b", "return tag`head${a}tail`;"))(tag, 1, 2);
|
|
assertEquals(5, callSites.length);
|
|
assertTrue(callSites[3] !== callSites[4]);
|
|
|
|
(new Function("tag", "a", "b", "return tag`head${b}tail`;"))(tag, 1, 2);
|
|
assertEquals(6, callSites.length);
|
|
assertTrue(callSites[4] !== callSites[5]);
|
|
|
|
callSites = [];
|
|
|
|
tag`foo${a}bar`;
|
|
tag`foo\${.}bar`;
|
|
assertEquals(2, callSites.length);
|
|
assertEquals(2, callSites[0].length);
|
|
assertEquals(1, callSites[1].length);
|
|
|
|
callSites = [];
|
|
|
|
for (var i = 0; i < 2; ++i) {
|
|
eval("tag`\\\r\n\\\n\\\r`");
|
|
}
|
|
assertEquals(2, callSites.length);
|
|
assertTrue(callSites[0] !== callSites[1]);
|
|
assertEquals("", callSites[0][0]);
|
|
assertEquals("\\\n\\\n\\\n", callSites[0].raw[0]);
|
|
|
|
callSites = [];
|
|
|
|
for (var i = 0; i < 2; ++i) {
|
|
tag`\uc548\ub155`;
|
|
}
|
|
assertEquals(2, callSites.length);
|
|
assertSame(callSites[0], callSites[1]);
|
|
assertEquals("안녕", callSites[0][0]);
|
|
assertEquals("\\uc548\\ub155", callSites[0].raw[0]);
|
|
|
|
callSites = [];
|
|
|
|
tag`\uc548\ub155`;
|
|
tag`안녕`;
|
|
assertEquals(2, callSites.length);
|
|
assertTrue(callSites[0] !== callSites[1]);
|
|
assertEquals("안녕", callSites[0][0]);
|
|
assertEquals("\\uc548\\ub155", callSites[0].raw[0]);
|
|
assertEquals("안녕", callSites[1][0]);
|
|
assertEquals("안녕", callSites[1].raw[0]);
|
|
|
|
// Extra-thorough UTF8 decoding test.
|
|
callSites = [];
|
|
|
|
tag`Iñtërnâtiônàlizætiøn\u2603\uD83D\uDCA9`;
|
|
tag`Iñtërnâtiônàlizætiøn☃💩`;
|
|
|
|
assertEquals(2, callSites.length);
|
|
assertTrue(callSites[0] !== callSites[1]);
|
|
assertEquals("Iñtërnâtiônàlizætiøn☃💩", callSites[0][0]);
|
|
assertEquals(
|
|
"Iñtërnâtiônàlizætiøn\\u2603\\uD83D\\uDCA9", callSites[0].raw[0]);
|
|
assertEquals("Iñtërnâtiônàlizætiøn☃💩", callSites[1][0]);
|
|
assertEquals("Iñtërnâtiônàlizætiøn☃💩", callSites[1].raw[0]);
|
|
})();
|
|
|
|
|
|
(function testExtendedArrayPrototype() {
|
|
Object.defineProperty(Array.prototype, 0, {
|
|
set: function() {
|
|
assertUnreachable();
|
|
},
|
|
configurable: true
|
|
});
|
|
function tag(){}
|
|
tag`a${1}b`;
|
|
delete Array.prototype[0];
|
|
})();
|
|
|
|
|
|
(function testRawLineNormalization() {
|
|
function raw0(callSiteObj) {
|
|
return callSiteObj.raw[0];
|
|
}
|
|
assertEquals(eval("raw0`\r`"), "\n");
|
|
assertEquals(eval("raw0`\r\n`"), "\n");
|
|
assertEquals(eval("raw0`\r\r\n`"), "\n\n");
|
|
assertEquals(eval("raw0`\r\n\r\n`"), "\n\n");
|
|
assertEquals(eval("raw0`\r\r\r\n`"), "\n\n\n");
|
|
})();
|
|
|
|
|
|
(function testHarmonyUnicode() {
|
|
function raw0(callSiteObj) {
|
|
return callSiteObj.raw[0];
|
|
}
|
|
assertEquals(raw0`a\u{62}c`, "a\\u{62}c");
|
|
assertEquals(raw0`a\u{000062}c`, "a\\u{000062}c");
|
|
assertEquals(raw0`a\u{0}c`, "a\\u{0}c");
|
|
|
|
assertEquals(`a\u{62}c`, "abc");
|
|
assertEquals(`a\u{000062}c`, "abc");
|
|
})();
|
|
|
|
|
|
(function testLiteralAfterRightBrace() {
|
|
// Regression test for https://code.google.com/p/v8/issues/detail?id=3734
|
|
function f() {}
|
|
`abc`;
|
|
|
|
function g() {}`def`;
|
|
|
|
{
|
|
// block
|
|
}
|
|
`ghi`;
|
|
|
|
{
|
|
// block
|
|
}`jkl`;
|
|
})();
|
|
|
|
|
|
(function testLegacyOctal() {
|
|
assertEquals('\u0000', `\0`);
|
|
assertEquals('\u0000a', `\0a`);
|
|
for (var i = 0; i < 10; i++) {
|
|
var code = "`\\0" + i + "`";
|
|
assertThrows(code, SyntaxError);
|
|
// Not an error if tagged.
|
|
code = "(function(){})" + code;
|
|
assertDoesNotThrow(code, SyntaxError);
|
|
}
|
|
|
|
assertEquals('\\0', String.raw`\0`);
|
|
})();
|
|
|
|
|
|
(function testSyntaxErrorsNonEscapeCharacter() {
|
|
assertThrows("`\\x`", SyntaxError);
|
|
assertThrows("`\\u`", SyntaxError);
|
|
for (var i = 1; i < 8; i++) {
|
|
var code = "`\\" + i + "`";
|
|
assertThrows(code, SyntaxError);
|
|
// Not an error if tagged.
|
|
code = "(function(){})" + code;
|
|
assertDoesNotThrow(code, SyntaxError);
|
|
}
|
|
})();
|
|
|
|
|
|
(function testInvalidNumericEscapes() {
|
|
assertThrows(function() { eval("`\\8`"); }, SyntaxError)
|
|
assertThrows(function() { eval("`\\9`"); }, SyntaxError)
|
|
})();
|
|
|
|
|
|
(function testLegacyOctalEscapesInExpressions() {
|
|
// Allowed in sloppy expression
|
|
assertEquals("\x07", `${"\07"}`);
|
|
|
|
// Disallowed in template tail
|
|
assertThrows("`${\"\\07\"}\\07`", SyntaxError);
|
|
|
|
// Disallowed in strict expression
|
|
assertThrows("`${(function() { \"use strict\"; return \"\\07\"; })()}`",
|
|
SyntaxError);
|
|
})();
|
|
|
|
|
|
var global = this;
|
|
(function testCallNew() {
|
|
"use strict";
|
|
var called = false;
|
|
var calledWith;
|
|
global.log = function(x) { called = true; calledWith = x; }
|
|
|
|
assertInstanceof(new Function`log("test")`, Object);
|
|
assertTrue(called);
|
|
assertSame("test", calledWith);
|
|
delete global.log;
|
|
})();
|
|
|
|
|
|
(function testCallNew2() {
|
|
"use strict";
|
|
var log = [];
|
|
function tag(x) {
|
|
log.push(x);
|
|
if (!(this instanceof tag)) {
|
|
return tag;
|
|
}
|
|
this.x = x === void 0 ? null : x;
|
|
return this;
|
|
}
|
|
// No arguments passed to constructor
|
|
var instance = new tag`x``y``z`;
|
|
assertInstanceof(instance, tag);
|
|
assertSame(tag.prototype, Object.getPrototypeOf(instance));
|
|
assertEquals({ x: null }, instance);
|
|
assertEquals([["x"], ["y"], ["z"], undefined], log);
|
|
|
|
// Arguments passed to constructor
|
|
log.length = 0;
|
|
instance = new tag`x2` `y2` `z2` (`test`);
|
|
assertInstanceof(instance, tag);
|
|
assertSame(tag.prototype, Object.getPrototypeOf(instance));
|
|
assertEquals({ x: "test" }, instance);
|
|
assertEquals([["x2"], ["y2"], ["z2"], "test"], log);
|
|
})();
|
|
|
|
|
|
(function testCallResultOfTagFn() {
|
|
"use strict";
|
|
var i = 0;
|
|
var raw = [];
|
|
function tag(cs) {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
var text = String.raw.apply(null, args);
|
|
if (i++ < 2) {
|
|
raw.push("tag;" + text);
|
|
return tag;
|
|
}
|
|
|
|
raw.push("raw;" + text);
|
|
return text;
|
|
}
|
|
assertEquals("test3", tag`test1``test2``test3`);
|
|
assertEquals([
|
|
"tag;test1",
|
|
"tag;test2",
|
|
"raw;test3"
|
|
], raw);
|
|
})();
|
|
|
|
|
|
(function testReturnValueAsTagFn() {
|
|
"use strict";
|
|
var i = 0;
|
|
function makeTag() {
|
|
return function tag(cs) {
|
|
var args = Array.prototype.slice.call(arguments, 1);
|
|
var rcs = [];
|
|
rcs.raw = cs.map(function(s) {
|
|
return '!' + s + '!';
|
|
});
|
|
args.unshift(rcs);
|
|
return String.raw.apply(null, args);
|
|
}
|
|
}
|
|
assertEquals('!hi!', makeTag()`hi`);
|
|
assertEquals('!test!0!test!', makeTag()`test${0}test`);
|
|
assertEquals('!!', makeTag()``);
|
|
});
|
|
|
|
|
|
(function testToStringSubstitutions() {
|
|
var a = {
|
|
toString: function() { return "a"; },
|
|
valueOf: function() { return "-a-"; }
|
|
};
|
|
var b = {
|
|
toString: function() { return "b"; },
|
|
valueOf: function() { return "-b-"; }
|
|
};
|
|
assertEquals("a", `${a}`);
|
|
assertEquals("ab", `${a}${b}`);
|
|
assertEquals("-a--b-", `${a + b}`);
|
|
assertEquals("-a-", `${a + ""}`);
|
|
assertEquals("1a", `1${a}`);
|
|
assertEquals("1a2", `1${a}2`);
|
|
assertEquals("1a2b", `1${a}2${b}`);
|
|
assertEquals("1a2b3", `1${a}2${b}3`);
|
|
})();
|
|
|
|
|
|
(function testToStringSubstitutionsOrder() {
|
|
var subs = [];
|
|
var log = [];
|
|
function getter(name, value) {
|
|
return {
|
|
get: function() {
|
|
log.push("get" + name);
|
|
return value;
|
|
},
|
|
set: function(v) {
|
|
log.push("set" + name);
|
|
}
|
|
};
|
|
}
|
|
Object.defineProperties(subs, {
|
|
0: getter(0, "a"),
|
|
1: getter(1, "b"),
|
|
2: getter(2, "c")
|
|
});
|
|
|
|
assertEquals("-a-b-c-", `-${subs[0]}-${subs[1]}-${subs[2]}-`);
|
|
assertArrayEquals(["get0", "get1", "get2"], log);
|
|
})();
|
|
|
|
|
|
(function testTaggedToStringSubstitutionsOrder() {
|
|
var subs = [];
|
|
var log = [];
|
|
var tagged = [];
|
|
function getter(name, value) {
|
|
return {
|
|
get: function() {
|
|
log.push("get" + name);
|
|
return value;
|
|
},
|
|
set: function(v) {
|
|
log.push("set" + name);
|
|
}
|
|
};
|
|
}
|
|
Object.defineProperties(subs, {
|
|
0: getter(0, 1),
|
|
1: getter(1, 2),
|
|
2: getter(2, 3)
|
|
});
|
|
|
|
function tag(cs) {
|
|
var n_substitutions = arguments.length - 1;
|
|
var n_cooked = cs.length;
|
|
var e = cs[0];
|
|
var i = 0;
|
|
assertEquals(n_cooked, n_substitutions + 1);
|
|
while (i < n_substitutions) {
|
|
var sub = arguments[i++ + 1];
|
|
var tail = cs[i];
|
|
tagged.push(sub);
|
|
e = e.concat(sub, tail);
|
|
}
|
|
return e;
|
|
}
|
|
|
|
assertEquals("-1-2-3-", tag`-${subs[0]}-${subs[1]}-${subs[2]}-`);
|
|
assertArrayEquals(["get0", "get1", "get2"], log);
|
|
assertArrayEquals([1, 2, 3], tagged);
|
|
|
|
tagged.length = 0;
|
|
log.length = 0;
|
|
assertEquals("-1-", tag`-${subs[0]}-`);
|
|
assertArrayEquals(["get0"], log);
|
|
assertArrayEquals([1], tagged);
|
|
})();
|
|
|
|
|
|
// Since the first argument to the tag function is always an array,
|
|
// eval calls will always just return that array.
|
|
(function testEvalTagStrict() {
|
|
"use strict";
|
|
var f = (x) => eval`a${x}b`;
|
|
var result = f();
|
|
assertEquals(["a", "b"], result);
|
|
assertSame(result, f());
|
|
})();
|
|
|
|
|
|
(function testEvalTagSloppy() {
|
|
var f = (x) => eval`a${x}b`;
|
|
var result = f();
|
|
assertEquals(["a", "b"], result);
|
|
assertSame(result, f());
|
|
})();
|
|
|
|
(function testTaggedTemplateInvalidAssignmentTargetStrict() {
|
|
"use strict";
|
|
function f() {}
|
|
assertThrows(() => Function("++f`foo`"), SyntaxError);
|
|
assertThrows(() => Function("f`foo`++"), SyntaxError);
|
|
assertThrows(() => Function("--f`foo`"), SyntaxError);
|
|
assertThrows(() => Function("f`foo`--"), SyntaxError);
|
|
assertThrows(() => Function("f`foo` = 1"), SyntaxError);
|
|
})();
|
|
|
|
(function testTaggedTemplateInvalidAssignmentTargetSloppy() {
|
|
function f() {}
|
|
assertThrows(() => Function("++f`foo`"), SyntaxError);
|
|
assertThrows(() => Function("f`foo`++"), SyntaxError);
|
|
assertThrows(() => Function("--f`foo`"), SyntaxError);
|
|
assertThrows(() => Function("f`foo`--"), SyntaxError);
|
|
assertThrows(() => Function("f`foo` = 1"), SyntaxError);
|
|
})();
|
|
|
|
// Disable eval caching if a tagged template occurs in a nested function
|
|
var v = 0;
|
|
var templates = [];
|
|
function tag(callSite) { templates.push(callSite); }
|
|
for (v = 0; v < 6; v += 2) {
|
|
eval("(function() { for (var i = 0; i < 2; ++i) tag`Hello${v}world` })()");
|
|
assertSame(templates[v], templates[v + 1]);
|
|
}
|
|
assertNotSame(templates[0], templates[2]);
|
|
assertNotSame(templates[0], templates[4]);
|
|
assertNotSame(templates[1], templates[3]);
|
|
assertNotSame(templates[1], templates[5]);
|
|
assertNotSame(templates[2], templates[4]);
|
|
assertNotSame(templates[3], templates[5]);
|
|
|
|
function makeSource1(id) {
|
|
return `function f() {
|
|
for (var i = 0; i < 2; ++i) tag\`Hello${id}world\`;
|
|
}
|
|
f();`;
|
|
}
|
|
templates = [];
|
|
for (v = 0; v < 6; v += 2) {
|
|
eval(makeSource1(v));
|
|
assertSame(templates[v], templates[v + 1]);
|
|
}
|
|
assertNotSame(templates[0], templates[2]);
|
|
assertNotSame(templates[0], templates[4]);
|
|
assertNotSame(templates[1], templates[3]);
|
|
assertNotSame(templates[1], templates[5]);
|
|
assertNotSame(templates[2], templates[4]);
|
|
assertNotSame(templates[3], templates[5]);
|
|
|
|
templates = [];
|
|
eval("(function() { for (var i = 0; i < 2; ++i) tag`Hello${1}world` })()");
|
|
eval("(function() { for (var i = 0; i < 2; ++i) tag`Hello${2}world` })()");
|
|
eval("(function() { for (var i = 0; i < 2; ++i) tag`Hello${2}world` })()");
|
|
assertSame(templates[0], templates[1]);
|
|
assertNotSame(templates[0], templates[2]);
|
|
assertNotSame(templates[0], templates[4]);
|
|
assertNotSame(templates[1], templates[3]);
|
|
assertNotSame(templates[1], templates[5]);
|
|
assertSame(templates[2], templates[3]);
|
|
assertNotSame(templates[2], templates[4]);
|
|
assertNotSame(templates[3], templates[5]);
|
|
assertSame(templates[4],templates[5]);
|
|
|
|
templates = [];
|
|
eval(makeSource1(1));
|
|
eval(makeSource1(2));
|
|
eval(makeSource1(3));
|
|
assertSame(templates[0], templates[1]);
|
|
assertNotSame(templates[0], templates[2]);
|
|
assertNotSame(templates[0], templates[4]);
|
|
assertNotSame(templates[1], templates[3]);
|
|
assertNotSame(templates[1], templates[5]);
|
|
assertSame(templates[2], templates[3]);
|
|
assertNotSame(templates[2], templates[4]);
|
|
assertNotSame(templates[3], templates[5]);
|
|
assertSame(templates[4],templates[5]);
|
|
|
|
// Disable eval caching if a tagged template occurs in an even deeper nested function
|
|
var v = 0;
|
|
templates = [];
|
|
for (v = 0; v < 6; v += 2) {
|
|
eval("(function() { (function() { for (var i = 0; i < 2; ++i) tag`Hello${v}world` })() })()");
|
|
if (!v) continue;
|
|
assertNotSame(templates[v], templates[v - 1]);
|
|
}
|
|
assertNotSame(templates[0], templates[2]);
|
|
assertNotSame(templates[0], templates[4]);
|
|
assertNotSame(templates[1], templates[3]);
|
|
assertNotSame(templates[1], templates[5]);
|
|
assertNotSame(templates[2], templates[4]);
|
|
assertNotSame(templates[3], templates[5]);
|
|
|
|
function makeSource2(id) {
|
|
return `function f() {
|
|
function innerF() {
|
|
for (var i = 0; i < 2; ++i) tag\`Hello${id}world\`;
|
|
}
|
|
return innerF();
|
|
}
|
|
f();`;
|
|
}
|
|
templates = [];
|
|
for (v = 0; v < 6; v += 2) {
|
|
eval(makeSource2(v));
|
|
assertSame(templates[v], templates[v + 1]);
|
|
}
|
|
assertNotSame(templates[0], templates[2]);
|
|
assertNotSame(templates[0], templates[4]);
|
|
assertNotSame(templates[1], templates[3]);
|
|
assertNotSame(templates[1], templates[5]);
|
|
assertNotSame(templates[2], templates[4]);
|
|
assertNotSame(templates[3], templates[5]);
|
|
|
|
templates = [];
|
|
eval("(function() { (function() { for (var i = 0; i < 2; ++i) tag`Hello${1}world` })() })()");
|
|
eval("(function() { (function() { for (var i = 0; i < 2; ++i) tag`Hello${2}world` })() })()");
|
|
eval("(function() { (function() { for (var i = 0; i < 2; ++i) tag`Hello${2}world` })() })()");
|
|
assertSame(templates[0], templates[1]);
|
|
assertNotSame(templates[0], templates[2]);
|
|
assertNotSame(templates[0], templates[4]);
|
|
assertNotSame(templates[1], templates[3]);
|
|
assertNotSame(templates[1], templates[5]);
|
|
assertSame(templates[2], templates[3]);
|
|
assertNotSame(templates[2], templates[4]);
|
|
assertNotSame(templates[3], templates[5]);
|
|
assertSame(templates[4], templates[5]);
|
|
|
|
templates = [];
|
|
eval(makeSource2(1));
|
|
eval(makeSource2(2));
|
|
eval(makeSource2(3));
|
|
assertSame(templates[0], templates[1]);
|
|
assertNotSame(templates[0], templates[2]);
|
|
assertNotSame(templates[0], templates[4]);
|
|
assertNotSame(templates[1], templates[3]);
|
|
assertNotSame(templates[1], templates[5]);
|
|
assertSame(templates[2], templates[3]);
|
|
assertNotSame(templates[2], templates[4]);
|
|
assertNotSame(templates[3], templates[5]);
|
|
assertSame(templates[4], templates[5]);
|
|
|
|
// Template objects should be kept alive even if only held weakly, and should
|
|
// preserve values when used as weak keys.
|
|
let weak_templates = new WeakMap();
|
|
let weak_tagged_value_id = 0;
|
|
function weakTag(callSite) {
|
|
if (weak_templates.has(callSite)) {
|
|
return weak_templates.get(callSite);
|
|
}
|
|
const value = {id: weak_tagged_value_id++};
|
|
weak_templates.set(callSite, value);
|
|
return value;
|
|
}
|
|
|
|
(function () {
|
|
function valueForTag1(x) {
|
|
return weakTag`Hello${x}world`;
|
|
}
|
|
function valueForTag2(x) {
|
|
return weakTag`Hello${x}world`;
|
|
}
|
|
let valueForTag1_1 = valueForTag1(1);
|
|
gc();
|
|
gc();
|
|
gc();
|
|
let valueForTag1_2 = valueForTag1(2);
|
|
let valueForTag2_0 = valueForTag2(0);
|
|
assertSame(valueForTag1_1, valueForTag1_2);
|
|
assertNotSame(valueForTag1_1, valueForTag2_0);
|
|
})();
|