FrontendContext.cpp (10660B)
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 "frontend/FrontendContext.h" 8 9 #ifdef _WIN32 10 # include "util/WindowsWrapper.h" 11 # include <process.h> // GetCurrentThreadId 12 #else 13 # include <pthread.h> // pthread_self 14 #endif 15 16 #include "gc/GC.h" 17 #include "js/AllocPolicy.h" // js::ReportOutOfMemory 18 #include "js/experimental/CompileScript.h" 19 #include "js/friend/StackLimits.h" // js::ReportOverRecursed, js::MinimumStackLimitMargin, js::StackLimitMargin 20 #include "js/Modules.h" 21 #include "util/DifferentialTesting.h" 22 #include "util/NativeStack.h" // GetNativeStackBase 23 #include "vm/JSContext.h" 24 25 using namespace js; 26 27 void FrontendErrors::clearErrors() { 28 error.reset(); 29 warnings.clear(); 30 overRecursed = false; 31 outOfMemory = false; 32 allocationOverflow = false; 33 } 34 35 void FrontendErrors::clearWarnings() { warnings.clear(); } 36 37 void FrontendAllocator::reportAllocationOverflow() { 38 fc_->onAllocationOverflow(); 39 } 40 41 void* FrontendAllocator::onOutOfMemory(AllocFunction allocFunc, 42 arena_id_t arena, size_t nbytes, 43 void* reallocPtr) { 44 return fc_->onOutOfMemory(allocFunc, arena, nbytes, reallocPtr); 45 } 46 47 FrontendContext::~FrontendContext() { 48 if (ownNameCollectionPool_) { 49 MOZ_ASSERT(nameCollectionPool_); 50 js_delete(nameCollectionPool_); 51 } 52 } 53 54 void FrontendContext::setStackQuota(JS::NativeStackSize stackSize) { 55 #ifdef __wasi__ 56 stackLimit_ = JS::WASINativeStackLimit; 57 #else // __wasi__ 58 if (stackSize == 0) { 59 stackLimit_ = JS::NativeStackLimitMax; 60 } else { 61 stackLimit_ = JS::GetNativeStackLimit(GetNativeStackBase(), stackSize - 1); 62 } 63 #endif // !__wasi__ 64 65 #ifdef DEBUG 66 setNativeStackLimitThread(); 67 #endif 68 } 69 70 JS_PUBLIC_API void JS::SetNativeStackQuota(JS::FrontendContext* fc, 71 JS::NativeStackSize stackSize) { 72 fc->setStackQuota(stackSize); 73 } 74 75 JS_PUBLIC_API JS::NativeStackSize JS::ThreadStackQuotaForSize( 76 size_t stackSize) { 77 // Set the stack quota to 10% less that the actual size. 78 static constexpr double RatioWithoutMargin = 0.9; 79 80 MOZ_ASSERT(double(stackSize) * (1 - RatioWithoutMargin) > 81 js::MinimumStackLimitMargin); 82 83 return JS::NativeStackSize(double(stackSize) * RatioWithoutMargin); 84 } 85 86 bool FrontendContext::allocateOwnedPool() { 87 MOZ_ASSERT(!nameCollectionPool_); 88 89 nameCollectionPool_ = js_new<frontend::NameCollectionPool>(); 90 if (!nameCollectionPool_) { 91 return false; 92 } 93 ownNameCollectionPool_ = true; 94 return true; 95 } 96 97 bool FrontendContext::hadErrors() const { 98 // All errors must be reported to FrontendContext. 99 MOZ_ASSERT_IF(maybeCx_, !maybeCx_->isExceptionPending()); 100 101 return errors_.hadErrors(); 102 } 103 104 JS_PUBLIC_API bool JS::HadFrontendErrors(JS::FrontendContext* fc) { 105 return fc->hadErrors(); 106 } 107 108 void FrontendContext::clearErrors() { 109 MOZ_ASSERT(!maybeCx_); 110 return errors_.clearErrors(); 111 } 112 113 JS_PUBLIC_API void JS::ClearFrontendErrors(JS::FrontendContext* fc) { 114 fc->clearErrors(); 115 } 116 117 void FrontendContext::clearWarnings() { return errors_.clearWarnings(); } 118 119 void* FrontendContext::onOutOfMemory(AllocFunction allocFunc, arena_id_t arena, 120 size_t nbytes, void* reallocPtr) { 121 addPendingOutOfMemory(); 122 return nullptr; 123 } 124 125 void FrontendContext::onAllocationOverflow() { 126 errors_.allocationOverflow = true; 127 } 128 129 void FrontendContext::onOutOfMemory() { addPendingOutOfMemory(); } 130 131 void FrontendContext::onOverRecursed() { errors_.overRecursed = true; } 132 133 void FrontendContext::recoverFromOutOfMemory() { 134 MOZ_ASSERT_IF(maybeCx_, !maybeCx_->isThrowingOutOfMemory()); 135 136 errors_.outOfMemory = false; 137 } 138 139 const JSErrorFormatString* FrontendContext::gcSafeCallback( 140 JSErrorCallback callback, void* userRef, const unsigned errorNumber) { 141 mozilla::Maybe<gc::AutoSuppressGC> suppressGC; 142 if (maybeCx_) { 143 suppressGC.emplace(maybeCx_); 144 } 145 146 return callback(userRef, errorNumber); 147 } 148 149 void FrontendContext::reportError(CompileError&& err) { 150 if (errors_.error) { 151 errors_.error.reset(); 152 } 153 154 // When compiling off thread, save the error so that the thread finishing the 155 // parse can report it later. 156 errors_.error.emplace(std::move(err)); 157 } 158 159 bool FrontendContext::reportWarning(CompileError&& err) { 160 if (!errors_.warnings.append(std::move(err))) { 161 ReportOutOfMemory(); 162 return false; 163 } 164 165 return true; 166 } 167 168 void FrontendContext::ReportOutOfMemory() { 169 /* 170 * OOMs are non-deterministic, especially across different execution modes 171 * (e.g. interpreter vs JIT). When doing differential testing, print to 172 * stderr so that the fuzzers can detect this. 173 */ 174 if (SupportDifferentialTesting()) { 175 fprintf(stderr, "ReportOutOfMemory called\n"); 176 } 177 178 addPendingOutOfMemory(); 179 } 180 181 void FrontendContext::addPendingOutOfMemory() { errors_.outOfMemory = true; } 182 183 void FrontendContext::setCurrentJSContext(JSContext* cx) { 184 MOZ_ASSERT(!nameCollectionPool_); 185 186 maybeCx_ = cx; 187 nameCollectionPool_ = &cx->frontendCollectionPool(); 188 scriptDataTableHolder_ = &cx->runtime()->scriptDataTableHolder(); 189 stackLimit_ = cx->stackLimitForCurrentPrincipal(); 190 191 #ifdef DEBUG 192 setNativeStackLimitThread(); 193 #endif 194 } 195 196 bool FrontendContext::convertToRuntimeError( 197 JSContext* cx, Warning warning /* = Warning::Report */) { 198 // Report out of memory errors eagerly, or errors could be malformed. 199 if (hadOutOfMemory()) { 200 js::ReportOutOfMemory(cx); 201 return false; 202 } 203 204 if (maybeError()) { 205 if (!maybeError()->throwError(cx)) { 206 return false; 207 } 208 } 209 if (warning == Warning::Report) { 210 for (CompileError& error : warnings()) { 211 #ifdef DEBUG 212 bool hadException = cx->isExceptionPending(); 213 #endif 214 if (!error.throwError(cx)) { 215 return false; 216 } 217 218 // The warning reporter shouldn't clear the exception set by 219 // other errors. 220 MOZ_ASSERT_IF(hadException, cx->isExceptionPending()); 221 } 222 } 223 if (hadOverRecursed()) { 224 js::ReportOverRecursed(cx); 225 } 226 if (hadAllocationOverflow()) { 227 js::ReportAllocationOverflow(cx); 228 } 229 230 MOZ_ASSERT(!extraBindingsAreNotUsed(), 231 "extraBindingsAreNotUsed shouldn't escape from FrontendContext"); 232 return true; 233 } 234 235 JS_PUBLIC_API bool JS::ConvertFrontendErrorsToRuntimeErrors( 236 JSContext* cx, JS::FrontendContext* fc, 237 const JS::ReadOnlyCompileOptions& options) { 238 return fc->convertToRuntimeError(cx); 239 } 240 241 #ifdef DEBUG 242 static size_t GetTid() { 243 # if defined(_WIN32) 244 return size_t(GetCurrentThreadId()); 245 # elif defined(__wasm__) 246 return 1; 247 # else 248 return size_t(pthread_self()); 249 # endif 250 } 251 252 void FrontendContext::setNativeStackLimitThread() { 253 stackLimitThreadId_.emplace(GetTid()); 254 } 255 256 void FrontendContext::assertNativeStackLimitThread() { 257 if (!stackLimitThreadId_.isSome()) { 258 return; 259 } 260 261 MOZ_ASSERT(*stackLimitThreadId_ == GetTid()); 262 } 263 #endif 264 265 #ifdef __wasi__ 266 void FrontendContext::incWasiRecursionDepth() { 267 if (maybeCx_) { 268 IncWasiRecursionDepth(maybeCx_); 269 } 270 } 271 272 void FrontendContext::decWasiRecursionDepth() { 273 if (maybeCx_) { 274 DecWasiRecursionDepth(maybeCx_); 275 } 276 } 277 278 bool FrontendContext::checkWasiRecursionLimit() { 279 if (maybeCx_) { 280 return CheckWasiRecursionLimit(maybeCx_); 281 } 282 return true; 283 } 284 285 JS_PUBLIC_API void js::IncWasiRecursionDepth(FrontendContext* fc) { 286 fc->incWasiRecursionDepth(); 287 } 288 289 JS_PUBLIC_API void js::DecWasiRecursionDepth(FrontendContext* fc) { 290 fc->decWasiRecursionDepth(); 291 } 292 293 JS_PUBLIC_API bool js::CheckWasiRecursionLimit(FrontendContext* fc) { 294 return fc->checkWasiRecursionLimit(); 295 } 296 #endif // __wasi__ 297 298 FrontendContext* js::NewFrontendContext() { 299 UniquePtr<FrontendContext> fc = MakeUnique<FrontendContext>(); 300 if (!fc) { 301 return nullptr; 302 } 303 304 if (!fc->allocateOwnedPool()) { 305 return nullptr; 306 } 307 308 return fc.release(); 309 } 310 311 JS_PUBLIC_API FrontendContext* JS::NewFrontendContext() { 312 MOZ_ASSERT(JS::detail::libraryInitState == JS::detail::InitState::Running, 313 "must call JS_Init prior to creating any FrontendContexts"); 314 315 return js::NewFrontendContext(); 316 } 317 318 void js::DestroyFrontendContext(FrontendContext* fc) { js_delete_poison(fc); } 319 320 JS_PUBLIC_API void JS::DestroyFrontendContext(FrontendContext* fc) { 321 return js::DestroyFrontendContext(fc); 322 } 323 324 #ifdef DEBUG 325 void FrontendContext::checkAndUpdateFrontendContextRecursionLimit(void* sp) { 326 // For the js::MinimumStackLimitMargin to be effective, it should be larger 327 // than the largest stack space which might be consumed by successive calls 328 // to AutoCheckRecursionLimit::check. 329 // 330 // This function asserts that this property holds by recalling the stack 331 // pointer of the previous call and comparing the consumed stack size with 332 // the minimum margin. 333 // 334 // If this property does not hold, either the stack limit should be increased 335 // or more calls to check for recursion should be added. 336 if (previousStackPointer_ != nullptr) { 337 # if JS_STACK_GROWTH_DIRECTION > 0 338 if (sp > previousStackPointer_) { 339 size_t diff = uintptr_t(sp) - uintptr_t(previousStackPointer_); 340 MOZ_ASSERT(diff < js::MinimumStackLimitMargin); 341 } 342 # else 343 if (sp < previousStackPointer_) { 344 size_t diff = uintptr_t(previousStackPointer_) - uintptr_t(sp); 345 MOZ_ASSERT(diff < js::MinimumStackLimitMargin); 346 } 347 # endif 348 } 349 previousStackPointer_ = sp; 350 } 351 352 void js::CheckAndUpdateFrontendContextRecursionLimit(FrontendContext* fc, 353 void* sp) { 354 fc->checkAndUpdateFrontendContextRecursionLimit(sp); 355 } 356 #endif 357 358 JS_PUBLIC_API const JSErrorReport* JS::GetFrontendErrorReport( 359 JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options) { 360 if (!fc->maybeError().isSome()) { 361 return nullptr; 362 } 363 return fc->maybeError().ptr(); 364 } 365 366 JS_PUBLIC_API bool JS::HadFrontendOverRecursed(JS::FrontendContext* fc) { 367 return fc->hadOverRecursed(); 368 } 369 370 JS_PUBLIC_API bool JS::HadFrontendOutOfMemory(JS::FrontendContext* fc) { 371 return fc->hadOutOfMemory(); 372 } 373 374 JS_PUBLIC_API bool JS::HadFrontendAllocationOverflow(JS::FrontendContext* fc) { 375 return fc->hadAllocationOverflow(); 376 } 377 378 JS_PUBLIC_API size_t JS::GetFrontendWarningCount(JS::FrontendContext* fc) { 379 return fc->warnings().length(); 380 } 381 382 JS_PUBLIC_API const JSErrorReport* JS::GetFrontendWarningAt( 383 JS::FrontendContext* fc, size_t index, 384 const JS::ReadOnlyCompileOptions& options) { 385 return &fc->warnings()[index]; 386 }