capture-stack.js (10733B)
1 // |jit-test| --setpref=experimental.error_capture_stack_trace; 2 load(libdir + "asserts.js"); 3 4 if ('captureStackTrace' in Error) { 5 assertEq(Error.captureStackTrace.length, 2); 6 7 let x = Error.captureStackTrace({}); 8 assertEq(x, undefined); 9 10 assertThrowsInstanceOf(() => Error.captureStackTrace(), TypeError); 11 assertThrowsInstanceOf(() => Error.captureStackTrace(2), TypeError); 12 13 Error.captureStackTrace({}, 2); 14 Error.captureStackTrace({}, null); 15 Error.captureStackTrace({}, {}); 16 17 function caller(f) { 18 return f(); 19 } 20 21 function fill() { 22 let x = {} 23 Error.captureStackTrace(x, caller); 24 return x; 25 } 26 27 function test_elision() { 28 let x = caller(fill); 29 let { stack } = x; 30 assertEq(stack.includes("caller"), false); 31 assertEq(stack.includes("fill"), false); 32 33 34 ({ stack } = caller(() => caller(fill))) 35 print(stack); 36 assertEq(stack.includes("caller"), true); // Only elide the first caller! 37 assertEq(stack.includes("fill"), false); 38 } 39 40 test_elision(); 41 42 function nestedLambda(f) { 43 (() => { 44 (() => { 45 (() => { 46 (() => { 47 f(); 48 })(); 49 })(); 50 })(); 51 })(); 52 } 53 54 55 // If we never see a matching frame when requesting a truncated 56 // stack we should return the empty string 57 function test_no_match() { 58 let obj = {}; 59 // test_elision chosen arbitrarily as a function object which 60 // doesn't exist in the call stack here. 61 let capture = () => Error.captureStackTrace(obj, test_elision); 62 nestedLambda(capture); 63 assertEq(obj.stack, ""); 64 } 65 test_no_match() 66 67 function count_frames(str) { 68 return str.split("\n").length 69 } 70 71 function test_nofilter() { 72 let obj = {}; 73 let capture = () => Error.captureStackTrace(obj); 74 nestedLambda(capture); 75 assertEq(count_frames(obj.stack), 9); 76 } 77 test_nofilter(); 78 79 function test_in_eval() { 80 let obj = eval(` 81 let obj = {}; 82 let capture = () => Error.captureStackTrace(obj); 83 nestedLambda(capture); 84 obj 85 `) 86 87 // Same as above, with an eval frame added! 88 assertEq(count_frames(obj.stack), 10); 89 } 90 test_in_eval(); 91 92 // 93 // [[ErrorData]] 94 // 95 const stackGetter = Object.getOwnPropertyDescriptor(Error.prototype, 'stack').get; 96 const getStack = function (obj) { 97 return stackGetter.call(obj); 98 }; 99 100 function test_uncensored() { 101 let err = undefined; 102 function create_err() { 103 err = new Error; 104 Error.captureStackTrace(err, test_uncensored); 105 } 106 107 nestedLambda(create_err); 108 109 // Calling Error.captureStackTrace doesn't mess with the internal 110 // [[ErrorData]] slot 111 assertEq(count_frames(err.stack), 2); 112 assertEq(count_frames(getStack(err)), 9) 113 } 114 test_uncensored() 115 116 // In general, the stacks a non-caller version of Error.captureStackStrace 117 // should match what Error gives you 118 function compare_stacks() { 119 function censor_column(str) { 120 return str.replace(/:(\d+):\d+\n/g, ":$1:censored\n") 121 } 122 123 let obj = {}; 124 let err = (Error.captureStackTrace(obj), new Error) 125 assertEq(censor_column(err.stack), censor_column(obj.stack)); 126 } 127 compare_stacks(); 128 nestedLambda(compare_stacks) 129 130 // New global 131 132 function test_in_global(global) { 133 global.evaluate(caller.toString()); 134 global.evaluate(fill.toString()); 135 global.evaluate(test_elision.toString()); 136 global.evaluate("test_elision()"); 137 138 global.evaluate(nestedLambda.toString()) 139 global.evaluate(test_no_match.toString()); 140 global.evaluate("test_no_match()"); 141 142 143 global.evaluate(compare_stacks.toString()); 144 global.evaluate(` 145 compare_stacks(); 146 nestedLambda(compare_stacks) 147 `) 148 } 149 150 let global = newGlobal(); 151 test_in_global(global); 152 153 let global2 = newGlobal({ principal: 0 }); 154 test_in_global(global2) 155 156 let global3 = newGlobal({ principal: 0xfffff }); 157 test_in_global(global3) 158 159 // What if the caller is a proxy? 160 const caller_proxy = new Proxy(caller, { 161 apply: function (target, thisArg, arguments) { 162 return target(...arguments); 163 } 164 }); 165 166 function fill_proxy() { 167 let x = {} 168 Error.captureStackTrace(x, caller_proxy); 169 return x; 170 } 171 172 // Proxies don't count for elision. 173 function test_proxy_elision() { 174 let x = caller_proxy(fill_proxy); 175 let { stack } = x; 176 assertEq(stack.includes("caller"), true); 177 assertEq(stack.includes("fill_proxy"), true); 178 } 179 test_proxy_elision(); 180 181 const trivial_proxy = new Proxy(caller, {}); 182 function fill_trivial() { 183 let x = {} 184 Error.captureStackTrace(x, trivial_proxy); 185 return x; 186 } 187 188 // Elision doesn't work even on forwarding proxy 189 function test_trivial_elision() { 190 let x = caller(fill_trivial); 191 let { stack } = x; 192 assertEq(stack.includes("caller"), true); 193 assertEq(stack.includes("fill"), true); 194 } 195 test_trivial_elision(); 196 197 // Elision happens through bind 198 function test_bind_elision() { 199 let b = caller.bind(undefined, fill); 200 let { stack } = b(); 201 assertEq(stack.includes("caller"), false); 202 assertEq(stack.includes("fill"), false); 203 } 204 test_bind_elision(); 205 206 // Cross Realm testing 207 208 let nr = newGlobal({ newCompartment: true }) 209 nr.eval(`globalThis.x = {}`); 210 Error.captureStackTrace(nr.x); 211 212 // Test strict definition 213 function test_strict_definition() { 214 "use strict"; 215 assertThrowsInstanceOf(() => Error.captureStackTrace(Object.freeze({ stack: null })), TypeError); 216 } 217 test_strict_definition(); 218 219 function test_property_descriptor() { 220 let o = {}; 221 Error.captureStackTrace(o); 222 let desc = Object.getOwnPropertyDescriptor(o, "stack"); 223 assertEq(desc.configurable, true) 224 assertEq(desc.writable, true) 225 assertEq(desc.enumerable, false) 226 } 227 test_property_descriptor(); 228 229 function test_delete() { 230 let o = {}; 231 Error.captureStackTrace(o); 232 delete o.stack 233 assertEq("stack" in o, false) 234 } 235 test_delete(); 236 237 // Principal testing: This is basic/shell-principals.js extended to support 238 // and compare Error.captureStackTrace. 239 // 240 // Reminder: 241 // > In the shell, a principal is simply a 32-bit mask: P subsumes Q if the 242 // > set bits in P are a superset of those in Q. Thus, the principal 0 is 243 // > subsumed by everything, and the principal ~0 subsumes everything. 244 245 // Given a string of letters |expected|, say "abc", assert that the stack 246 // contains calls to a series of functions named by the next letter from 247 // the string, say a, b, and then c. Younger frames appear earlier in 248 // |expected| than older frames. 249 let count = 0; 250 function check(expected, stack) { 251 print("check(" + JSON.stringify(expected) + ") against:\n" + stack); 252 count++; 253 254 // Extract only the function names from the stack trace. Omit the frames 255 // for the top-level evaluation, if it is present. 256 var split = stack.split(/(.)?@.*\n/).slice(0, -1); 257 if (split[split.length - 1] === undefined) 258 split = split.slice(0, -2); 259 260 print(JSON.stringify(split)); 261 // Check the function names against the expected sequence. 262 assertEq(split.length, expected.length * 2); 263 for (var i = 0; i < expected.length; i++) 264 assertEq(split[i * 2 + 1], expected[i]); 265 } 266 267 var low = newGlobal({ principal: 0 }); 268 var mid = newGlobal({ principal: 0xffff }); 269 var high = newGlobal({ principal: 0xfffff }); 270 271 eval('function a() { let o = {}; Error.captureStackTrace(o); check("a", o.stack); b(); }'); 272 low.eval('function b() { let o = {}; Error.captureStackTrace(o); check("b", o.stack); c(); }'); 273 mid.eval('function c() { let o = {}; Error.captureStackTrace(o); check("cba", o.stack); d(); }'); 274 high.eval('function d() { let o = {}; Error.captureStackTrace(o); check("dcba", o.stack); e(); }'); 275 276 // Globals created with no explicit principals get 0xffff. 277 eval('function e() { let o = {}; Error.captureStackTrace(o); check("ecba", o.stack); f(); }'); 278 279 low.eval('function f() { let o = {}; Error.captureStackTrace(o); check("fb", o.stack); g(); }'); 280 mid.eval('function g() { let o = {}; Error.captureStackTrace(o); check("gfecba", o.stack); h(); }'); 281 high.eval('function h() { let o = {}; Error.captureStackTrace(o); check("hgfedcba", o.stack); }'); 282 283 // Make everyone's functions visible to each other, as needed. 284 b = low.b; 285 low.c = mid.c; 286 mid.d = high.d; 287 high.e = e; 288 f = low.f; 289 low.g = mid.g; 290 mid.h = high.h; 291 292 low.check = mid.check = high.check = check; 293 294 // Kick the whole process off. 295 a(); 296 297 assertEq(count, 8); 298 299 // Ensure filtering is based on caller realm not on target object. 300 low.eval("low_target = {}"); 301 mid.eval("mid_target = {}"); 302 high.eval("high_target = {}"); 303 304 high.low_target = mid.low_target = low.low_target; 305 high.mid_target = low.mid_target = mid.mid_target; 306 mid.high_target = low.high_target = high.high_target; 307 308 high.low_cst = mid.low_cst = low.low_cst = low.Error.captureStackTrace; 309 high.mid_cst = low.mid_cst = mid.mid_cst = mid.Error.captureStackTrace; 310 mid.high_cst = low.high_cst = high.high_cst = high.Error.captureStackTrace; 311 312 for (let g of [low, mid, high]) { 313 assertEq("low_target" in g, true); 314 assertEq("mid_target" in g, true); 315 assertEq("high_target" in g, true); 316 317 assertEq("low_cst" in g, true); 318 assertEq("mid_cst" in g, true); 319 assertEq("high_cst" in g, true); 320 321 // install caller function z -- single letter name for 322 // check compat. 323 g.eval("function z(f) { f() }") 324 } 325 326 low.eval("function q() { Error.captureStackTrace(low_target); }") 327 328 329 high.q = low.q; 330 331 // Caller function z is from high, but using low Error.captureStackTrace, so 332 // z should be elided. 333 high.eval("z(q)"); 334 check("q", low.low_target.stack); 335 336 low.eval("function r() { high_cst(low_target) }") 337 high.r = low.r; 338 339 // Can see everything here using high cst and low target. 340 high.eval("function t() { z(r) }"); 341 high.t(); 342 check("rzt", low.low_target.stack); 343 344 345 }