@fromjs/backend
Version:
1,186 lines (1,185 loc) • 96.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@fromjs/core");
const { instrumentAndRun, server } = core_1.testHelpers;
const traverse_1 = require("./traverse");
const traverse = async function (firstStep, options = {}) {
options = Object.assign({ optimistic: false }, options);
const steps = (await traverse_1.traverse.call(null, firstStep, [], server, options));
return steps;
};
function getStepTypeList(traversalResult) {
return traversalResult.map((t) => t.operationLog.operation);
}
test("Can track concatenation of 'a' and 'b' - (simple)", async () => {
const { normal, tracking, code } = await instrumentAndRun("return 'a' + 'b'");
expect(normal).toBe("ab");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
var t2 = await traverse({ operationLog: tracking, charIndex: 1 });
expect(t1[0].operationLog.result.primitive).toBe("ab");
var t1LastStep = t1[t1.length - 1];
var t2LastStep = t2[t2.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe("a");
expect(t2LastStep.operationLog.operation).toBe("stringLiteral");
expect(t2LastStep.operationLog.result.primitive).toBe("b");
});
test("Can track concatenation of 'a' and 'b' in an add function", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
function add(arg1, arg2) {
return arg1 + arg2
}
return add('a', 'b')
`);
expect(normal).toBe("ab");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
var t2 = await traverse({ operationLog: tracking, charIndex: 1 });
var t1LastStep = t1[t1.length - 1];
var t2LastStep = t2[t2.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe("a");
expect(t2LastStep.operationLog.operation).toBe("stringLiteral");
expect(t2LastStep.operationLog.result.primitive).toBe("b");
expect(getStepTypeList(t1)).toEqual([
"callExpression",
"returnStatement",
"binaryExpression",
"identifier",
"stringLiteral",
]);
});
test("Can traverse concatenation of a string and a number", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const num = 100
return num + "x"
`);
expect(normal).toBe("100x");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
var t2 = await traverse({ operationLog: tracking, charIndex: 4 });
var t1LastStep = t1[t1.length - 1];
var t2LastStep = t2[t2.length - 1];
expect(t1LastStep.operationLog.operation).toBe("numericLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe(100);
expect(t2LastStep.operationLog.operation).toBe("stringLiteral");
expect(t2LastStep.operationLog.result.primitive).toBe("x");
});
test("Can traverse concatenation of a number and a string", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const num = 100
return "x" + num
`);
expect(normal).toBe("x100");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
var t2 = await traverse({ operationLog: tracking, charIndex: 4 });
var t1LastStep = t1[t1.length - 1];
var t2LastStep = t2[t2.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe("x");
expect(t2LastStep.operationLog.operation).toBe("numericLiteral");
expect(t2LastStep.operationLog.result.primitive).toBe(100);
});
describe("Assignment Expressions", () => {
test("Can track values through object assignments", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = {}
obj.a = "x"
return obj.a
`);
var t = await traverse({ operationLog: tracking, charIndex: 0 });
expect(getStepTypeList(t)).toEqual([
"memberExpression",
"assignmentExpression",
"stringLiteral",
]);
});
test("Can track used return values of assignment expressions (to variables)", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var str = "a"
return (str += "b")
`);
expect(normal).toBe("ab");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
expect(t1[0].operationLog.result.primitive).toBe("ab");
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe("a");
var t2 = await traverse({ operationLog: tracking, charIndex: 1 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.operationLog.operation).toBe("stringLiteral");
expect(t2LastStep.operationLog.result.primitive).toBe("b");
});
test("Can traverse += operation for object assignments", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = { a: "a" }
obj.a += "b"
return obj.a
`);
expect(normal).toBe("ab");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe("a");
var t2 = await traverse({ operationLog: tracking, charIndex: 1 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.operationLog.operation).toBe("stringLiteral");
expect(t2LastStep.operationLog.result.primitive).toBe("b");
});
test("Can traverse return value of object assignments", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = { a: "a" }
return (obj.a += "b")
`);
expect(normal).toBe("ab");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1[0].operationLog.result.primitive).toBe("ab");
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe("a");
var t2 = await traverse({ operationLog: tracking, charIndex: 1 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.operationLog.operation).toBe("stringLiteral");
expect(t2LastStep.operationLog.result.primitive).toBe("b");
});
test("Can traverse results of assignment expression", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var v
return v = "a"
`);
expect(normal).toBe("a");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe("a");
expect(t1LastStep.charIndex).toBe(0);
});
test("Can traverse results of assignment expression if assigning to an object", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = {}
return obj.a = "a"
`);
expect(normal).toBe("a");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe("a");
expect(t1LastStep.charIndex).toBe(0);
});
test("Can traverse if the previous value of a function argument was undefined", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
function fn(a,b) {
if (!b) {
b = a
}
return a
}
return fn("a")
`);
expect(normal).toBe("a");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe("a");
expect(t1LastStep.charIndex).toBe(0);
});
test("Works in strict mode when assigning a value to something without a tracking identifier", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
"use strict"
function fn() {}
function fn2() {}
// neither fn___tv nor fv2___tv exist here
fn2 = fn
return "ok"
`, {}, { logCode: false });
expect(normal).toBe("ok");
});
});
test("Can track values through object literals", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = {a: "x"}
return obj.a
`);
var t = await traverse({ operationLog: tracking, charIndex: 0 });
expect(getStepTypeList(t)).toEqual([
"memberExpression",
"objectProperty",
"stringLiteral",
]);
});
test("Can traverse String.prototype.slice", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var str = "abcde"
str = str.slice(2,4)
return str
`);
expect(normal).toBe("cd");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
var lastStep = t[t.length - 1];
expect(lastStep.charIndex).toBe(2);
expect(getStepTypeList(t)).toEqual([
"identifier",
"assignmentExpression",
"callExpression",
"identifier",
"stringLiteral",
]);
});
describe("Array.prototype.splice", () => {
test("Can traverse Array.prototype.splice return value", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = [1,2,3,4]
return arr.splice(1,2).join("")
`);
expect(normal).toBe("23");
let step = await traverseAndGetLastStep(tracking, 0);
expect(step.operationLog.operation).toBe("numericLiteral");
step = await traverseAndGetLastStep(tracking, 1);
expect(step.operationLog.operation).toBe("numericLiteral");
});
});
describe("Object.entries", () => {
test("Return value of Object.entries is tracked", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const entries = Object.entries({a:"A",b:"B"})
return entries[0][0] + entries[0][1] + entries[1][0] + entries[1][1]
`);
expect(normal).toBe("aAbB");
let step = await traverseAndGetLastStep(tracking, 0);
expect(step.operationLog.operation).toBe("stringLiteral");
expect(step.charIndex).toBe(0);
expect(step.operationLog.result.primitive).toBe("a");
step = await traverseAndGetLastStep(tracking, 3);
expect(step.operationLog.operation).toBe("stringLiteral");
expect(step.charIndex).toBe(0);
expect(step.operationLog.result.primitive).toBe("B");
});
});
test("Traverse str[n] character access", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var str = "abcde"
return str[2]
`);
expect(normal).toBe("c");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
var lastStep = t[t.length - 1];
expect(lastStep.charIndex).toBe(2);
expect(lastStep.operationLog.operation).toBe("stringLiteral");
});
test("Traversing += assignment expressions", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var a = "a"
a += "b"
return a
`);
expect(normal).toBe("ab");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
expect(t[0].operationLog.result.primitive).toBe("ab");
var lastStep = t[t.length - 1];
expect(lastStep.operationLog.result.primitive).toBe("a");
});
test("Can track values through conditional expression", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var b = false ? "a" : "b"
return b
`);
expect(normal).toBe("b");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
expect(getStepTypeList(t)).toEqual([
"identifier",
"conditionalExpression",
"stringLiteral",
]);
});
describe("Can traverse string replacement calls", () => {
test("works for simple replacements using regexes", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var ret = "ababab".replace(/b/g, "cc") // => "accaccacc"
return ret
`);
expect(normal).toBe("accaccacc");
var t = await traverse({ operationLog: tracking, charIndex: 5 });
expect(getStepTypeList(t)).toEqual([
"identifier",
"callExpression",
"stringLiteral",
]);
const lastStep = t[t.length - 1];
expect(lastStep.charIndex).toBe(1);
expect(lastStep.operationLog.result.primitive).toBe("cc");
});
test("works for simple replacements using strings", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var ret = "Hello {{name}}!".replace("{{name}}", "Susan")
return ret
`);
expect(normal).toBe("Hello Susan!");
var t1 = await traverse({ operationLog: tracking, charIndex: 2 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.charIndex).toBe(2);
var t2 = await traverse({ operationLog: tracking, charIndex: 6 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.charIndex).toBe(0);
});
test("works for non-match locations behind a match", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var ret = "abc".replace("a", "12345")
return ret
`);
expect(normal).toBe("12345bc");
var t1 = await traverse({ operationLog: tracking, charIndex: 6 }); // "c"
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.charIndex).toBe(2);
});
test("Works for $n substitutions", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var ret = "abbbxy".replace(/(b+)/, "$1<>")
return ret
`);
expect(normal).toBe("abbb<>xy");
var t1 = await traverse({
operationLog: tracking,
charIndex: "abbb<>xy".indexOf("y"),
});
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.charIndex).toBe("abbbxy".indexOf("y"));
});
test("Can handle numbers after a dollar sign that don't identify the submatch", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var ret = "_abc#_".replace(/([a-z]+)#/g, '$1' + 123)
return ret
`);
expect(normal).toBe("_abc123_");
var t1 = await traverse({
operationLog: tracking,
charIndex: "abbb<>xy".indexOf("y"),
});
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.charIndex).toBe("abbbxy".indexOf("y"));
});
test("Doesn't break replace with numbers after dollar sign if there's a subgroup but no submatch", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var ret = "# insecure requests found".replace(/(^|[^\\\\])#/g, '$1' + 555)
return ret
`);
expect(normal).toBe("555 insecure requests found");
var t1 = await traverse({ operationLog: tracking, charIndex: 1 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("numericLiteral");
});
});
describe("JSON.parse", () => {
it("Can traverse JSON.parse", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var json = '{"a": {"b": "xyz"}}';
var obj = JSON.parse(json);
return obj.a.b
`);
var t = await traverse({ operationLog: tracking, charIndex: 2 });
var lastStep = t[t.length - 1];
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(15);
});
it("Can traverse JSON.parse when using keys", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var json = '{"a":"abc", "b": "bcd"}';
var obj = JSON.parse(json);
return Object.keys(obj)[1]
`);
var t = await traverse({ operationLog: tracking, charIndex: 0 });
var lastStep = t[t.length - 1];
expect(normal).toBe("b");
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(13);
});
it("Can traverse JSON.parse with JSON containing arrays", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var json = '{"a": {"b": ["one", "two"]}}';
var obj = JSON.parse(json);
return obj.a.b[1]
`);
var t = await traverse({ operationLog: tracking, charIndex: 0 });
var lastStep = t[t.length - 1];
expect(normal).toBe("two");
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(21);
});
it("Returns the correct character index for longer strings", async () => {
const text = `{"a": {"b": "Hello"}}`;
const { normal, tracking, code } = await instrumentAndRun(`
var json = '${text}';
var obj = JSON.parse(json);
return obj.a.b
`);
var t = await traverse({
operationLog: tracking,
charIndex: "Hello".indexOf("l"),
});
var lastStep = t[t.length - 1];
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(text.indexOf("l"));
});
it("Can find property names in the JSON", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = JSON.parse('{"a": {"b": 5}}');
let ret
for (var name in obj) {
ret = name
}
return ret
`);
expect(normal).toBe("a");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
var lastStep = t[t.length - 1];
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(2);
expect(getStepTypeList(t)).toContain("jsonParseResult");
});
it("Can handle character mapping if the JSON contains an escaped line break", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var json = '{"a": "\\\\nHello"}';
var obj = JSON.parse(json);
return obj.a + "|" + json
`);
const json = normal.split("|")[1];
var t = await traverse({
operationLog: tracking,
charIndex: normal.indexOf("l"),
});
var lastStep = t[t.length - 1];
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(json.indexOf("l"));
});
it("Can handle character mapping if the JSON contains an unicode escape sequence ", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var json = '{"a": "\\\\u003cHello"}';
var obj = JSON.parse(json);
return obj.a + "|" + json
`);
const json = normal.split("|")[1];
var t = await traverse({
operationLog: tracking,
charIndex: normal.indexOf("l"),
});
var lastStep = t[t.length - 1];
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(json.indexOf("l"));
});
it("Can handle JSON that just contains a string", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var json = '"abc"';
var str = JSON.parse(json);
return str
`);
var t = await traverse({
operationLog: tracking,
charIndex: normal.indexOf("b"),
});
var lastStep = t[t.length - 1];
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe('"abc"'.indexOf("b"));
});
it("Can handle JSON that just contains a number", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var json = '5';
var str = JSON.parse(json);
return str
`);
expect(normal).toBe(5);
var t = await traverse({
operationLog: tracking,
charIndex: 0,
});
var lastStep = t[t.length - 1];
expect(lastStep.operationLog.operation).toBe("stringLiteral");
});
});
describe("JSON.stringify", () => {
it("Can traverse a JSON.stringify result", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = {greeting: "Hello ", name: {first: "w", last: "orld"}}
var str = JSON.stringify(obj, null, 4);
return str
`);
var lastStep = await traverseAndGetLastStep(tracking, normal.indexOf("Hello"));
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(0);
expect(lastStep.operationLog.result.primitive).toBe("Hello ");
var lastStep = await traverseAndGetLastStep(tracking, normal.indexOf("orld"));
expect(lastStep.charIndex).toBe(0);
expect(lastStep.operationLog.result.primitive).toBe("orld");
var lastStep = await traverseAndGetLastStep(tracking, normal.indexOf("first"));
expect(lastStep.charIndex).toBe(0);
expect(lastStep.operationLog.result.primitive).toBe("first");
});
it("Can traverse JSON.stringify result that's not prettified", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = {greeting: "Hello ", name: {first: "w", last: "orld"}}
var str = JSON.stringify(obj);
return str
`);
var lastStep = await traverseAndGetLastStep(tracking, normal.indexOf("orld") + 2);
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(2);
expect(lastStep.operationLog.result.primitive).toBe("orld");
});
it("Can traverse JSON.stringify correctly when looking at keys", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = {greeting: "Hello "};
var str = JSON.stringify(obj);
return str
`);
var lastStep = await traverseAndGetLastStep(tracking, normal.indexOf("greeting"));
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(0);
expect(lastStep.operationLog.result.primitive).toBe("greeting");
});
it("Can traverse JSON.stringify result where keys are used multiple times", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = {
one: {hello: 123},
two: {}
}
obj.two["he" + "llo"] = 456
var str = JSON.stringify(obj);
return str
`);
var lastStep = await traverseAndGetLastStep(tracking, normal.lastIndexOf("hello"));
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(0);
expect(lastStep.operationLog.result.primitive).toBe("he");
});
it("Can handle arrays", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["one", "two"]
var str = JSON.stringify(arr);
return str
`);
var lastStep = await traverseAndGetLastStep(tracking, normal.indexOf("two"));
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(0);
expect(lastStep.operationLog.result.primitive).toBe("two");
});
it("Can handle nested arrays", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["one", ["two", "three"]]
var str = JSON.stringify(arr);
return str
`);
var lastStep = await traverseAndGetLastStep(tracking, normal.indexOf("three"));
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(0);
expect(lastStep.operationLog.result.primitive).toBe("three");
});
it("Doesn't break if property names contain dots", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = {"a.b": "c"}
var str = JSON.stringify(obj);
return str
`);
var lastStep = await traverseAndGetLastStep(tracking, normal.indexOf("c"));
expect(lastStep.operationLog.operation).toBe("stringLiteral");
expect(lastStep.charIndex).toBe(0);
expect(lastStep.operationLog.result.primitive).toBe("c");
});
it("Doesn't break if there are undefiend properties", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var obj = {"a": undefined, "b": 5}
var str = JSON.stringify(obj);
return str
`);
var lastStep = await traverseAndGetLastStep(tracking, normal.indexOf("5"));
expect(lastStep.operationLog.operation).toBe("numericLiteral");
});
it("Can handle JSON that just contains a string", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var json = "abc";
var str = JSON.stringify(json);
return str
`);
expect(normal).toBe('"abc"');
var lastStep = await traverseAndGetLastStep(tracking, normal.indexOf("abc"));
expect(lastStep.operationLog.operation).toBe("stringLiteral");
});
it("Can handle JSON that just contains a number", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var json = 5;
var str = JSON.stringify(json);
return str
`);
expect(normal).toBe("5");
var t = await traverse({
operationLog: tracking,
charIndex: 0,
});
var lastStep = t[t.length - 1];
expect(lastStep.operationLog.operation).toBe("numericLiteral");
});
it("Can handle values that are Symbols", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var json = {a: Symbol("sth")};
var str = JSON.stringify(json);
return str
`);
expect(normal).toBe("{}");
});
});
it("Can traverse arguments for a function expression (rather than a function declaration)", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var fn = function(a) {
return a
}
return fn("a")
`);
expect(normal).toBe("a");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
const tLastStep = t[t.length - 1];
expect(getStepTypeList(t)).toEqual([
"callExpression",
"returnStatement",
"identifier",
"stringLiteral",
]);
});
describe("String.prototype.substr", () => {
it("Works in when passing a positive start and length", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
return "abcde".substr(2, 2)
`);
expect(normal).toBe("cd");
var t = await traverse({ operationLog: tracking, charIndex: 1 }); // char "d"
const tLastStep = t[t.length - 1];
expect(tLastStep.charIndex).toBe(3);
});
it("Works in when passing a start argument only", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
return "abcde".substr(2)
`);
expect(normal).toBe("cde");
var t = await traverse({ operationLog: tracking, charIndex: 1 }); // char "c"
const tLastStep = t[t.length - 1];
expect(tLastStep.charIndex).toBe(3);
});
it("Works in when passing a negative start argument", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
return "abcde".substr(-2, 1)
`);
expect(normal).toBe("d");
var t = await traverse({ operationLog: tracking, charIndex: 0 }); // char "d"
const tLastStep = t[t.length - 1];
expect(tLastStep.charIndex).toBe(3);
});
});
describe("String.prototype.trim", () => {
it("Adjusts char index based on amount of whitespace removed", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const str = String.fromCharCode(10) + " ab cd"
return str.trim()
`);
expect(normal).toBe("ab cd");
var t = await traverse({ operationLog: tracking, charIndex: 3 }); // char "c"
const tLastStep = t[t.length - 1];
expect(tLastStep.charIndex).toBe(8);
});
it("Doesn't break if there's no whitespace at the start", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const str = "abc"
return str.trim()
`);
expect(normal).toBe("abc");
var t = await traverse({ operationLog: tracking, charIndex: 2 }); // char "c"
const tLastStep = t[t.length - 1];
expect(tLastStep.charIndex).toBe(2);
});
it("Doesn't break when called with apply", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var str = " a"
return String.prototype.trim.apply(str, [])
`);
expect(normal).toBe("a");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
const tLastStep = t[t.length - 1];
expect(tLastStep.charIndex).toBe(1);
});
});
describe("call/apply", () => {
it("Correctly traverses argument values when they are passed in with apply", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
function fn(str1, str2, str3) {
return str3
}
return fn.apply(null, ["a", "b", "c"])
`);
expect(normal).toBe("c");
var t = await traverseAndGetLastStep(tracking, 0);
expect(t.operationLog.operation).toBe("stringLiteral");
});
it("Correctly traverses argument values when they are passed in with call", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
function fn(str1, str2, str3) {
return str3
}
return fn.call(null, "a", "b", "c")
`);
expect(normal).toBe("c");
var t = await traverseAndGetLastStep(tracking, 0);
expect(t.operationLog.operation).toBe("stringLiteral");
});
});
describe("It can traverse logical expressions", () => {
it("Can traverse ||", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
function fn(a, b) {
b = b || a
return b
}
return fn("a") + fn("x", "y")
`, {}, { logCode: false });
expect(normal).toBe("ay");
var t = await traverseAndGetLastStep(tracking, 0);
console.log(JSON.stringify(t, null, 2));
// I disabled the scopeHasIdentifierWithTrackingIdentifier check it broke when injecting
// the code of a function into the browser – we assumed that var__tv is in scope
// but it's not in the browser
// Disabling this means that we check if b___tv is typeof undefined, and it is
// but that's not because the value isn't in scope but because it's actually undefined
// expect(t.operationLog.operation).toBe("stringLiteral");
expect(t.operationLog.result.primitive).toBe("a");
var t = await traverseAndGetLastStep(tracking, 1);
// expect(t.operationLog.operation).toBe("stringLiteral");
expect(t.operationLog.result.primitive).toBe("y");
});
it("Doesn't break the execution logic of || expressions", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
let str = "a";
function setStrToB() {
str = "b"
}
("a" || setStrToB())
return str
`
// {},
// { logCode: true }
);
expect(normal).toBe("a");
});
it("Doesn't break the execution logic of nested || expressions", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
let str = "a";
function setStrToB() {
str = "b"
}
function setStrToC() {
str = "c"
return true
}
(null || setStrToC() || setStrToB())
return str
`
// {},
// { logCode: true }
);
expect(normal).toBe("c");
});
it("Doesn't break the execution logic of && expressions", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
let str = "a";
function setStrToB() {
str = "b"
}
false && setStrToB()
return str
`
// {},
// { logCode: true }
);
expect(normal).toBe("a");
});
});
it("Can traverse arguments fn.apply(this, arguments)", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
function f1() {
return f2.apply(this, arguments)
}
function f2(a) {
return a + a
}
return f1("a")
`);
expect(normal).toBe("aa");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
const tLastStep = t[t.length - 1];
expect(tLastStep.operationLog.operation).toBe("stringLiteral");
});
describe("Arrays", () => {
it("Can traverse values in array literals", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["a", "b"]
return arr[1]
`);
expect(normal).toBe("b");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
const tLastStep = t[t.length - 1];
expect(tLastStep.operationLog.operation).toBe("stringLiteral");
});
it("Can traverse pushed values", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = []
arr.push("a", "b")
return arr[1]
`);
expect(normal).toBe("b");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
const tLastStep = t[t.length - 1];
expect(tLastStep.operationLog.operation).toBe("stringLiteral");
});
it("Can traverse shifted/unshifted values", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const arr = ["a", "c"]
const v = arr.shift()
arr.unshift("b")
return v + arr[0] + arr[1]
`);
expect(normal).toBe("abc");
var t = await traverse({ operationLog: tracking, charIndex: 0 });
var tLastStep = t[t.length - 1];
expect(tLastStep.operationLog.operation).toBe("stringLiteral");
expect(tLastStep.operationLog.result.primitive).toBe("a");
var t = await traverse({ operationLog: tracking, charIndex: 1 });
var tLastStep = t[t.length - 1];
expect(tLastStep.operationLog.operation).toBe("stringLiteral");
expect(tLastStep.operationLog.result.primitive).toBe("b");
var t = await traverse({ operationLog: tracking, charIndex: 2 });
var tLastStep = t[t.length - 1];
console.log(JSON.stringify(tLastStep, null, 2));
expect(tLastStep.operationLog.operation).toBe("stringLiteral");
expect(tLastStep.operationLog.result.primitive).toBe("c");
});
describe("Array.prototype.join", () => {
it("Can traverse basic join call", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["ab", "cd"]
return arr.join("-#-")
`);
expect(normal).toBe("ab-#-cd");
var t1 = await traverse({ operationLog: tracking, charIndex: 6 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.charIndex).toBe(1);
expect(t1LastStep.operationLog.result.primitive).toBe("cd");
var t2 = await traverse({ operationLog: tracking, charIndex: 3 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.charIndex).toBe(1);
expect(t2LastStep.operationLog.result.primitive).toBe("-#-");
});
});
it("Can traverse join calls with the default separator (,)", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["a", "b"]
return arr.join()
`);
expect(normal).toBe("a,b");
var t1 = await traverse({ operationLog: tracking, charIndex: 1 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.charIndex).toBe(0);
expect(t1LastStep.operationLog.result.primitive).toBe(",");
});
it("Can traverse join calls with undefined/null values", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
return [null, undefined].join("-")
`);
expect(normal).toBe("-");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.charIndex).toBe(0);
expect(t1LastStep.operationLog.result.primitive).toBe("-");
});
it("Can traverse join called with .call", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
return Array.prototype.join.call(["a","b"], "-")
`);
expect(normal).toBe("a-b");
var t1 = await traverse({ operationLog: tracking, charIndex: 2 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.charIndex).toBe(0);
expect(t1LastStep.operationLog.result.primitive).toBe("b");
});
it("Works on array like objects", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
return Array.prototype.join.call({0: "a", 1: "b", length: 2})
`);
expect(normal).toBe("a,b");
var t1 = await traverse({ operationLog: tracking, charIndex: 2 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.charIndex).toBe(0);
expect(t1LastStep.operationLog.result.primitive).toBe("b");
});
});
it("Tracks Object.keys", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const obj = {}
obj["a"] = 100
return Object.keys(obj)[0]
`);
expect(normal).toBe("a");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
});
it("Can traverse Object.assign", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
let obj = {a: "a"}
let obj2 = {b: "b"}
obj = Object.assign(obj, obj2)
return obj.b
`);
expect(normal).toBe("b");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
});
describe("Array.slice", () => {
it("Supports traversal of normal usage", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1", "2", "3"]
return arr.slice(1,3)[1]
`);
expect(normal).toBe("3");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
});
it("Supports traversal with negative start index", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1", "2", "3", "4"]
return arr.slice(-3, 3)[1]
`);
expect(normal).toBe("3");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
});
it("Supports traversal with negative start index and no end index", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1", "2", "3", "4"]
return arr.slice(-2)[1]
`);
expect(normal).toBe("4");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
});
it("Supports traversal with positive start index and no end index", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1", "2", "3", "4"]
return arr.slice(1)[2]
`);
expect(normal).toBe("4");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
});
it("Supports traversal with positive start index and negative end index", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1", "2", "3", "4"]
return arr.slice(1, -1)[1]
`);
expect(normal).toBe("3");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
});
it("Supports being called with .call and arguments object", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const slice = Array.prototype.slice
function fn() {
const arr = slice.call(arguments)
return arr[0]
}
return fn("Hi")
`);
expect(normal).toBe("Hi");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
});
});
describe("Array.prototype.map", () => {
it("Passes values through to mapping function", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1", "2"]
return arr.map(function(value){
return value
})[1]
`);
expect(normal).toBe("2");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
});
it("Supports all callback arguments and thisArg", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1", "2"]
return arr.map(function(value, index, array){
return value + index + array.length + this
}, "x")[0]
`);
expect(normal).toBe("102x");
});
it("When invoked with .apply, still supports all callback arguments and thisArg", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1", "2"]
const map = arr.map
var arr2 = map.apply(arr, [function(value, index, array){
return value + index + array.length + this
}, "x"])
return arr2[0]
`);
expect(normal).toBe("102x");
});
it("When invoked with .call, still supports all callback arguments and thisArg", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1", "2"]
const map = arr.map
var arr2 = map.call(arr, function(value, index, array){
return value + index + array.length + this
}, "x")
return arr2[0]
`);
expect(normal).toBe("102x");
});
});
describe("Array.prototype.filter", () => {
it("Passes values through to filter function and tracks result values", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1", "2", "3", "4"]
let v
const greaterThan2 = arr.filter(function(value){
v = value
return parseFloat(value) > 2
})
return v + "-" + greaterThan2[0]
`);
expect(normal).toBe("4-3");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
var t2 = await traverse({ operationLog: tracking, charIndex: 2 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.operationLog.operation).toBe("stringLiteral");
});
it("Doesn't break this argument", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
let arr = ["1","2","3","4"]
let t
arr = arr.filter(function(value){
t = this.toString()
return value > parseFloat(this)
}, "2")
return arr[0] + t
`);
expect(normal).toBe("32");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
});
});
describe("Array.concat", () => {
it("Works when calling .concat with an array argument", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1"].concat(["2"])
return arr[0] + arr[1]
`);
expect(normal).toBe("12");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.operationLog.result.primitive).toBe("1");
var t2 = await traverse({ operationLog: tracking, charIndex: 1 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.operationLog.operation).toBe("stringLiteral");
expect(t2LastStep.operationLog.result.primitive).toBe("2");
});
it("Works when calling .concat with an non-array argument", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
var arr = ["1"].concat("2")
return arr[0] + arr[1]
`);
expect(normal).toBe("12");
var t2 = await traverse({ operationLog: tracking, charIndex: 1 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.operationLog.operation).toBe("stringLiteral");
expect(t2LastStep.operationLog.result.primitive).toBe("2");
});
});
describe("encodeURICompoennt", () => {
it("Can traverse encodeURIComponent", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
return encodeURIComponent("a@b#c")
`);
expect(normal).toBe("a%40b%23c");
var t1 = await traverse({ operationLog: tracking, charIndex: 7 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.charIndex).toBe(3);
var t2 = await traverse({ operationLog: tracking, charIndex: 9 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.charIndex).toBe(5);
});
it("Can traverse encodeURIComponent when called with .call", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
return encodeURIComponent.call(null, "a@b#c")
`);
expect(normal).toBe("a%40b%23c");
var t1 = await traverse({ operationLog: tracking, charIndex: 7 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.charIndex).toBe(3);
var t2 = await traverse({ operationLog: tracking, charIndex: 9 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.charIndex).toBe(5);
});
});
it("Can traverse decodeURIComponent", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
return decodeURIComponent("a%40b%23c")
`);
expect(normal).toBe("a@b#c");
var t1 = await traverse({ operationLog: tracking, charIndex: 3 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.charIndex).toBe(5);
var t2 = await traverse({ operationLog: tracking, charIndex: 4 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.charIndex).toBe(8);
});
describe("String.prototype.match", () => {
test("Can traverse String.prototype.match results", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const arr = "a4c89a".match(/[0-9]+/g)
return arr[0] + arr[1]
`);
expect(normal).toBe("489");
var t1 = await traverse({ operationLog: tracking, charIndex: 0 });
const t1LastStep = t1[t1.length - 1];
expect(t1LastStep.operationLog.operation).toBe("stringLiteral");
expect(t1LastStep.charIndex).toBe(1);
var t2 = await traverse({ operationLog: tracking, charIndex: 2 });
const t2LastStep = t2[t2.length - 1];
expect(t2LastStep.charIndex).toBe(4);
});
// not technically a traversal test but it makes sense to have .match tests in one place
// ... maybe we should just merge core.test.ts with traverse.test.ts
// ... or move them next to the relevant files, like callexpression.ts
it("Doesn't break on non-global regexes with multiple match groups", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const arr = "zzabc".match(/(a)(b)/)
return arr[0] + arr[1] + arr[2]
`);
expect(normal).toBe("abab");
});
it("Tries to not get confused with same matched characters in mutliple groups", async () => {
const { normal, tracking, code } = await instrumentAndRun(`
const arr = "zzaba".match(/(a)(b)(a)/)
return arr[1] + arr[2] + arr[3]
`);
expect(normal).toBe("aba");
var t2 = await traverse({ operationLog: tracking, charIndex: 2 });
const t2LastStep = t2[t2.len