CallArgs.h (11873B)
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 /* 8 * [SMDOC] JS::CallArgs API 9 * 10 * Helper classes encapsulating access to the callee, |this| value, arguments, 11 * and argument count for a call/construct operation. 12 * 13 * JS::CallArgs encapsulates access to a JSNative's un-abstracted 14 * |unsigned argc, Value* vp| arguments. The principal way to create a 15 * JS::CallArgs is using JS::CallArgsFromVp: 16 * 17 * // If provided no arguments or a non-numeric first argument, return zero. 18 * // Otherwise return |this| exactly as given, without boxing. 19 * static bool 20 * Func(JSContext* cx, unsigned argc, JS::Value* vp) 21 * { 22 * JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 23 * 24 * // Guard against no arguments or a non-numeric arg0. 25 * if (args.length() == 0 || !args[0].isNumber()) { 26 * args.rval().setInt32(0); 27 * return true; 28 * } 29 * 30 * // Access to the callee must occur before accessing/setting 31 * // the return value. 32 * JSObject& callee = args.callee(); 33 * args.rval().setObject(callee); 34 * 35 * // callee() and calleev() will now assert. 36 * 37 * // It's always fine to access thisv(). 38 * HandleValue thisv = args.thisv(); 39 * args.rval().set(thisv); 40 * 41 * // As the return value was last set to |this|, returns |this|. 42 * return true; 43 * } 44 * 45 * CallArgs is exposed publicly and used internally. Not all parts of its 46 * public interface are meant to be used by embedders! See inline comments to 47 * for details. 48 * 49 * It's possible (albeit deprecated) to manually index into |vp| to access the 50 * callee, |this|, and arguments of a function, and to set its return value. 51 * This does not have the error-handling or moving-GC correctness of CallArgs. 52 * New code should use CallArgs instead whenever possible. 53 * 54 * The eventual plan is to change JSNative to take |const CallArgs&| directly, 55 * for automatic assertion of correct use and to make calling functions more 56 * efficient. Embedders should start internally switching away from using 57 * |argc| and |vp| directly, except to create a |CallArgs|. Then, when an 58 * eventual release making that change occurs, porting efforts will require 59 * changing methods' signatures but won't require invasive changes to the 60 * methods' implementations, potentially under time pressure. 61 */ 62 63 #ifndef js_CallArgs_h 64 #define js_CallArgs_h 65 66 #include "mozilla/Assertions.h" 67 #include "mozilla/Attributes.h" 68 69 #include <type_traits> 70 71 #include "jstypes.h" 72 73 #include "js/RootingAPI.h" 74 #include "js/Value.h" 75 76 /* Typedef for native functions called by the JS VM. */ 77 using JSNative = bool (*)(JSContext* cx, unsigned argc, JS::Value* vp); 78 79 namespace JS { 80 81 extern JS_PUBLIC_DATA const HandleValue UndefinedHandleValue; 82 83 namespace detail { 84 85 /* 86 * Compute |this| for the |vp| inside a JSNative, either boxing primitives or 87 * replacing with the global object as necessary. 88 */ 89 extern JS_PUBLIC_API bool ComputeThis(JSContext* cx, JS::Value* vp, 90 MutableHandleObject thisObject); 91 92 #ifdef JS_DEBUG 93 extern JS_PUBLIC_API void CheckIsValidConstructible(const Value& v); 94 #endif 95 96 class MOZ_STACK_CLASS IncludeUsedRval { 97 mutable bool usedRval_ = false; 98 99 public: 100 bool usedRval() const { return usedRval_; } 101 void setUsedRval() const { usedRval_ = true; } 102 void clearUsedRval() const { usedRval_ = false; } 103 void assertUnusedRval() const { MOZ_ASSERT(!usedRval_); } 104 }; 105 106 class MOZ_STACK_CLASS NoUsedRval { 107 public: 108 bool usedRval() const { return false; } 109 void setUsedRval() const {} 110 void clearUsedRval() const {} 111 void assertUnusedRval() const {} 112 }; 113 114 template <class WantUsedRval> 115 class MOZ_STACK_CLASS MOZ_NON_PARAM CallArgsBase { 116 static_assert(std::is_same_v<WantUsedRval, IncludeUsedRval> || 117 std::is_same_v<WantUsedRval, NoUsedRval>, 118 "WantUsedRval can only be IncludeUsedRval or NoUsedRval"); 119 120 protected: 121 Value* argv_ = nullptr; 122 unsigned argc_ = 0; 123 bool constructing_ : 1; 124 125 // True if the caller does not use the return value. 126 bool ignoresReturnValue_ : 1; 127 128 #ifdef JS_DEBUG 129 WantUsedRval wantUsedRval_; 130 bool usedRval() const { return wantUsedRval_.usedRval(); } 131 void setUsedRval() const { wantUsedRval_.setUsedRval(); } 132 void clearUsedRval() const { wantUsedRval_.clearUsedRval(); } 133 void assertUnusedRval() const { wantUsedRval_.assertUnusedRval(); } 134 #else 135 bool usedRval() const { return false; } 136 void setUsedRval() const {} 137 void clearUsedRval() const {} 138 void assertUnusedRval() const {} 139 #endif 140 141 CallArgsBase() : constructing_(false), ignoresReturnValue_(false) {} 142 143 public: 144 // CALLEE ACCESS 145 146 /* 147 * Returns the function being called, as a value. Must not be called after 148 * rval() has been used! 149 */ 150 HandleValue calleev() const { 151 this->assertUnusedRval(); 152 return HandleValue::fromMarkedLocation(&argv_[-2]); 153 } 154 155 /* 156 * Returns the function being called, as an object. Must not be called 157 * after rval() has been used! 158 */ 159 JSObject& callee() const { return calleev().toObject(); } 160 161 // CALLING/CONSTRUCTING-DIFFERENTIATIONS 162 163 bool isConstructing() const { 164 if (!argv_[-1].isMagic()) { 165 return false; 166 } 167 168 #ifdef JS_DEBUG 169 if (!this->usedRval()) { 170 CheckIsValidConstructible(calleev()); 171 } 172 #endif 173 174 return true; 175 } 176 177 bool ignoresReturnValue() const { return ignoresReturnValue_; } 178 179 MutableHandleValue newTarget() const { 180 MOZ_ASSERT(constructing_); 181 return MutableHandleValue::fromMarkedLocation(&this->argv_[argc_]); 182 } 183 184 /* 185 * Returns the |this| value passed to the function. This method must not 186 * be called when the function is being called as a constructor via |new|. 187 * The value may or may not be an object: it is the individual function's 188 * responsibility to box the value if needed. 189 */ 190 HandleValue thisv() const { 191 // Some internal code uses thisv() in constructing cases, so don't do 192 // this yet. 193 // MOZ_ASSERT(!argv_[-1].isMagic(JS_IS_CONSTRUCTING)); 194 return HandleValue::fromMarkedLocation(&argv_[-1]); 195 } 196 197 bool computeThis(JSContext* cx, MutableHandleObject thisObject) const { 198 if (thisv().isObject()) { 199 thisObject.set(&thisv().toObject()); 200 return true; 201 } 202 203 return ComputeThis(cx, base(), thisObject); 204 } 205 206 // ARGUMENTS 207 208 /* Returns the number of arguments. */ 209 unsigned length() const { return argc_; } 210 211 /* Returns the i-th zero-indexed argument. */ 212 MutableHandleValue operator[](unsigned i) const { 213 MOZ_RELEASE_ASSERT(i < argc_); 214 return MutableHandleValue::fromMarkedLocation(&this->argv_[i]); 215 } 216 217 /* 218 * Returns the i-th zero-indexed argument, or |undefined| if there's no 219 * such argument. 220 */ 221 HandleValue get(unsigned i) const { 222 return i < length() ? HandleValue::fromMarkedLocation(&this->argv_[i]) 223 : UndefinedHandleValue; 224 } 225 226 /* 227 * Returns true if the i-th zero-indexed argument is present and is not 228 * |undefined|. 229 */ 230 bool hasDefined(unsigned i) const { 231 return i < argc_ && !this->argv_[i].isUndefined(); 232 } 233 234 // RETURN VALUE 235 236 /* 237 * Returns the currently-set return value. The initial contents of this 238 * value are unspecified. Once this method has been called, callee() and 239 * calleev() can no longer be used. (If you're compiling against a debug 240 * build of SpiderMonkey, these methods will assert to aid debugging.) 241 * 242 * If the method you're implementing succeeds by returning true, you *must* 243 * set this. (SpiderMonkey doesn't currently assert this, but it will do 244 * so eventually.) You don't need to use or change this if your method 245 * fails. 246 */ 247 MutableHandleValue rval() const { 248 this->setUsedRval(); 249 return MutableHandleValue::fromMarkedLocation(&argv_[-2]); 250 } 251 252 /* 253 * Returns true if there are at least |required| arguments passed in. If 254 * false, it reports an error message on the context. 255 */ 256 JS_PUBLIC_API inline bool requireAtLeast(JSContext* cx, const char* fnname, 257 unsigned required) const; 258 259 public: 260 // These methods are publicly exposed, but they are *not* to be used when 261 // implementing a JSNative method and encapsulating access to |vp| within 262 // it. You probably don't want to use these! 263 264 void setCallee(const Value& aCalleev) const { 265 this->clearUsedRval(); 266 argv_[-2] = aCalleev; 267 } 268 269 void setThis(const Value& aThisv) const { argv_[-1] = aThisv; } 270 271 MutableHandleValue mutableThisv() const { 272 return MutableHandleValue::fromMarkedLocation(&argv_[-1]); 273 } 274 275 public: 276 // These methods are publicly exposed, but we're unsure of the interfaces 277 // (because they're hackish and drop assertions). Avoid using these if you 278 // can. 279 280 Value* array() const { return argv_; } 281 Value* end() const { return argv_ + argc_ + constructing_; } 282 283 public: 284 // These methods are only intended for internal use. Embedders shouldn't 285 // use them! 286 287 Value* base() const { return argv_ - 2; } 288 289 Value* spAfterCall() const { 290 this->setUsedRval(); 291 return argv_ - 1; 292 } 293 }; 294 295 } // namespace detail 296 297 class MOZ_STACK_CLASS MOZ_NON_PARAM CallArgs 298 : public detail::CallArgsBase<detail::IncludeUsedRval> { 299 private: 300 friend CallArgs CallArgsFromVp(unsigned argc, Value* vp); 301 friend CallArgs CallArgsFromSp(unsigned stackSlots, Value* sp, 302 bool constructing, bool ignoresReturnValue); 303 304 static CallArgs create(unsigned argc, Value* argv, bool constructing, 305 bool ignoresReturnValue = false) { 306 CallArgs args; 307 args.clearUsedRval(); 308 args.argv_ = argv; 309 args.argc_ = argc; 310 args.constructing_ = constructing; 311 args.ignoresReturnValue_ = ignoresReturnValue; 312 #ifdef DEBUG 313 AssertValueIsNotGray(args.thisv()); 314 AssertValueIsNotGray(args.calleev()); 315 for (unsigned i = 0; i < argc; ++i) { 316 AssertValueIsNotGray(argv[i]); 317 } 318 if (constructing) { 319 AssertValueIsNotGray(args.newTarget()); 320 } 321 #endif 322 return args; 323 } 324 325 public: 326 /* 327 * Helper for requireAtLeast to report the actual exception. Public 328 * so we can call it from CallArgsBase and not need multiple 329 * per-template instantiations of it. 330 */ 331 static JS_PUBLIC_API void reportMoreArgsNeeded(JSContext* cx, 332 const char* fnname, 333 unsigned required, 334 unsigned actual); 335 }; 336 337 namespace detail { 338 template <class WantUsedRval> 339 JS_PUBLIC_API inline bool CallArgsBase<WantUsedRval>::requireAtLeast( 340 JSContext* cx, const char* fnname, unsigned required) const { 341 if (MOZ_LIKELY(required <= length())) { 342 return true; 343 } 344 345 CallArgs::reportMoreArgsNeeded(cx, fnname, required, length()); 346 return false; 347 } 348 } // namespace detail 349 350 MOZ_ALWAYS_INLINE CallArgs CallArgsFromVp(unsigned argc, Value* vp) { 351 return CallArgs::create(argc, vp + 2, 352 vp[1].isMagicNoReleaseCheck(JS_IS_CONSTRUCTING)); 353 } 354 355 // This method is only intended for internal use in SpiderMonkey. We may 356 // eventually move it to an internal header. Embedders should use 357 // JS::CallArgsFromVp! 358 MOZ_ALWAYS_INLINE CallArgs CallArgsFromSp(unsigned stackSlots, Value* sp, 359 bool constructing = false, 360 bool ignoresReturnValue = false) { 361 return CallArgs::create(stackSlots - constructing, sp - stackSlots, 362 constructing, ignoresReturnValue); 363 } 364 365 } // namespace JS 366 367 #endif /* js_CallArgs_h */