Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "jit/VMFunctions.h"
#include "mozilla/FloatingPoint.h"
#include "builtin/MapObject.h"
#include "builtin/String.h"
#include "ds/OrderedHashTable.h"
#include "gc/Cell.h"
#include "gc/GC.h"
#include "jit/arm/Simulator-arm.h"
#include "jit/AtomicOperations.h"
#include "jit/BaselineIC.h"
#include "jit/CalleeToken.h"
#include "jit/JitFrames.h"
#include "jit/JitRuntime.h"
#include "jit/mips32/Simulator-mips32.h"
#include "jit/mips64/Simulator-mips64.h"
#include "jit/Simulator.h"
#include "js/experimental/JitInfo.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
#include "js/friend/WindowProxy.h" // js::IsWindow
#include "js/Printf.h"
#include "js/TraceKind.h"
#include "proxy/ScriptedProxyHandler.h"
#include "util/Unicode.h"
#include "vm/ArrayObject.h"
#include "vm/Compartment.h"
#include "vm/DateObject.h"
#include "vm/Float16.h"
#include "vm/Interpreter.h"
#include "vm/JSAtomUtils.h" // AtomizeString
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/SelfHosting.h"
#include "vm/StaticStrings.h"
#include "vm/TypedArrayObject.h"
#include "vm/TypeofEqOperand.h" // TypeofEqOperand
#include "vm/Watchtower.h"
#include "wasm/WasmGcObject.h"
#include "debugger/DebugAPI-inl.h"
#include "gc/StoreBuffer-inl.h"
#include "jit/BaselineFrame-inl.h"
#include "jit/VMFunctionList-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/JSAtomUtils-inl.h" // TypeName
#include "vm/JSContext-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/PlainObject-inl.h" // js::CreateThis
#include "vm/StringObject-inl.h"
using namespace js;
using namespace js::jit;
namespace js {
class ArgumentsObject;
class NamedLambdaObject;
class AsyncFunctionGeneratorObject;
class RegExpObject;
namespace jit {
struct IonOsrTempData;
struct PopValues {
uint8_t numValues;
explicit constexpr PopValues(uint8_t numValues = 0) : numValues(numValues) {}
};
template <class>
struct ReturnTypeToDataType { /* Unexpected return type for a VMFunction. */
};
template <>
struct ReturnTypeToDataType<void> {
static const DataType result = Type_Void;
};
template <>
struct ReturnTypeToDataType<bool> {
static const DataType result = Type_Bool;
};
template <class T>
struct ReturnTypeToDataType<T*> {
// Assume by default that any pointer return types are cells.
static_assert(std::is_base_of_v<gc::Cell, T>);
static const DataType result = Type_Cell;
};
// Convert argument types to properties of the argument known by the jit.
template <class T>
struct TypeToArgProperties {
static const uint32_t result =
(sizeof(T) <= sizeof(void*) ? VMFunctionData::Word
: VMFunctionData::Double);
};
template <>
struct TypeToArgProperties<const Value&> {
static const uint32_t result =
TypeToArgProperties<Value>::result | VMFunctionData::ByRef;
};
template <>
struct TypeToArgProperties<HandleValue> {
static const uint32_t result =
TypeToArgProperties<Value>::result | VMFunctionData::ByRef;
};
template <>
struct TypeToArgProperties<MutableHandleValue> {
static const uint32_t result =
TypeToArgProperties<Value>::result | VMFunctionData::ByRef;
};
template <>
struct TypeToArgProperties<HandleId> {
static const uint32_t result =
TypeToArgProperties<jsid>::result | VMFunctionData::ByRef;
};
template <class T>
struct TypeToArgProperties<Handle<T*>> {
// Assume by default that any pointer handle types are cells.
static_assert(std::is_base_of_v<gc::Cell, T>);
static const uint32_t result =
TypeToArgProperties<T*>::result | VMFunctionData::ByRef;
};
template <class T>
struct TypeToArgProperties<Handle<T>> {
// Fail for Handle types that aren't specialized above.
};
// Convert argument type to whether or not it should be passed in a float
// register on platforms that have them, like x64.
template <class T>
struct TypeToPassInFloatReg {
static const uint32_t result = 0;
};
template <>
struct TypeToPassInFloatReg<double> {
static const uint32_t result = 1;
};
// Convert argument types to root types used by the gc, see TraceJitExitFrame.
template <class T>
struct TypeToRootType {
static const uint32_t result = VMFunctionData::RootNone;
};
template <>
struct TypeToRootType<HandleValue> {
static const uint32_t result = VMFunctionData::RootValue;
};
template <>
struct TypeToRootType<MutableHandleValue> {
static const uint32_t result = VMFunctionData::RootValue;
};
template <>
struct TypeToRootType<HandleId> {
static const uint32_t result = VMFunctionData::RootId;
};
template <class T>
struct TypeToRootType<Handle<T*>> {
// Assume by default that any pointer types are cells.
static_assert(std::is_base_of_v<gc::Cell, T>);
static constexpr uint32_t rootType() {
using JS::TraceKind;
switch (JS::MapTypeToTraceKind<T>::kind) {
case TraceKind::Object:
return VMFunctionData::RootObject;
case TraceKind::BigInt:
return VMFunctionData::RootBigInt;
case TraceKind::String:
return VMFunctionData::RootString;
case TraceKind::Shape:
case TraceKind::Script:
case TraceKind::Scope:
return VMFunctionData::RootCell;
case TraceKind::Symbol:
case TraceKind::BaseShape:
case TraceKind::Null:
case TraceKind::JitCode:
case TraceKind::RegExpShared:
case TraceKind::GetterSetter:
case TraceKind::PropMap:
MOZ_CRASH("Unexpected trace kind");
}
}
static constexpr uint32_t result = rootType();
};
template <class T>
struct TypeToRootType<Handle<T>> {
// Fail for Handle types that aren't specialized above.
};
template <class>
struct OutParamToDataType {
static const DataType result = Type_Void;
};
template <class T>
struct OutParamToDataType<const T*> {
// Const pointers can't be output parameters.
static const DataType result = Type_Void;
};
template <>
struct OutParamToDataType<uint64_t*> {
// Already used as an input type, so it can't be used as an output param.
static const DataType result = Type_Void;
};
template <>
struct OutParamToDataType<JSObject*> {
// Already used as an input type, so it can't be used as an output param.
static const DataType result = Type_Void;
};
template <>
struct OutParamToDataType<JSString*> {
// Already used as an input type, so it can't be used as an output param.
static const DataType result = Type_Void;
};
template <>
struct OutParamToDataType<BaselineFrame*> {
// Already used as an input type, so it can't be used as an output param.
static const DataType result = Type_Void;
};
template <>
struct OutParamToDataType<gc::AllocSite*> {
// Already used as an input type, so it can't be used as an output param.
static const DataType result = Type_Void;
};
template <>
struct OutParamToDataType<Value*> {
static const DataType result = Type_Value;
};
template <>
struct OutParamToDataType<int*> {
static const DataType result = Type_Int32;
};
template <>
struct OutParamToDataType<uint32_t*> {
static const DataType result = Type_Int32;
};
template <>
struct OutParamToDataType<bool*> {
static const DataType result = Type_Bool;
};
template <>
struct OutParamToDataType<double*> {
static const DataType result = Type_Double;
};
template <class T>
struct OutParamToDataType<T*> {
// Fail for pointer types that aren't specialized above.
};
template <class T>
struct OutParamToDataType<T**> {
static const DataType result = Type_Pointer;
};
template <class T>
struct OutParamToDataType<MutableHandle<T>> {
static const DataType result = Type_Handle;
};
template <class>
struct OutParamToRootType {
static const VMFunctionData::RootType result = VMFunctionData::RootNone;
};
template <>
struct OutParamToRootType<MutableHandleValue> {
static const VMFunctionData::RootType result = VMFunctionData::RootValue;
};
template <>
struct OutParamToRootType<MutableHandleObject> {
static const VMFunctionData::RootType result = VMFunctionData::RootObject;
};
template <>
struct OutParamToRootType<MutableHandleString> {
static const VMFunctionData::RootType result = VMFunctionData::RootString;
};
template <>
struct OutParamToRootType<MutableHandleBigInt> {
static const VMFunctionData::RootType result = VMFunctionData::RootBigInt;
};
// Construct a bit mask from a list of types. The mask is constructed as an OR
// of the mask produced for each argument. The result of each argument is
// shifted by its index, such that the result of the first argument is on the
// low bits of the mask, and the result of the last argument in part of the
// high bits of the mask.
template <template <typename> class Each, typename ResultType, size_t Shift,
typename... Args>
struct BitMask;
template <template <typename> class Each, typename ResultType, size_t Shift>
struct BitMask<Each, ResultType, Shift> {
static constexpr ResultType result = ResultType();
};
template <template <typename> class Each, typename ResultType, size_t Shift,
typename HeadType, typename... TailTypes>
struct BitMask<Each, ResultType, Shift, HeadType, TailTypes...> {
static_assert(ResultType(Each<HeadType>::result) < (1 << Shift),
"not enough bits reserved by the shift for individual results");
static_assert(sizeof...(TailTypes) < (8 * sizeof(ResultType) / Shift),
"not enough bits in the result type to store all bit masks");
static constexpr ResultType result =
ResultType(Each<HeadType>::result) |
(BitMask<Each, ResultType, Shift, TailTypes...>::result << Shift);
};
// Helper template to build the VMFunctionData for a function.
template <typename... Args>
struct VMFunctionDataHelper;
template <class R, typename... Args>
struct VMFunctionDataHelper<R (*)(JSContext*, Args...)>
: public VMFunctionData {
using Fun = R (*)(JSContext*, Args...);
static constexpr DataType returnType() {
return ReturnTypeToDataType<R>::result;
}
static constexpr DataType outParam() {
return OutParamToDataType<typename LastArg<Args...>::Type>::result;
}
static constexpr RootType outParamRootType() {
return OutParamToRootType<typename LastArg<Args...>::Type>::result;
}
static constexpr size_t NbArgs() { return sizeof...(Args); }
static constexpr size_t explicitArgs() {
return NbArgs() - (outParam() != Type_Void ? 1 : 0);
}
static constexpr uint32_t argumentProperties() {
return BitMask<TypeToArgProperties, uint32_t, 2, Args...>::result;
}
static constexpr uint32_t argumentPassedInFloatRegs() {
return BitMask<TypeToPassInFloatReg, uint32_t, 2, Args...>::result;
}
static constexpr uint64_t argumentRootTypes() {
return BitMask<TypeToRootType, uint64_t, 3, Args...>::result;
}
constexpr explicit VMFunctionDataHelper(const char* name)
: VMFunctionData(name, explicitArgs(), argumentProperties(),
argumentPassedInFloatRegs(), argumentRootTypes(),
outParam(), outParamRootType(), returnType(),
/* extraValuesToPop = */ 0) {}
constexpr explicit VMFunctionDataHelper(const char* name,
PopValues extraValuesToPop)
: VMFunctionData(name, explicitArgs(), argumentProperties(),
argumentPassedInFloatRegs(), argumentRootTypes(),
outParam(), outParamRootType(), returnType(),
extraValuesToPop.numValues) {}
};
// GCC warns when the signature does not have matching attributes (for example
// [[nodiscard]]). Squelch this warning to avoid a GCC-only footgun.
#if MOZ_IS_GCC
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wignored-attributes"
#endif
// Generate VMFunctionData array.
static constexpr VMFunctionData vmFunctions[] = {
#define DEF_VMFUNCTION(name, fp, valuesToPop...) \
VMFunctionDataHelper<decltype(&(::fp))>(#name, PopValues(valuesToPop)),
VMFUNCTION_LIST(DEF_VMFUNCTION)
#undef DEF_VMFUNCTION
};
#if MOZ_IS_GCC
# pragma GCC diagnostic pop
#endif
// Generate arrays storing C++ function pointers. These pointers are not stored
// in VMFunctionData because there's no good way to cast them to void* in
// constexpr code. Compilers are smart enough to treat the const array below as
// constexpr.
#define DEF_VMFUNCTION(name, fp, ...) (void*)(::fp),
static void* const vmFunctionTargets[] = {VMFUNCTION_LIST(DEF_VMFUNCTION)};
#undef DEF_VMFUNCTION
const VMFunctionData& GetVMFunction(VMFunctionId id) {
return vmFunctions[size_t(id)];
}
static DynFn GetVMFunctionTarget(VMFunctionId id) {
return DynFn{vmFunctionTargets[size_t(id)]};
}
size_t NumVMFunctions() { return size_t(VMFunctionId::Count); }
size_t VMFunctionData::sizeOfOutParamStackSlot() const {
switch (outParam) {
case Type_Value:
return sizeof(Value);
case Type_Pointer:
case Type_Int32:
case Type_Bool:
return sizeof(uintptr_t);
case Type_Double:
return sizeof(double);
case Type_Handle:
switch (outParamRootType) {
case RootNone:
MOZ_CRASH("Handle must have root type");
case RootObject:
case RootString:
case RootCell:
case RootBigInt:
case RootId:
return sizeof(uintptr_t);
case RootValue:
return sizeof(Value);
}
MOZ_CRASH("Invalid type");
case Type_Void:
return 0;
case Type_Cell:
MOZ_CRASH("Unexpected outparam type");
}
MOZ_CRASH("Invalid type");
}
bool JitRuntime::generateVMWrappers(JSContext* cx, MacroAssembler& masm,
PerfSpewerRangeRecorder& rangeRecorder) {
// Generate all VM function wrappers.
static constexpr size_t NumVMFunctions = size_t(VMFunctionId::Count);
if (!functionWrapperOffsets_.reserve(NumVMFunctions)) {
return false;
}
#ifdef DEBUG
const char* lastName = nullptr;
#endif
for (size_t i = 0; i < NumVMFunctions; i++) {
VMFunctionId id = VMFunctionId(i);
const VMFunctionData& fun = GetVMFunction(id);
#ifdef DEBUG
// Assert the list is sorted by name.
if (lastName) {
MOZ_ASSERT(strcmp(lastName, fun.name()) < 0,
"VM function list must be sorted by name");
}
lastName = fun.name();
#endif
JitSpew(JitSpew_Codegen, "# VM function wrapper (%s)", fun.name());
uint32_t offset;
if (!generateVMWrapper(cx, masm, id, fun, GetVMFunctionTarget(id),
&offset)) {
return false;
}
#if defined(JS_ION_PERF)
rangeRecorder.recordVMWrapperOffset(fun.name());
#else
rangeRecorder.recordOffset("Trampoline: VMWrapper");
#endif
MOZ_ASSERT(functionWrapperOffsets_.length() == size_t(id));
functionWrapperOffsets_.infallibleAppend(offset);
}
return true;
};
bool InvokeFunction(JSContext* cx, HandleObject obj, bool constructing,
bool ignoresReturnValue, uint32_t argc, Value* argv,
MutableHandleValue rval) {
RootedExternalValueArray argvRoot(cx, argc + 1 + constructing, argv);
// Data in the argument vector is arranged for a JIT -> JIT call.
RootedValue thisv(cx, argv[0]);
Value* argvWithoutThis = argv + 1;
RootedValue fval(cx, ObjectValue(*obj));
if (constructing) {
if (!IsConstructor(fval)) {
ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, fval,
nullptr);
return false;
}
ConstructArgs cargs(cx);
if (!cargs.init(cx, argc)) {
return false;
}
for (uint32_t i = 0; i < argc; i++) {
cargs[i].set(argvWithoutThis[i]);
}
RootedValue newTarget(cx, argvWithoutThis[argc]);
// See CreateThisFromIon for why this can be NullValue.
if (thisv.isNull()) {
thisv.setMagic(JS_IS_CONSTRUCTING);
}
// If |this| hasn't been created, or is JS_UNINITIALIZED_LEXICAL,
// we can use normal construction code without creating an extraneous
// object.
if (thisv.isMagic()) {
MOZ_ASSERT(thisv.whyMagic() == JS_IS_CONSTRUCTING ||
thisv.whyMagic() == JS_UNINITIALIZED_LEXICAL);
RootedObject obj(cx);
if (!Construct(cx, fval, cargs, newTarget, &obj)) {
return false;
}
rval.setObject(*obj);
return true;
}
// Otherwise the default |this| has already been created. We could
// almost perform a *call* at this point, but we'd break |new.target|
// in the function. So in this one weird case we call a one-off
// construction path that *won't* set |this| to JS_IS_CONSTRUCTING.
return InternalConstructWithProvidedThis(cx, fval, thisv, cargs, newTarget,
rval);
}
InvokeArgsMaybeIgnoresReturnValue args(cx);
if (!args.init(cx, argc, ignoresReturnValue)) {
return false;
}
for (size_t i = 0; i < argc; i++) {
args[i].set(argvWithoutThis[i]);
}
return Call(cx, fval, thisv, args, rval);
}
void* GetContextSensitiveInterpreterStub() {
return TlsContext.get()->runtime()->jitRuntime()->interpreterStub().value;
}
bool InvokeFromInterpreterStub(JSContext* cx,
InterpreterStubExitFrameLayout* frame) {
JitFrameLayout* jsFrame = frame->jsFrame();
CalleeToken token = jsFrame->calleeToken();
Value* argv = jsFrame->thisAndActualArgs();
uint32_t numActualArgs = jsFrame->numActualArgs();
bool constructing = CalleeTokenIsConstructing(token);
RootedFunction fun(cx, CalleeTokenToFunction(token));
// Ensure new.target immediately follows the actual arguments (the arguments
// rectifier added padding).
if (constructing && numActualArgs < fun->nargs()) {
argv[1 + numActualArgs] = argv[1 + fun->nargs()];
}
RootedValue rval(cx);
if (!InvokeFunction(cx, fun, constructing,
/* ignoresReturnValue = */ false, numActualArgs, argv,
&rval)) {
return false;
}
// Overwrite |this| with the return value.
argv[0] = rval;
return true;
}
static bool CheckOverRecursedImpl(JSContext* cx, size_t extra) {
// We just failed the jitStackLimit check. There are two possible reasons:
// 1) jitStackLimit was the real stack limit and we're over-recursed
// 2) jitStackLimit was set to JS::NativeStackLimitMin by
// JSContext::requestInterrupt and we need to call
// JSContext::handleInterrupt.
// This handles 1).
#ifdef JS_SIMULATOR
if (cx->simulator()->overRecursedWithExtra(extra)) {
ReportOverRecursed(cx);
return false;
}
#else
AutoCheckRecursionLimit recursion(cx);
if (!recursion.checkWithExtra(cx, extra)) {
return false;
}
#endif
// This handles 2).
gc::MaybeVerifyBarriers(cx);
return cx->handleInterrupt();
}
bool CheckOverRecursed(JSContext* cx) { return CheckOverRecursedImpl(cx, 0); }
bool CheckOverRecursedBaseline(JSContext* cx, BaselineFrame* frame) {
// The stack check in Baseline happens before pushing locals so we have to
// account for that by including script->nslots() in the C++ recursion check.
size_t extra = frame->script()->nslots() * sizeof(Value);
return CheckOverRecursedImpl(cx, extra);
}
bool MutatePrototype(JSContext* cx, Handle<PlainObject*> obj,
HandleValue value) {
if (!value.isObjectOrNull()) {
return true;
}
RootedObject newProto(cx, value.toObjectOrNull());
return SetPrototype(cx, obj, newProto);
}
template <EqualityKind Kind>
bool StringsEqual(JSContext* cx, HandleString lhs, HandleString rhs,
bool* res) {
JSLinearString* linearLhs = lhs->ensureLinear(cx);
if (!linearLhs) {
return false;
}
JSLinearString* linearRhs = rhs->ensureLinear(cx);
if (!linearRhs) {
return false;
}
*res = EqualChars(linearLhs, linearRhs);
if constexpr (Kind == EqualityKind::NotEqual) {
*res = !*res;
}
return true;
}
template bool StringsEqual<EqualityKind::Equal>(JSContext* cx, HandleString lhs,
HandleString rhs, bool* res);
template bool StringsEqual<EqualityKind::NotEqual>(JSContext* cx,
HandleString lhs,
HandleString rhs, bool* res);
template <ComparisonKind Kind>
bool StringsCompare(JSContext* cx, HandleString lhs, HandleString rhs,
bool* res) {
int32_t result;
if (!js::CompareStrings(cx, lhs, rhs, &result)) {
return false;
}
if (Kind == ComparisonKind::LessThan) {
*res = result < 0;
} else {
*res = result >= 0;
}
return true;
}
template bool StringsCompare<ComparisonKind::LessThan>(JSContext* cx,
HandleString lhs,
HandleString rhs,
bool* res);
template bool StringsCompare<ComparisonKind::GreaterThanOrEqual>(
JSContext* cx, HandleString lhs, HandleString rhs, bool* res);
JSString* ArrayJoin(JSContext* cx, HandleObject array, HandleString sep) {
JS::RootedValueArray<3> argv(cx);
argv[0].setUndefined();
argv[1].setObject(*array);
argv[2].setString(sep);
if (!js::array_join(cx, 1, argv.begin())) {
return nullptr;
}
return argv[0].toString();
}
bool SetArrayLength(JSContext* cx, HandleObject obj, HandleValue value,
bool strict) {
Handle<ArrayObject*> array = obj.as<ArrayObject>();
RootedId id(cx, NameToId(cx->names().length));
ObjectOpResult result;
// SetArrayLength is called by IC stubs for SetProp and SetElem on arrays'
// "length" property.
//
// ArraySetLength below coerces |value| before checking for length being
// writable, and in the case of illegal values, will throw RangeError even
// when "length" is not writable. This is incorrect observable behavior,
// as a regular [[Set]] operation will check for "length" being
// writable before attempting any assignment.
//
// So, perform ArraySetLength if and only if "length" is writable.
if (array->lengthIsWritable()) {
Rooted<PropertyDescriptor> desc(
cx, PropertyDescriptor::Data(value, JS::PropertyAttribute::Writable));
if (!ArraySetLength(cx, array, id, desc, result)) {
return false;
}
} else {
MOZ_ALWAYS_TRUE(result.fail(JSMSG_READ_ONLY));
}
return result.checkStrictModeError(cx, obj, id, strict);
}
bool CharCodeAt(JSContext* cx, HandleString str, int32_t index,
uint32_t* code) {
char16_t c;
if (!str->getChar(cx, index, &c)) {
return false;
}
*code = c;
return true;
}
bool CodePointAt(JSContext* cx, HandleString str, int32_t index,
uint32_t* code) {
char32_t codePoint;
if (!str->getCodePoint(cx, size_t(index), &codePoint)) {
return false;
}
*code = codePoint;
return true;
}
JSLinearString* StringFromCharCodeNoGC(JSContext* cx, int32_t code) {
AutoUnsafeCallWithABI unsafe;
char16_t c = char16_t(code);
if (StaticStrings::hasUnit(c)) {
return cx->staticStrings().getUnit(c);
}
return NewInlineString<NoGC>(cx, {c}, 1);
}
JSLinearString* LinearizeForCharAccessPure(JSString* str) {
AutoUnsafeCallWithABI unsafe;
// Should only be called on ropes.
MOZ_ASSERT(str->isRope());
// ensureLinear is intentionally called with a nullptr to avoid OOM reporting.
return str->ensureLinear(nullptr);
}
JSLinearString* LinearizeForCharAccess(JSContext* cx, JSString* str) {
// Should only be called on ropes.
MOZ_ASSERT(str->isRope());
return str->ensureLinear(cx);
}
template <typename CharT>
static size_t StringTrimStartIndex(mozilla::Range<CharT> chars) {
size_t begin = 0;
while (begin < chars.length() && unicode::IsSpace(chars[begin])) {
++begin;
}
return begin;
}
template <typename CharT>
static size_t StringTrimEndIndex(mozilla::Range<CharT> chars, size_t begin) {
size_t end = chars.length();
while (end > begin && unicode::IsSpace(chars[end - 1])) {
--end;
}
return end;
}
int32_t StringTrimStartIndex(const JSString* str) {
AutoUnsafeCallWithABI unsafe;
MOZ_ASSERT(str->isLinear());
const auto* linear = &str->asLinear();
size_t begin;
if (linear->hasLatin1Chars()) {
JS::AutoCheckCannotGC nogc;
begin = StringTrimStartIndex(linear->latin1Range(nogc));
} else {
JS::AutoCheckCannotGC nogc;
begin = StringTrimStartIndex(linear->twoByteRange(nogc));
}
return int32_t(begin);
}
int32_t StringTrimEndIndex(const JSString* str, int32_t start) {
AutoUnsafeCallWithABI unsafe;
MOZ_ASSERT(str->isLinear());
MOZ_ASSERT(start >= 0 && size_t(start) <= str->length());
const auto* linear = &str->asLinear();
size_t end;
if (linear->hasLatin1Chars()) {
JS::AutoCheckCannotGC nogc;
end = StringTrimEndIndex(linear->latin1Range(nogc), size_t(start));
} else {
JS::AutoCheckCannotGC nogc;
end = StringTrimEndIndex(linear->twoByteRange(nogc), size_t(start));
}
return int32_t(end);
}
JSString* CharCodeToLowerCase(JSContext* cx, int32_t code) {
RootedString str(cx, StringFromCharCode(cx, code));
if (!str) {
return nullptr;
}
return js::StringToLowerCase(cx, str);
}
JSString* CharCodeToUpperCase(JSContext* cx, int32_t code) {
RootedString str(cx, StringFromCharCode(cx, code));
if (!str) {
return nullptr;
}
return js::StringToUpperCase(cx, str);
}
bool SetProperty(JSContext* cx, HandleObject obj, Handle<PropertyName*> name,
HandleValue value, bool strict, jsbytecode* pc) {
RootedId id(cx, NameToId(name));
RootedValue receiver(cx, ObjectValue(*obj));
ObjectOpResult result;
if (MOZ_LIKELY(!obj->getOpsSetProperty())) {
JSOp op = JSOp(*pc);
if (op == JSOp::SetName || op == JSOp::StrictSetName ||
op == JSOp::SetGName || op == JSOp::StrictSetGName) {
if (!NativeSetProperty<Unqualified>(cx, obj.as<NativeObject>(), id, value,
receiver, result)) {
return false;
}
} else {
if (!NativeSetProperty<Qualified>(cx, obj.as<NativeObject>(), id, value,
receiver, result)) {
return false;
}
}
} else {
if (!SetProperty(cx, obj, id, value, receiver, result)) {
return false;
}
}
return result.checkStrictModeError(cx, obj, id, strict);
}
bool InterruptCheck(JSContext* cx) {
gc::MaybeVerifyBarriers(cx);
return CheckForInterrupt(cx);
}
JSObject* NewStringObject(JSContext* cx, HandleString str) {
return StringObject::create(cx, str);
}
bool OperatorIn(JSContext* cx, HandleValue key, HandleObject obj, bool* out) {
RootedId id(cx);
return ToPropertyKey(cx, key, &id) && HasProperty(cx, obj, id, out);
}
bool GetIntrinsicValue(JSContext* cx, Handle<PropertyName*> name,
MutableHandleValue rval) {
return GlobalObject::getIntrinsicValue(cx, cx->global(), name, rval);
}
bool CreateThisFromIC(JSContext* cx, HandleObject callee,
HandleObject newTarget, MutableHandleValue rval) {
HandleFunction fun = callee.as<JSFunction>();
MOZ_ASSERT(fun->isInterpreted());
MOZ_ASSERT(fun->isConstructor());
MOZ_ASSERT(cx->realm() == fun->realm(),
"Realm switching happens before creating this");
// CreateThis expects rval to be this magic value.
rval.set(MagicValue(JS_IS_CONSTRUCTING));
if (!js::CreateThis(cx, fun, newTarget, GenericObject, rval)) {
return false;
}
MOZ_ASSERT_IF(rval.isObject(), fun->realm() == rval.toObject().nonCCWRealm());
return true;
}
bool CreateThisFromIon(JSContext* cx, HandleObject callee,
HandleObject newTarget, MutableHandleValue rval) {
// Return JS_IS_CONSTRUCTING for cases not supported by the inline call path.
rval.set(MagicValue(JS_IS_CONSTRUCTING));
if (!callee->is<JSFunction>()) {
return true;
}
HandleFunction fun = callee.as<JSFunction>();
if (!fun->isInterpreted() || !fun->isConstructor()) {
return true;
}
// If newTarget is not a function or is a function with a possibly-getter
// .prototype property, return NullValue to signal to LCallGeneric that it has
// to take the slow path. Note that we return NullValue instead of a
// MagicValue only because it's easier and faster to check for in JIT code
// (if we returned a MagicValue, JIT code would have to check both the type
// tag and the JSWhyMagic payload).
if (!fun->constructorNeedsUninitializedThis()) {
if (!newTarget->is<JSFunction>()) {
rval.setNull();
return true;
}
JSFunction* newTargetFun = &newTarget->as<JSFunction>();
if (!newTargetFun->hasNonConfigurablePrototypeDataProperty()) {
rval.setNull();
return true;
}
}
AutoRealm ar(cx, fun);
if (!js::CreateThis(cx, fun, newTarget, GenericObject, rval)) {
return false;
}
MOZ_ASSERT_IF(rval.isObject(), fun->realm() == rval.toObject().nonCCWRealm());
return true;
}
void PostWriteBarrier(JSRuntime* rt, js::gc::Cell* cell) {
AutoUnsafeCallWithABI unsafe;
rt->gc.storeBuffer().putWholeCellDontCheckLast(cell);
}
static const size_t MAX_WHOLE_CELL_BUFFER_SIZE = 4096;
void PostWriteElementBarrier(JSRuntime* rt, JSObject* obj, int32_t index) {
AutoUnsafeCallWithABI unsafe;
MOZ_ASSERT(!IsInsideNursery(obj));
NativeObject* nobj = &obj->as<NativeObject>();
MOZ_ASSERT(index >= 0);
MOZ_ASSERT(uint32_t(index) < nobj->getDenseInitializedLength());
if (gc::StoreBuffer::isInWholeCellBuffer(nobj)) {
return;
}
gc::StoreBuffer* sb = &rt->gc.storeBuffer();
if (nobj->getDenseInitializedLength() > MAX_WHOLE_CELL_BUFFER_SIZE ||
rt->hasZealMode(gc::ZealMode::ElementsBarrier)) {
sb->putSlot(nobj, HeapSlot::Element, nobj->unshiftedIndex(index), 1);
return;
}
sb->putWholeCell(obj);
}
void PostGlobalWriteBarrier(JSRuntime* rt, GlobalObject* obj) {
MOZ_ASSERT(obj->JSObject::is<GlobalObject>());
if (!obj->realm()->globalWriteBarriered) {
AutoUnsafeCallWithABI unsafe;
rt->gc.storeBuffer().putWholeCell(obj);
obj->realm()->globalWriteBarriered = 1;
}
}
bool GetInt32FromStringPure(JSContext* cx, JSString* str, int32_t* result) {
// We shouldn't GC here as this is called directly from IC code.
AutoUnsafeCallWithABI unsafe;
double d;
if (!StringToNumberPure(cx, str, &d)) {
return false;
}
return mozilla::NumberIsInt32(d, result);
}
int32_t GetIndexFromString(JSString* str) {
// We shouldn't GC here as this is called directly from IC code.
AutoUnsafeCallWithABI unsafe;
if (!str->isLinear()) {
return -1;
}
uint32_t index = UINT32_MAX; // Initialize this to appease Valgrind.
if (!str->asLinear().isIndex(&index) || index > INT32_MAX) {
return -1;
}
return int32_t(index);
}
JSObject* WrapObjectPure(JSContext* cx, JSObject* obj) {
// IC code calls this directly so we shouldn't GC.
AutoUnsafeCallWithABI unsafe;
MOZ_ASSERT(obj);