|
1 // Two Environments nested in the same runtime scope share the correct tail of their parent chains. |
|
2 |
|
3 // The compiler must be allowed to elide empty scopes and so forth, so this |
|
4 // test does not check the number of unshared Environments. Instead, each test |
|
5 // case identifies the expected innermost shared scope by the name of a |
|
6 // variable in it. |
|
7 |
|
8 var g = newGlobal(); |
|
9 g.eval("function h() { debugger; }"); |
|
10 var dbg = Debugger(g); |
|
11 var hits, name, shared, unshared; |
|
12 dbg.onDebuggerStatement = function (hframe) { |
|
13 var frame = hframe.older; |
|
14 |
|
15 // Find name in frame.environment. |
|
16 var env, child = null; |
|
17 for (env = frame.environment; env !== null; env = env.parent) { |
|
18 if (env.names().indexOf(name) != -1) |
|
19 break; |
|
20 child = env; |
|
21 } |
|
22 assertEq(env !== null, true, "expected '" + name + "' to be in scope"); |
|
23 assertEq(env, frame.environment.find(name), |
|
24 "env.find should find the same frame as the written out search"); |
|
25 |
|
26 if (hits === 0) { |
|
27 // First hit. |
|
28 shared = env; |
|
29 unshared = child; |
|
30 } else { |
|
31 // Subsequent hit. |
|
32 assertEq(env, shared, "the environment containing '" + name + "' should be shared"); |
|
33 assertEq(child === null || unshared === null || unshared !== child, true, |
|
34 "environments nested within the one containing '" + name + "' should not be shared"); |
|
35 } |
|
36 hits++; |
|
37 }; |
|
38 |
|
39 function test(sharedName, expectedHits, code) { |
|
40 hits = 0; |
|
41 name = sharedName; |
|
42 shared = unshared = undefined; |
|
43 g.eval(code); |
|
44 assertEq(hits, expectedHits); |
|
45 } |
|
46 |
|
47 // Basic test cases. |
|
48 // |
|
49 // (The stray "a = b" assignments in these tests are to inhibit the flat closure |
|
50 // optimization, which Environments expose. There's nothing really wrong with |
|
51 // the optimization or with the debugger exposing it, but that's not what we |
|
52 // want to test here.) |
|
53 |
|
54 test("q", 2, "var q = function (a) { h(); }; q(1); q(2);"); |
|
55 test("a", 2, "q = function (a) { (function (b) { h(); a = b; })(2); h(); }; q(1);"); |
|
56 test("a", 2, "q = function (a) { h(); return function (b) { h(); a = b; }; }; q(1)(2);"); |
|
57 test("n", 3, "q = function (n) { for (var i = 0; i < n; i++) { let (j = i) { h(); } } }; q(3);"); |
|
58 |
|
59 // A function with long dynamic and static chains. |
|
60 var N = 80; |
|
61 |
|
62 var code = "function f" + N + "(a" + N + ") {\neval('a0 + a1'); h();\n}\n"; |
|
63 for (var i = N; --i >= 0;) { |
|
64 var call = "f" + (i + 1) + "(a" + i + " - 1);\n"; |
|
65 code = ("function f" + i + "(a" + i + ") {\n" + |
|
66 code + |
|
67 call + |
|
68 "if (a" + i + " === 0) " + call + |
|
69 "}\n"); |
|
70 } |
|
71 |
|
72 g.eval(code); |
|
73 test("a0", 2, "f0(0);"); |
|
74 test("a17", 2, "f0(17);"); |
|
75 test("a" + (N-2), 2, "f0(" + (N-2) + ");"); |
|
76 test("a" + (N-1), 2, "f0(" + (N-1) + ");"); |
|
77 |
|
78 // A function with a short dynamic chain and a long static chain. |
|
79 N = 60; |
|
80 |
|
81 function DeepStaticShallowDynamic(i, n) { |
|
82 var code = "function f" + i + "(a" + i + ") {\n"; |
|
83 if (i >= n) |
|
84 code += "eval('a1 + a2'); h();\n"; |
|
85 else |
|
86 code += "return " + DeepStaticShallowDynamic(i+1, n) + ";\n"; |
|
87 code += "}"; |
|
88 return code; |
|
89 } |
|
90 g.eval(DeepStaticShallowDynamic(1, N)); |
|
91 |
|
92 function range(start, stop) { |
|
93 for (var i = start; i < stop; i++) |
|
94 yield i; |
|
95 } |
|
96 |
|
97 function DSSDsplit(s) { |
|
98 return ("var mid = f1" + ["(" + i + ")" for (i in range(0, s))].join("") + ";\n" + |
|
99 "mid" + ["(" + i + ")" for (i in range(s, N))].join("") + ";\n" + |
|
100 "mid" + ["(" + i + ")" for (i in range(s, N))].join("") + ";\n"); |
|
101 } |
|
102 |
|
103 test("a1", 2, DSSDsplit(1)); |
|
104 test("a17", 2, DSSDsplit(17)); |
|
105 test("a" + (N-2), 2, DSSDsplit(N-2)); |
|
106 test("a" + (N-1), 2, DSSDsplit(N-1)); |