FrontendContext.h (9122B)
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 #ifndef frontend_FrontendContext_h 8 #define frontend_FrontendContext_h 9 10 #include "mozilla/Assertions.h" // MOZ_ASSERT 11 #include "mozilla/Attributes.h" // MOZ_STACK_CLASS 12 #include "mozilla/Maybe.h" // mozilla::Maybe 13 14 #include <stddef.h> // size_t 15 16 #include "js/AllocPolicy.h" // SystemAllocPolicy, AllocFunction 17 #include "js/ErrorReport.h" // JSErrorCallback, JSErrorFormatString 18 #include "js/Stack.h" // JS::NativeStackSize, JS::NativeStackLimit, JS::NativeStackLimitMax 19 #include "js/Vector.h" // Vector 20 #include "vm/ErrorReporting.h" // CompileError 21 #include "vm/MallocProvider.h" // MallocProvider 22 #include "vm/SharedScriptDataTableHolder.h" // js::SharedScriptDataTableHolder, js::globalSharedScriptDataTableHolder 23 24 struct JSContext; 25 26 namespace js { 27 28 class FrontendContext; 29 30 namespace frontend { 31 class NameCollectionPool; 32 } // namespace frontend 33 34 struct FrontendErrors { 35 FrontendErrors() = default; 36 // Any errors or warnings produced during compilation. These are reported 37 // when finishing the script. 38 mozilla::Maybe<CompileError> error; 39 Vector<CompileError, 0, SystemAllocPolicy> warnings; 40 bool overRecursed = false; 41 bool outOfMemory = false; 42 bool allocationOverflow = false; 43 44 // Set to true if the compilation is initiated with extra bindings, but 45 // the script has no reference to the bindings, and the script should be 46 // compiled without the extra bindings. 47 // 48 // See frontend::CompileGlobalScriptWithExtraBindings. 49 bool extraBindingsAreNotUsed = false; 50 51 bool hadErrors() const { 52 return outOfMemory || overRecursed || allocationOverflow || 53 extraBindingsAreNotUsed || error; 54 } 55 56 void clearErrors(); 57 void clearWarnings(); 58 }; 59 60 class FrontendAllocator : public MallocProvider<FrontendAllocator> { 61 private: 62 FrontendContext* const fc_; 63 64 public: 65 explicit FrontendAllocator(FrontendContext* fc) : fc_(fc) {} 66 67 void* onOutOfMemory(js::AllocFunction allocFunc, arena_id_t arena, 68 size_t nbytes, void* reallocPtr = nullptr); 69 void reportAllocationOverflow(); 70 }; 71 72 class FrontendContext { 73 private: 74 FrontendAllocator alloc_; 75 js::FrontendErrors errors_; 76 77 // NameCollectionPool can be either: 78 // * owned by this FrontendContext, or 79 // * borrowed from JSContext 80 frontend::NameCollectionPool* nameCollectionPool_; 81 bool ownNameCollectionPool_; 82 83 js::SharedScriptDataTableHolder* scriptDataTableHolder_; 84 85 // Limit pointer for checking native stack consumption. 86 // 87 // The pointer is calculated based on the stack base of the current thread 88 // except for JS::NativeStackLimitMax. Once such value is set, this 89 // FrontendContext can be used only in the thread. 90 // 91 // In order to enforce this thread rule, setNativeStackLimitThread should 92 // be called when setting the value, and assertNativeStackLimitThread should 93 // be called at each entry-point that might make use of this field. 94 JS::NativeStackLimit stackLimit_ = JS::NativeStackLimitMax; 95 96 #ifdef DEBUG 97 // The thread ID where the native stack limit is set. 98 mozilla::Maybe<size_t> stackLimitThreadId_; 99 100 // The stack pointer where the AutoCheckRecursionLimit check is performed 101 // last time. 102 void* previousStackPointer_ = nullptr; 103 #endif 104 105 protected: 106 // (optional) Current JSContext to support main-thread-specific 107 // handling for error reporting, GC, and memory allocation. 108 // 109 // Set by setCurrentJSContext. 110 JSContext* maybeCx_ = nullptr; 111 112 public: 113 FrontendContext() 114 : alloc_(this), 115 nameCollectionPool_(nullptr), 116 ownNameCollectionPool_(false), 117 scriptDataTableHolder_(&js::globalSharedScriptDataTableHolder) {} 118 ~FrontendContext(); 119 120 void setStackQuota(JS::NativeStackSize stackSize); 121 JS::NativeStackLimit stackLimit() const { return stackLimit_; } 122 123 bool allocateOwnedPool(); 124 125 frontend::NameCollectionPool& nameCollectionPool() { 126 MOZ_ASSERT( 127 nameCollectionPool_, 128 "Either allocateOwnedPool or setCurrentJSContext must be called"); 129 return *nameCollectionPool_; 130 } 131 132 js::SharedScriptDataTableHolder* scriptDataTableHolder() { 133 MOZ_ASSERT(scriptDataTableHolder_); 134 return scriptDataTableHolder_; 135 } 136 137 FrontendAllocator* getAllocator() { return &alloc_; } 138 139 // Use the given JSContext's for: 140 // * js::frontend::NameCollectionPool for reusing allocation 141 // * js::SharedScriptDataTableHolder for de-duplicating bytecode 142 // within given runtime 143 // * Copy the native stack limit from the JSContext 144 // 145 // And also this JSContext can be retrieved by maybeCurrentJSContext below. 146 void setCurrentJSContext(JSContext* cx); 147 148 // Returns JSContext if any. 149 // 150 // This can be used only for: 151 // * Main-thread-specific operation, such as operating on JSAtom 152 // * Optional operation, such as providing better error message 153 JSContext* maybeCurrentJSContext() { return maybeCx_; } 154 155 enum class Warning { Suppress, Report }; 156 157 // Returns false if the error cannot be converted (such as due to OOM). An 158 // error might still be reported to the given JSContext. Returns true 159 // otherwise. 160 bool convertToRuntimeError(JSContext* cx, Warning warning = Warning::Report); 161 162 mozilla::Maybe<CompileError>& maybeError() { return errors_.error; } 163 Vector<CompileError, 0, SystemAllocPolicy>& warnings() { 164 return errors_.warnings; 165 } 166 167 // Report CompileErrors 168 void reportError(js::CompileError&& err); 169 bool reportWarning(js::CompileError&& err); 170 171 // Report FrontendAllocator errors 172 void* onOutOfMemory(js::AllocFunction allocFunc, arena_id_t arena, 173 size_t nbytes, void* reallocPtr = nullptr); 174 void onAllocationOverflow(); 175 176 void onOutOfMemory(); 177 void onOverRecursed(); 178 179 void recoverFromOutOfMemory(); 180 181 const JSErrorFormatString* gcSafeCallback(JSErrorCallback callback, 182 void* userRef, 183 const unsigned errorNumber); 184 185 // Status of errors reported to this FrontendContext 186 bool hadOutOfMemory() const { return errors_.outOfMemory; } 187 bool hadOverRecursed() const { return errors_.overRecursed; } 188 bool hadAllocationOverflow() const { return errors_.allocationOverflow; } 189 bool extraBindingsAreNotUsed() const { 190 return errors_.extraBindingsAreNotUsed; 191 } 192 void reportExtraBindingsAreNotUsed() { 193 errors_.extraBindingsAreNotUsed = true; 194 } 195 void clearNoExtraBindingReferencesFound() { 196 errors_.extraBindingsAreNotUsed = false; 197 } 198 bool hadErrors() const; 199 // Clear errors and warnings. 200 void clearErrors(); 201 // Clear warnings only. 202 void clearWarnings(); 203 204 #ifdef __wasi__ 205 void incWasiRecursionDepth(); 206 void decWasiRecursionDepth(); 207 bool checkWasiRecursionLimit(); 208 #endif // __wasi__ 209 210 #ifdef DEBUG 211 void setNativeStackLimitThread(); 212 void assertNativeStackLimitThread(); 213 #endif 214 215 #ifdef DEBUG 216 void checkAndUpdateFrontendContextRecursionLimit(void* sp); 217 #endif 218 219 private: 220 void ReportOutOfMemory(); 221 void addPendingOutOfMemory(); 222 }; 223 224 // Automatically report any pending exception when leaving the scope. 225 class MOZ_STACK_CLASS AutoReportFrontendContext : public FrontendContext { 226 // The target JSContext to report the errors to. 227 JSContext* cx_; 228 229 Warning warning_; 230 231 public: 232 explicit AutoReportFrontendContext(JSContext* cx, 233 Warning warning = Warning::Report) 234 : cx_(cx), warning_(warning) { 235 setCurrentJSContext(cx_); 236 MOZ_ASSERT(cx_ == maybeCx_); 237 } 238 239 ~AutoReportFrontendContext() { 240 if (cx_) { 241 convertToRuntimeErrorAndClear(); 242 } 243 } 244 245 void clearAutoReport() { cx_ = nullptr; } 246 247 bool convertToRuntimeErrorAndClear() { 248 bool result = convertToRuntimeError(cx_, warning_); 249 cx_ = nullptr; 250 return result; 251 } 252 }; 253 254 /* 255 * Explicitly report any pending exception before leaving the scope. 256 * 257 * Before an instance of this class leaves the scope, you must call either 258 * failure() (if there are exceptions to report) or ok() (if there are no 259 * exceptions to report). 260 */ 261 class ManualReportFrontendContext : public FrontendContext { 262 JSContext* cx_; 263 #ifdef DEBUG 264 bool handled_ = false; 265 #endif 266 267 public: 268 explicit ManualReportFrontendContext(JSContext* cx) : cx_(cx) { 269 setCurrentJSContext(cx_); 270 } 271 272 ~ManualReportFrontendContext() { MOZ_ASSERT(handled_); } 273 274 void ok() { 275 #ifdef DEBUG 276 handled_ = true; 277 #endif 278 } 279 280 void failure() { 281 #ifdef DEBUG 282 handled_ = true; 283 #endif 284 convertToRuntimeError(cx_); 285 } 286 }; 287 288 // Create function for FrontendContext, which is manually allocated and 289 // exclusively owned. 290 extern FrontendContext* NewFrontendContext(); 291 292 // Destroy function for FrontendContext, which was allocated with 293 // NewFrontendContext. 294 extern void DestroyFrontendContext(FrontendContext* fc); 295 296 } // namespace js 297 298 #endif /* frontend_FrontendContext_h */