StackLimits.h (10906B)
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 js_friend_StackLimits_h 8 #define js_friend_StackLimits_h 9 10 #include "mozilla/Attributes.h" // MOZ_ALWAYS_INLINE, MOZ_COLD 11 #include "mozilla/Likely.h" // MOZ_LIKELY 12 #include "mozilla/Variant.h" // mozilla::Variant, mozilla::AsVariant 13 14 #include <stddef.h> // size_t 15 16 #include "jstypes.h" // JS_PUBLIC_API 17 18 #include "js/HeapAPI.h" // JS::StackKind, JS::StackForTrustedScript, JS::StackForUntrustedScript 19 #include "js/RootingAPI.h" // JS::RootingContext 20 #include "js/Stack.h" // JS::NativeStackLimit 21 #include "js/Utility.h" // JS_STACK_OOM_POSSIBLY_FAIL 22 23 struct JS_PUBLIC_API JSContext; 24 25 #ifndef JS_STACK_GROWTH_DIRECTION 26 # ifdef __hppa 27 # define JS_STACK_GROWTH_DIRECTION (1) 28 # else 29 # define JS_STACK_GROWTH_DIRECTION (-1) 30 # endif 31 #endif 32 33 namespace js { 34 35 class FrontendContext; 36 37 #ifdef __wasi__ 38 extern MOZ_COLD JS_PUBLIC_API void IncWasiRecursionDepth(JSContext* cx); 39 extern MOZ_COLD JS_PUBLIC_API void DecWasiRecursionDepth(JSContext* cx); 40 extern MOZ_COLD JS_PUBLIC_API bool CheckWasiRecursionLimit(JSContext* cx); 41 42 extern MOZ_COLD JS_PUBLIC_API void IncWasiRecursionDepth(FrontendContext* fc); 43 extern MOZ_COLD JS_PUBLIC_API void DecWasiRecursionDepth(FrontendContext* fc); 44 extern MOZ_COLD JS_PUBLIC_API bool CheckWasiRecursionLimit(FrontendContext* fc); 45 #endif // __wasi__ 46 47 // The minimum margin for stack limit to ensure that the periodic 48 // AutoCheckRecursionLimit operation is sufficient. 49 // 50 // See FrontendContext::checkAndUpdateFrontendContextRecursionLimit and 51 // JS::ThreadStackQuotaForSize. 52 static constexpr size_t MinimumStackLimitMargin = 32 * 1024; 53 54 // AutoCheckRecursionLimit can be used to check whether we're close to using up 55 // the C++ stack. 56 // 57 // Typical usage is like this: 58 // 59 // AutoCheckRecursionLimit recursion(cx); 60 // if (!recursion.check(cx)) { 61 // return false; 62 // } 63 // 64 // The check* functions return |false| if we are close to the stack limit. 65 // They also report an overrecursion error, except for the DontReport variants. 66 // 67 // The checkSystem variant gives us a little extra space so we can ensure that 68 // crucial code is able to run. 69 // 70 // checkConservative allows less space than any other check, including a safety 71 // buffer (as in, it uses the untrusted limit and subtracts a little more from 72 // it). 73 class MOZ_RAII AutoCheckRecursionLimit { 74 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkLimitImpl( 75 JS::NativeStackLimit limit, void* sp) const; 76 77 MOZ_ALWAYS_INLINE JS::NativeStackLimit getStackLimitSlow(JSContext* cx) const; 78 MOZ_ALWAYS_INLINE JS::NativeStackLimit getStackLimitHelper( 79 JSContext* cx, JS::StackKind kind, int extraAllowance) const; 80 81 JS::NativeStackLimit getStackLimit(FrontendContext* fc) const; 82 83 JS_PUBLIC_API JS::StackKind stackKindForCurrentPrincipal(JSContext* cx) const; 84 85 #ifdef __wasi__ 86 // The JSContext outlives AutoCheckRecursionLimit so it is safe to use raw 87 // pointer here. 88 mozilla::Variant<JSContext*, FrontendContext*> context_; 89 #endif // __wasi__ 90 91 public: 92 explicit MOZ_ALWAYS_INLINE AutoCheckRecursionLimit(JSContext* cx) 93 #ifdef __wasi__ 94 : context_(mozilla::AsVariant(cx)) 95 #endif // __wasi__ 96 { 97 #ifdef __wasi__ 98 incWasiRecursionDepth(); 99 #endif // __wasi__ 100 } 101 102 explicit MOZ_ALWAYS_INLINE AutoCheckRecursionLimit(FrontendContext* fc) 103 #ifdef __wasi__ 104 : context_(mozilla::AsVariant(fc)) 105 #endif // __wasi__ 106 { 107 #ifdef __wasi__ 108 incWasiRecursionDepth(); 109 #endif // __wasi__ 110 } 111 112 MOZ_ALWAYS_INLINE ~AutoCheckRecursionLimit() { 113 #ifdef __wasi__ 114 decWasiRecursionDepth(); 115 #endif // __wasi__ 116 } 117 118 #ifdef __wasi__ 119 MOZ_ALWAYS_INLINE void incWasiRecursionDepth() { 120 if (context_.is<JSContext*>()) { 121 JSContext* cx = context_.as<JSContext*>(); 122 IncWasiRecursionDepth(cx); 123 } else { 124 FrontendContext* fc = context_.as<FrontendContext*>(); 125 IncWasiRecursionDepth(fc); 126 } 127 } 128 129 MOZ_ALWAYS_INLINE void decWasiRecursionDepth() { 130 if (context_.is<JSContext*>()) { 131 JSContext* cx = context_.as<JSContext*>(); 132 DecWasiRecursionDepth(cx); 133 } else { 134 FrontendContext* fc = context_.as<FrontendContext*>(); 135 DecWasiRecursionDepth(fc); 136 } 137 } 138 139 MOZ_ALWAYS_INLINE bool checkWasiRecursionLimit() const { 140 if (context_.is<JSContext*>()) { 141 JSContext* cx = context_.as<JSContext*>(); 142 if (!CheckWasiRecursionLimit(cx)) { 143 return false; 144 } 145 } else { 146 FrontendContext* fc = context_.as<FrontendContext*>(); 147 if (!CheckWasiRecursionLimit(fc)) { 148 return false; 149 } 150 } 151 152 return true; 153 } 154 #endif // __wasi__ 155 156 AutoCheckRecursionLimit(const AutoCheckRecursionLimit&) = delete; 157 void operator=(const AutoCheckRecursionLimit&) = delete; 158 159 [[nodiscard]] MOZ_ALWAYS_INLINE bool check(JSContext* cx) const; 160 [[nodiscard]] MOZ_ALWAYS_INLINE bool check(FrontendContext* fc) const; 161 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkDontReport(JSContext* cx) const; 162 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkDontReport( 163 FrontendContext* fc) const; 164 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithExtra(JSContext* cx, 165 size_t extra) const; 166 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithExtraDontReport( 167 JSContext* cx, size_t extra) const; 168 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithStackPointerDontReport( 169 JSContext* cx, void* sp) const; 170 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithStackPointerDontReport( 171 FrontendContext* fc, void* sp) const; 172 173 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservative(JSContext* cx) const; 174 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservativeDontReport( 175 JSContext* cx) const; 176 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservativeDontReport( 177 JS::NativeStackLimit limit) const; 178 179 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkSystem(JSContext* cx) const; 180 [[nodiscard]] MOZ_ALWAYS_INLINE bool checkSystemDontReport( 181 JSContext* cx) const; 182 }; 183 184 extern MOZ_COLD JS_PUBLIC_API void ReportOverRecursed(JSContext* maybecx); 185 extern MOZ_COLD JS_PUBLIC_API void ReportOverRecursed(FrontendContext* fc); 186 187 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkLimitImpl( 188 JS::NativeStackLimit limit, void* sp) const { 189 JS_STACK_OOM_POSSIBLY_FAIL(); 190 191 #ifdef __wasi__ 192 if (!checkWasiRecursionLimit()) { 193 return false; 194 } 195 #endif // __wasi__ 196 197 #if JS_STACK_GROWTH_DIRECTION > 0 198 return MOZ_LIKELY(JS::NativeStackLimit(sp) < limit); 199 #else 200 return MOZ_LIKELY(JS::NativeStackLimit(sp) > limit); 201 #endif 202 } 203 204 MOZ_ALWAYS_INLINE JS::NativeStackLimit 205 AutoCheckRecursionLimit::getStackLimitSlow(JSContext* cx) const { 206 JS::StackKind kind = stackKindForCurrentPrincipal(cx); 207 return getStackLimitHelper(cx, kind, 0); 208 } 209 210 MOZ_ALWAYS_INLINE JS::NativeStackLimit 211 AutoCheckRecursionLimit::getStackLimitHelper(JSContext* cx, JS::StackKind kind, 212 int extraAllowance) const { 213 JS::NativeStackLimit limit = 214 JS::RootingContext::get(cx)->nativeStackLimit[kind]; 215 #if JS_STACK_GROWTH_DIRECTION > 0 216 limit += extraAllowance; 217 #else 218 limit -= extraAllowance; 219 #endif 220 return limit; 221 } 222 223 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::check(JSContext* cx) const { 224 if (MOZ_UNLIKELY(!checkDontReport(cx))) { 225 ReportOverRecursed(cx); 226 return false; 227 } 228 return true; 229 } 230 231 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::check( 232 FrontendContext* fc) const { 233 if (MOZ_UNLIKELY(!checkDontReport(fc))) { 234 ReportOverRecursed(fc); 235 return false; 236 } 237 return true; 238 } 239 240 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkDontReport( 241 JSContext* cx) const { 242 return checkWithStackPointerDontReport(cx, __builtin_frame_address(0)); 243 } 244 245 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkDontReport( 246 FrontendContext* fc) const { 247 return checkWithStackPointerDontReport(fc, __builtin_frame_address(0)); 248 } 249 250 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithStackPointerDontReport( 251 JSContext* cx, void* sp) const { 252 // getStackLimitSlow(cx) is pretty slow because it has to do an uninlined 253 // call to stackKindForCurrentPrincipal to determine which stack limit to 254 // use. To work around this, check the untrusted limit first to avoid the 255 // overhead in most cases. 256 JS::NativeStackLimit untrustedLimit = 257 getStackLimitHelper(cx, JS::StackForUntrustedScript, 0); 258 if (MOZ_LIKELY(checkLimitImpl(untrustedLimit, sp))) { 259 return true; 260 } 261 return checkLimitImpl(getStackLimitSlow(cx), sp); 262 } 263 264 #ifdef DEBUG 265 extern void CheckAndUpdateFrontendContextRecursionLimit(FrontendContext* fc, 266 void* sp); 267 #endif 268 269 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithStackPointerDontReport( 270 FrontendContext* fc, void* sp) const { 271 #ifdef DEBUG 272 CheckAndUpdateFrontendContextRecursionLimit(fc, sp); 273 #endif 274 return checkLimitImpl(getStackLimit(fc), sp); 275 } 276 277 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithExtra( 278 JSContext* cx, size_t extra) const { 279 if (MOZ_UNLIKELY(!checkWithExtraDontReport(cx, extra))) { 280 ReportOverRecursed(cx); 281 return false; 282 } 283 return true; 284 } 285 286 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithExtraDontReport( 287 JSContext* cx, size_t extra) const { 288 char* sp = reinterpret_cast<char*>(__builtin_frame_address(0)); 289 #if JS_STACK_GROWTH_DIRECTION > 0 290 sp += extra; 291 #else 292 sp -= extra; 293 #endif 294 return checkWithStackPointerDontReport(cx, sp); 295 } 296 297 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkSystem( 298 JSContext* cx) const { 299 if (MOZ_UNLIKELY(!checkSystemDontReport(cx))) { 300 ReportOverRecursed(cx); 301 return false; 302 } 303 return true; 304 } 305 306 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkSystemDontReport( 307 JSContext* cx) const { 308 JS::NativeStackLimit limit = 309 getStackLimitHelper(cx, JS::StackForSystemCode, 0); 310 return checkLimitImpl(limit, __builtin_frame_address(0)); 311 } 312 313 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservative( 314 JSContext* cx) const { 315 if (MOZ_UNLIKELY(!checkConservativeDontReport(cx))) { 316 ReportOverRecursed(cx); 317 return false; 318 } 319 return true; 320 } 321 322 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservativeDontReport( 323 JSContext* cx) const { 324 JS::NativeStackLimit limit = getStackLimitHelper( 325 cx, JS::StackForUntrustedScript, -4096 * int(sizeof(size_t))); 326 return checkLimitImpl(limit, __builtin_frame_address(0)); 327 } 328 329 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservativeDontReport( 330 JS::NativeStackLimit limit) const { 331 return checkLimitImpl(limit, __builtin_frame_address(0)); 332 } 333 334 } // namespace js 335 336 #endif // js_friend_StackLimits_h