testSavedStacks.cpp (14586B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/Utf8.h" // mozilla::Utf8Unit 8 9 #include "builtin/TestingFunctions.h" 10 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin 11 #include "js/CompilationAndEvaluation.h" // JS::Evaluate 12 #include "js/Exception.h" 13 #include "js/SavedFrameAPI.h" 14 #include "js/SourceText.h" // JS::Source{Ownership,Text} 15 #include "js/Stack.h" 16 #include "jsapi-tests/tests.h" 17 #include "util/Text.h" 18 #include "vm/ArrayObject.h" 19 #include "vm/Realm.h" 20 #include "vm/SavedStacks.h" 21 22 BEGIN_TEST(testSavedStacks_withNoStack) { 23 JS::Realm* realm = cx->realm(); 24 realm->setAllocationMetadataBuilder(&js::SavedStacks::metadataBuilder); 25 JS::RootedObject obj(cx, js::NewDenseEmptyArray(cx)); 26 realm->setAllocationMetadataBuilder(nullptr); 27 return true; 28 } 29 END_TEST(testSavedStacks_withNoStack) 30 31 BEGIN_TEST(testSavedStacks_ApiDefaultValues) { 32 JS::Rooted<js::SavedFrame*> savedFrame(cx, nullptr); 33 34 JSPrincipals* principals = cx->realm()->principals(); 35 36 // Source 37 JS::RootedString str(cx); 38 JS::SavedFrameResult result = 39 JS::GetSavedFrameSource(cx, principals, savedFrame, &str); 40 CHECK(result == JS::SavedFrameResult::AccessDenied); 41 CHECK(str.get() == cx->runtime()->emptyString); 42 43 // Line 44 uint32_t line = 123; 45 result = JS::GetSavedFrameLine(cx, principals, savedFrame, &line); 46 CHECK(result == JS::SavedFrameResult::AccessDenied); 47 CHECK(line == 0); 48 49 // Column 50 JS::TaggedColumnNumberOneOrigin column(JS::LimitedColumnNumberOneOrigin(123)); 51 result = JS::GetSavedFrameColumn(cx, principals, savedFrame, &column); 52 CHECK(result == JS::SavedFrameResult::AccessDenied); 53 CHECK(column == JS::TaggedColumnNumberOneOrigin()); 54 55 // Function display name 56 result = 57 JS::GetSavedFrameFunctionDisplayName(cx, principals, savedFrame, &str); 58 CHECK(result == JS::SavedFrameResult::AccessDenied); 59 CHECK(str.get() == nullptr); 60 61 // Parent 62 JS::RootedObject parent(cx); 63 result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent); 64 CHECK(result == JS::SavedFrameResult::AccessDenied); 65 CHECK(parent.get() == nullptr); 66 67 // Stack string 68 CHECK(JS::BuildStackString(cx, principals, savedFrame, &str)); 69 CHECK(str.get() == cx->runtime()->emptyString); 70 71 return true; 72 } 73 END_TEST(testSavedStacks_ApiDefaultValues) 74 75 BEGIN_TEST(testSavedStacks_RangeBasedForLoops) { 76 CHECK(js::DefineTestingFunctions(cx, global, false, false)); 77 78 JS::RootedValue val(cx); 79 CHECK( 80 evaluate("(function one() { \n" // 1 81 " return (function two() { \n" // 2 82 " return (function three() { \n" // 3 83 " return saveStack(); \n" // 4 84 " }()); \n" // 5 85 " }()); \n" // 6 86 "}()); \n", // 7 87 "filename.js", 1, &val)); 88 89 CHECK(val.isObject()); 90 JS::RootedObject obj(cx, &val.toObject()); 91 92 CHECK(obj->is<js::SavedFrame>()); 93 JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>()); 94 95 JS::Rooted<js::SavedFrame*> rf(cx, savedFrame); 96 for (JS::Handle<js::SavedFrame*> frame : 97 js::SavedFrame::RootedRange(cx, rf)) { 98 JS_GC(cx); 99 CHECK(frame == rf); 100 rf = rf->getParent(); 101 } 102 CHECK(rf == nullptr); 103 104 // Stack string 105 static const char SpiderMonkeyStack[] = 106 "three@filename.js:4:14\n" 107 "two@filename.js:5:6\n" 108 "one@filename.js:6:4\n" 109 "@filename.js:7:2\n"; 110 static const char V8Stack[] = 111 " at three (filename.js:4:14)\n" 112 " at two (filename.js:5:6)\n" 113 " at one (filename.js:6:4)\n" 114 " at filename.js:7:2"; 115 struct { 116 js::StackFormat format; 117 const char* expected; 118 } expectations[] = {{js::StackFormat::Default, SpiderMonkeyStack}, 119 {js::StackFormat::SpiderMonkey, SpiderMonkeyStack}, 120 {js::StackFormat::V8, V8Stack}}; 121 auto CheckStacks = [&]() { 122 for (auto& expectation : expectations) { 123 JS::RootedString str(cx); 124 JSPrincipals* principals = cx->realm()->principals(); 125 CHECK(JS::BuildStackString(cx, principals, savedFrame, &str, 0, 126 expectation.format)); 127 JSLinearString* lin = str->ensureLinear(cx); 128 CHECK(lin); 129 CHECK(js::StringEqualsAscii(lin, expectation.expected)); 130 } 131 return true; 132 }; 133 134 CHECK(CheckStacks()); 135 136 js::SetStackFormat(cx, js::StackFormat::V8); 137 expectations[0].expected = V8Stack; 138 139 CHECK(CheckStacks()); 140 141 return true; 142 } 143 END_TEST(testSavedStacks_RangeBasedForLoops) 144 145 BEGIN_TEST(testSavedStacks_ErrorStackSpiderMonkey) { 146 JS::RootedValue val(cx); 147 CHECK( 148 evaluate("(function one() { \n" // 1 149 " return (function two() { \n" // 2 150 " return (function three() { \n" // 3 151 " return new Error('foo'); \n" // 4 152 " }()); \n" // 5 153 " }()); \n" // 6 154 "}()).stack \n", // 7 155 "filename.js", 1, &val)); 156 157 CHECK(val.isString()); 158 JS::RootedString stack(cx, val.toString()); 159 160 // Stack string 161 static const char SpiderMonkeyStack[] = 162 "three@filename.js:4:14\n" 163 "two@filename.js:5:6\n" 164 "one@filename.js:6:4\n" 165 "@filename.js:7:2\n"; 166 JSLinearString* lin = stack->ensureLinear(cx); 167 CHECK(lin); 168 CHECK(js::StringEqualsLiteral(lin, SpiderMonkeyStack)); 169 170 return true; 171 } 172 END_TEST(testSavedStacks_ErrorStackSpiderMonkey) 173 174 BEGIN_TEST(testSavedStacks_ErrorStackV8) { 175 js::SetStackFormat(cx, js::StackFormat::V8); 176 177 JS::RootedValue val(cx); 178 CHECK( 179 evaluate("(function one() { \n" // 1 180 " return (function two() { \n" // 2 181 " return (function three() { \n" // 3 182 " return new Error('foo'); \n" // 4 183 " }()); \n" // 5 184 " }()); \n" // 6 185 "}()).stack \n", // 7 186 "filename.js", 1, &val)); 187 188 CHECK(val.isString()); 189 JS::RootedString stack(cx, val.toString()); 190 191 // Stack string 192 static const char V8Stack[] = 193 "Error: foo\n" 194 " at three (filename.js:4:14)\n" 195 " at two (filename.js:5:6)\n" 196 " at one (filename.js:6:4)\n" 197 " at filename.js:7:2"; 198 JSLinearString* lin = stack->ensureLinear(cx); 199 CHECK(lin); 200 CHECK(js::StringEqualsLiteral(lin, V8Stack)); 201 202 return true; 203 } 204 END_TEST(testSavedStacks_ErrorStackV8) 205 206 BEGIN_TEST(testSavedStacks_selfHostedFrames) { 207 CHECK(js::DefineTestingFunctions(cx, global, false, false)); 208 209 JS::RootedValue val(cx); 210 // 0 1 2 3 211 // 0123456789012345678901234567890123456789 212 CHECK( 213 evaluate("(function one() { \n" // 1 214 " try { \n" // 2 215 " [1].map(function two() { \n" // 3 216 " throw saveStack(); \n" // 4 217 " }); \n" // 5 218 " } catch (stack) { \n" // 6 219 " return stack; \n" // 7 220 " } \n" // 8 221 "}()) \n", // 9 222 "filename.js", 1, &val)); 223 224 CHECK(val.isObject()); 225 JS::RootedObject obj(cx, &val.toObject()); 226 227 CHECK(obj->is<js::SavedFrame>()); 228 JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>()); 229 230 JS::Rooted<js::SavedFrame*> selfHostedFrame(cx, savedFrame->getParent()); 231 CHECK(selfHostedFrame->isSelfHosted(cx)); 232 233 JSPrincipals* principals = cx->realm()->principals(); 234 235 // Source 236 JS::RootedString str(cx); 237 JS::SavedFrameResult result = JS::GetSavedFrameSource( 238 cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude); 239 CHECK(result == JS::SavedFrameResult::Ok); 240 JSLinearString* lin = str->ensureLinear(cx); 241 CHECK(lin); 242 CHECK(js::StringEqualsLiteral(lin, "filename.js")); 243 244 // Source, including self-hosted frames 245 result = JS::GetSavedFrameSource(cx, principals, selfHostedFrame, &str, 246 JS::SavedFrameSelfHosted::Include); 247 CHECK(result == JS::SavedFrameResult::Ok); 248 lin = str->ensureLinear(cx); 249 CHECK(lin); 250 CHECK(js::StringEqualsLiteral(lin, "self-hosted")); 251 252 // Line 253 uint32_t line = 123; 254 result = JS::GetSavedFrameLine(cx, principals, selfHostedFrame, &line, 255 JS::SavedFrameSelfHosted::Exclude); 256 CHECK(result == JS::SavedFrameResult::Ok); 257 CHECK_EQUAL(line, 3U); 258 259 // Column 260 JS::TaggedColumnNumberOneOrigin column(JS::LimitedColumnNumberOneOrigin(123)); 261 result = JS::GetSavedFrameColumn(cx, principals, selfHostedFrame, &column, 262 JS::SavedFrameSelfHosted::Exclude); 263 CHECK(result == JS::SavedFrameResult::Ok); 264 CHECK_EQUAL(column.oneOriginValue(), 9U); 265 266 // Function display name 267 result = JS::GetSavedFrameFunctionDisplayName( 268 cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude); 269 CHECK(result == JS::SavedFrameResult::Ok); 270 lin = str->ensureLinear(cx); 271 CHECK(lin); 272 CHECK(js::StringEqualsLiteral(lin, "one")); 273 274 // Parent 275 JS::RootedObject parent(cx); 276 result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent, 277 JS::SavedFrameSelfHosted::Exclude); 278 CHECK(result == JS::SavedFrameResult::Ok); 279 // JS::GetSavedFrameParent does this super funky and potentially unexpected 280 // thing where it doesn't return the next subsumed parent but any next 281 // parent. This so that callers can still get the "asyncParent" property 282 // which is only on the first frame of the async parent stack and that frame 283 // might not be subsumed by the caller. It is expected that callers will 284 // still interact with the frame through the JSAPI accessors, so this should 285 // be safe and should not leak privileged info to unprivileged 286 // callers. However, because of that, we don't test that the parent we get 287 // here is the selfHostedFrame's parent (because, as just explained, it 288 // isn't) and instead check that asking for the source property gives us the 289 // expected value. 290 result = JS::GetSavedFrameSource(cx, principals, parent, &str, 291 JS::SavedFrameSelfHosted::Exclude); 292 CHECK(result == JS::SavedFrameResult::Ok); 293 lin = str->ensureLinear(cx); 294 CHECK(lin); 295 CHECK(js::StringEqualsLiteral(lin, "filename.js")); 296 297 return true; 298 } 299 END_TEST(testSavedStacks_selfHostedFrames) 300 301 BEGIN_TEST(test_GetPendingExceptionStack) { 302 CHECK(js::DefineTestingFunctions(cx, global, false, false)); 303 304 JSPrincipals* principals = cx->realm()->principals(); 305 306 static const char sourceText[] = 307 // 1 2 3 308 // 123456789012345678901234567890123456789 309 "(function one() { \n" // 1 310 " (function two() { \n" // 2 311 " (function three() { \n" // 3 312 " throw 5; \n" // 4 313 " }()); \n" // 5 314 " }()); \n" // 6 315 "}()) \n"; // 7 316 317 JS::CompileOptions opts(cx); 318 opts.setFileAndLine("filename.js", 1U); 319 320 JS::SourceText<mozilla::Utf8Unit> srcBuf; 321 CHECK(srcBuf.init(cx, sourceText, js_strlen(sourceText), 322 JS::SourceOwnership::Borrowed)); 323 324 JS::RootedValue val(cx); 325 bool ok = JS::Evaluate(cx, opts, srcBuf, &val); 326 327 CHECK(!ok); 328 CHECK(JS_IsExceptionPending(cx)); 329 CHECK(val.isUndefined()); 330 331 JS::ExceptionStack exnStack(cx); 332 CHECK(JS::GetPendingExceptionStack(cx, &exnStack)); 333 CHECK(exnStack.stack()); 334 CHECK(exnStack.stack()->is<js::SavedFrame>()); 335 JS::Rooted<js::SavedFrame*> savedFrameStack( 336 cx, &exnStack.stack()->as<js::SavedFrame>()); 337 338 CHECK(exnStack.exception().isInt32()); 339 CHECK(exnStack.exception().toInt32() == 5); 340 341 struct { 342 uint32_t line; 343 uint32_t column; 344 const char* source; 345 const char* functionDisplayName; 346 } expected[] = {{4, 7, "filename.js", "three"}, 347 {5, 6, "filename.js", "two"}, 348 {6, 4, "filename.js", "one"}, 349 {7, 2, "filename.js", nullptr}}; 350 351 size_t i = 0; 352 for (JS::Handle<js::SavedFrame*> frame : 353 js::SavedFrame::RootedRange(cx, savedFrameStack)) { 354 CHECK(i < 4); 355 356 // Line 357 uint32_t line = 123; 358 JS::SavedFrameResult result = JS::GetSavedFrameLine( 359 cx, principals, frame, &line, JS::SavedFrameSelfHosted::Exclude); 360 CHECK(result == JS::SavedFrameResult::Ok); 361 CHECK_EQUAL(line, expected[i].line); 362 363 // Column 364 JS::TaggedColumnNumberOneOrigin column( 365 JS::LimitedColumnNumberOneOrigin(123)); 366 result = JS::GetSavedFrameColumn(cx, principals, frame, &column, 367 JS::SavedFrameSelfHosted::Exclude); 368 CHECK(result == JS::SavedFrameResult::Ok); 369 CHECK_EQUAL(column.oneOriginValue(), expected[i].column); 370 371 // Source 372 JS::RootedString str(cx); 373 result = JS::GetSavedFrameSource(cx, principals, frame, &str, 374 JS::SavedFrameSelfHosted::Exclude); 375 CHECK(result == JS::SavedFrameResult::Ok); 376 JSLinearString* linear = str->ensureLinear(cx); 377 CHECK(linear); 378 CHECK(js::StringEqualsAscii(linear, expected[i].source)); 379 380 // Function display name 381 result = JS::GetSavedFrameFunctionDisplayName( 382 cx, principals, frame, &str, JS::SavedFrameSelfHosted::Exclude); 383 CHECK(result == JS::SavedFrameResult::Ok); 384 if (auto expectedName = expected[i].functionDisplayName) { 385 CHECK(str); 386 linear = str->ensureLinear(cx); 387 CHECK(linear); 388 CHECK(js::StringEqualsAscii(linear, expectedName)); 389 } else { 390 CHECK(!str); 391 } 392 393 i++; 394 } 395 396 return true; 397 } 398 END_TEST(test_GetPendingExceptionStack)