From 12e3ee76966664ca0330738a07c7afb9fd754857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Mon, 14 May 2018 10:39:09 -0700 Subject: Update tzdata in ICU data files to 2018e. --- js/src/builtin/IntlTimeZoneData.h | 2 +- js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js | 2 +- js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js | 2 +- js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js | 2 +- js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) (limited to 'js') diff --git a/js/src/builtin/IntlTimeZoneData.h b/js/src/builtin/IntlTimeZoneData.h index 3d5c1b0d5..fa808c0b9 100644 --- a/js/src/builtin/IntlTimeZoneData.h +++ b/js/src/builtin/IntlTimeZoneData.h @@ -1,5 +1,5 @@ // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2018d +// tzdata version = 2018e #ifndef builtin_IntlTimeZoneData_h #define builtin_IntlTimeZoneData_h diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js index 7b3a46a60..d87abd7be 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2018d +// tzdata version = 2018e const tzMapper = [ x => x, diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js index ed63df921..b96dac96f 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2018d +// tzdata version = 2018e const tzMapper = [ x => x, diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js index 215808765..66ef3075d 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2018d +// tzdata version = 2018e const tzMapper = [ x => x, diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js index 48242dfbd..8d44204bc 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2018d +// tzdata version = 2018e const tzMapper = [ x => x, -- cgit v1.2.3 From ff901dc5267903b165c53139ddcff5f31bbf6964 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Fri, 6 Apr 2018 13:24:25 -0400 Subject: Change inlining of intrinsics. --- js/src/builtin/Array.js | 13 +++++----- js/src/builtin/Map.js | 6 ++--- js/src/builtin/Set.js | 6 ++--- js/src/builtin/String.js | 9 ++++--- js/src/jit/CodeGenerator.cpp | 26 +++++++++++++++++++ js/src/jit/CodeGenerator.h | 1 + js/src/jit/InlinableNatives.h | 10 +++++--- js/src/jit/IonBuilder.h | 1 + js/src/jit/Lowering.cpp | 10 ++++++++ js/src/jit/Lowering.h | 1 + js/src/jit/MCallOptimize.cpp | 51 +++++++++++++++++++++++++++++++------ js/src/jit/MIR.h | 42 ++++++++++++++++++++++++++++++ js/src/jit/MOpcodes.h | 1 + js/src/jit/shared/LIR-shared.h | 23 +++++++++++++++++ js/src/jit/shared/LOpcodes-shared.h | 1 + js/src/vm/SelfHosting.cpp | 45 +++++++++++++++++++++++--------- 16 files changed, 206 insertions(+), 40 deletions(-) (limited to 'js') diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index 360dd2af1..30e6fb35f 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -711,13 +711,14 @@ function CreateArrayIterator(obj, kind) { // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-%arrayiteratorprototype%.next function ArrayIteratorNext() { // Step 1-3. - if (!IsObject(this) || !IsArrayIterator(this)) { + var obj; + if (!IsObject(this) || (obj = GuardToArrayIterator(this)) === null) { return callFunction(CallArrayIteratorMethodIfWrapped, this, "ArrayIteratorNext"); } // Step 4. - var a = UnsafeGetReservedSlot(this, ITERATOR_SLOT_TARGET); + var a = UnsafeGetReservedSlot(obj, ITERATOR_SLOT_TARGET); var result = { value: undefined, done: false }; // Step 5. @@ -728,10 +729,10 @@ function ArrayIteratorNext() { // Step 6. // The index might not be an integer, so we have to do a generic get here. - var index = UnsafeGetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX); + var index = UnsafeGetReservedSlot(obj, ITERATOR_SLOT_NEXT_INDEX); // Step 7. - var itemKind = UnsafeGetInt32FromReservedSlot(this, ITERATOR_SLOT_ITEM_KIND); + var itemKind = UnsafeGetInt32FromReservedSlot(obj, ITERATOR_SLOT_ITEM_KIND); // Step 8-9. var len = IsPossiblyWrappedTypedArray(a) @@ -740,13 +741,13 @@ function ArrayIteratorNext() { // Step 10. if (index >= len) { - UnsafeSetReservedSlot(this, ITERATOR_SLOT_TARGET, null); + UnsafeSetReservedSlot(obj, ITERATOR_SLOT_TARGET, null); result.done = true; return result; } // Step 11. - UnsafeSetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX, index + 1); + UnsafeSetReservedSlot(obj, ITERATOR_SLOT_NEXT_INDEX, index + 1); // Step 16. if (itemKind === ITEM_KIND_VALUE) { diff --git a/js/src/builtin/Map.js b/js/src/builtin/Map.js index 580629a13..434cd6529 100644 --- a/js/src/builtin/Map.js +++ b/js/src/builtin/Map.js @@ -62,8 +62,8 @@ function MapIteratorNext() { var O = this; // Steps 2-3. - if (!IsObject(O) || !IsMapIterator(O)) - return callFunction(CallMapIteratorMethodIfWrapped, O, "MapIteratorNext"); + if (!IsObject(O) || (O = GuardToMapIterator(O)) === null) + return callFunction(CallMapIteratorMethodIfWrapped, this, "MapIteratorNext"); // Steps 4-5 (implemented in _GetNextMapEntryForIterator). // Steps 8-9 (omitted). @@ -82,7 +82,7 @@ function MapIteratorNext() { // Steps 10.b-c (omitted). // Step 6. - var itemKind = UnsafeGetInt32FromReservedSlot(this, ITERATOR_SLOT_ITEM_KIND); + var itemKind = UnsafeGetInt32FromReservedSlot(O, ITERATOR_SLOT_ITEM_KIND); var result; if (itemKind === ITEM_KIND_KEY) { diff --git a/js/src/builtin/Set.js b/js/src/builtin/Set.js index 9af6cf8d1..e2571e66a 100644 --- a/js/src/builtin/Set.js +++ b/js/src/builtin/Set.js @@ -64,8 +64,8 @@ function SetIteratorNext() { var O = this; // Steps 2-3. - if (!IsObject(O) || !IsSetIterator(O)) - return callFunction(CallSetIteratorMethodIfWrapped, O, "SetIteratorNext"); + if (!IsObject(O) || (O = GuardToSetIterator(O)) === null) + return callFunction(CallSetIteratorMethodIfWrapped, this, "SetIteratorNext"); // Steps 4-5 (implemented in _GetNextSetEntryForIterator). // Steps 8-9 (omitted). @@ -83,7 +83,7 @@ function SetIteratorNext() { // Steps 10.b-c (omitted). // Step 6. - var itemKind = UnsafeGetInt32FromReservedSlot(this, ITERATOR_SLOT_ITEM_KIND); + var itemKind = UnsafeGetInt32FromReservedSlot(O, ITERATOR_SLOT_ITEM_KIND); var result; if (itemKind === ITEM_KIND_VALUE) { diff --git a/js/src/builtin/String.js b/js/src/builtin/String.js index 6d1d335a0..e5b2ad552 100644 --- a/js/src/builtin/String.js +++ b/js/src/builtin/String.js @@ -529,16 +529,17 @@ function String_iterator() { } function StringIteratorNext() { - if (!IsObject(this) || !IsStringIterator(this)) { + var obj; + if (!IsObject(this) || (obj = GuardToStringIterator(this)) === null) { return callFunction(CallStringIteratorMethodIfWrapped, this, "StringIteratorNext"); } - var S = UnsafeGetStringFromReservedSlot(this, ITERATOR_SLOT_TARGET); + var S = UnsafeGetStringFromReservedSlot(obj, ITERATOR_SLOT_TARGET); // We know that JSString::MAX_LENGTH <= INT32_MAX (and assert this in // SelfHostring.cpp) so our current index can never be anything other than // an Int32Value. - var index = UnsafeGetInt32FromReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX); + var index = UnsafeGetInt32FromReservedSlot(obj, ITERATOR_SLOT_NEXT_INDEX); var size = S.length; var result = { value: undefined, done: false }; @@ -556,7 +557,7 @@ function StringIteratorNext() { } } - UnsafeSetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX, index + charCount); + UnsafeSetReservedSlot(obj, ITERATOR_SLOT_NEXT_INDEX, index + charCount); result.value = callFunction(String_substring, S, index, index + charCount); return result; diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 7b2f8214b..16d026092 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -11528,6 +11528,32 @@ CodeGenerator::visitHasClass(LHasClass* ins) masm.cmpPtrSet(Assembler::Equal, output, ImmPtr(ins->mir()->getClass()), output); } +void +CodeGenerator::visitGuardToClass(LGuardToClass* ins) +{ + Register lhs = ToRegister(ins->lhs()); + Register output = ToRegister(ins->output()); + Register temp = ToRegister(ins->temp()); + + Label notEqual; + + masm.branchTestObjClass(Assembler::NotEqual, lhs, temp, ins->mir()->getClass(), ¬Equal); + masm.mov(lhs, output); + + if (ins->mir()->type() == MIRType::Object) { + // Can't return null-return here, so bail + bailoutFrom(¬Equal, ins->snapshot()); + } else { + Label done; + masm.jump(&done); + + masm.bind(¬Equal); + masm.mov(ImmPtr(0), output); + + masm.bind(&done); + } +} + void CodeGenerator::visitWasmParameter(LWasmParameter* lir) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index d3126651b..b226f6cc9 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -377,6 +377,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitIsObject(LIsObject* lir); void visitIsObjectAndBranch(LIsObjectAndBranch* lir); void visitHasClass(LHasClass* lir); + void visitGuardToClass(LGuardToClass* lir); void visitWasmParameter(LWasmParameter* lir); void visitWasmParameterI64(LWasmParameterI64* lir); void visitWasmReturn(LWasmReturn* ret); diff --git a/js/src/jit/InlinableNatives.h b/js/src/jit/InlinableNatives.h index 89c2ff0a4..18535389a 100644 --- a/js/src/jit/InlinableNatives.h +++ b/js/src/jit/InlinableNatives.h @@ -117,14 +117,16 @@ _(IntrinsicDefineDataProperty) \ _(IntrinsicObjectHasPrototype) \ \ - _(IntrinsicIsArrayIterator) \ - _(IntrinsicIsMapIterator) \ - _(IntrinsicIsSetIterator) \ - _(IntrinsicIsStringIterator) \ + _(IntrinsicGuardToArrayIterator) \ + _(IntrinsicGuardToMapIterator) \ + _(IntrinsicGuardToSetIterator) \ _(IntrinsicIsListIterator) \ + _(IntrinsicGuardToStringIterator) \ \ + _(IntrinsicGuardToMapObject) \ _(IntrinsicGetNextMapEntryForIterator) \ \ + _(IntrinsicGuardToSetObject) \ _(IntrinsicGetNextSetEntryForIterator) \ \ _(IntrinsicArrayBufferByteLength) \ diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 35ad120f7..f24ef30c8 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -969,6 +969,7 @@ class IonBuilder const Class* clasp2 = nullptr, const Class* clasp3 = nullptr, const Class* clasp4 = nullptr); + InliningStatus inlineGuardToClass(CallInfo& callInfo, const Class* clasp); InliningStatus inlineIsConstructing(CallInfo& callInfo); InliningStatus inlineSubstringKernel(CallInfo& callInfo); InliningStatus inlineObjectHasPrototype(CallInfo& callInfo); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 730697163..709de9987 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -4170,6 +4170,16 @@ LIRGenerator::visitHasClass(MHasClass* ins) define(new(alloc()) LHasClass(useRegister(ins->object())), ins); } +void +LIRGenerator::visitGuardToClass(MGuardToClass* ins) +{ + MOZ_ASSERT(ins->object()->type() == MIRType::Object); + MOZ_ASSERT(ins->type() == MIRType::ObjectOrNull|| ins->type() == MIRType::Object); + LGuardToClass* lir = new(alloc()) LGuardToClass(useRegister(ins->object()), temp()); + assignSnapshot(lir, Bailout_TypeBarrierO); + define(lir, ins); +} + void LIRGenerator::visitWasmAddOffset(MWasmAddOffset* ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index b2805cb7a..9b4095aec 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -289,6 +289,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitIsConstructor(MIsConstructor* ins); void visitIsObject(MIsObject* ins); void visitHasClass(MHasClass* ins); + void visitGuardToClass(MGuardToClass* ins); void visitWasmAddOffset(MWasmAddOffset* ins); void visitWasmBoundsCheck(MWasmBoundsCheck* ins); void visitWasmLoadGlobalVar(MWasmLoadGlobalVar* ins); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 202aef497..01755094a 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -280,14 +280,14 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target) return inlineIsConstructing(callInfo); case InlinableNative::IntrinsicSubstringKernel: return inlineSubstringKernel(callInfo); - case InlinableNative::IntrinsicIsArrayIterator: - return inlineHasClass(callInfo, &ArrayIteratorObject::class_); - case InlinableNative::IntrinsicIsMapIterator: - return inlineHasClass(callInfo, &MapIteratorObject::class_); - case InlinableNative::IntrinsicIsSetIterator: - return inlineHasClass(callInfo, &SetIteratorObject::class_); - case InlinableNative::IntrinsicIsStringIterator: - return inlineHasClass(callInfo, &StringIteratorObject::class_); + case InlinableNative::IntrinsicGuardToArrayIterator: + return inlineGuardToClass(callInfo, &ArrayIteratorObject::class_); + case InlinableNative::IntrinsicGuardToMapIterator: + return inlineGuardToClass(callInfo, &MapIteratorObject::class_); + case InlinableNative::IntrinsicGuardToSetIterator: + return inlineGuardToClass(callInfo, &SetIteratorObject::class_); + case InlinableNative::IntrinsicGuardToStringIterator: + return inlineGuardToClass(callInfo, &StringIteratorObject::class_); case InlinableNative::IntrinsicIsListIterator: return inlineHasClass(callInfo, &ListIteratorObject::class_); case InlinableNative::IntrinsicDefineDataProperty: @@ -296,10 +296,14 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target) return inlineObjectHasPrototype(callInfo); // Map intrinsics. + case InlinableNative::IntrinsicGuardToMapObject: + return inlineGuardToClass(callInfo, &MapObject::class_); case InlinableNative::IntrinsicGetNextMapEntryForIterator: return inlineGetNextEntryForIterator(callInfo, MGetNextEntryForIterator::Map); // Set intrinsics. + case InlinableNative::IntrinsicGuardToSetObject: + return inlineGuardToClass(callInfo, &SetObject::class_); case InlinableNative::IntrinsicGetNextSetEntryForIterator: return inlineGetNextEntryForIterator(callInfo, MGetNextEntryForIterator::Set); @@ -2219,6 +2223,37 @@ IonBuilder::inlineHasClass(CallInfo& callInfo, return InliningStatus_Inlined; } +IonBuilder::InliningStatus +IonBuilder::inlineGuardToClass(CallInfo& callInfo, const Class* clasp) +{ + MOZ_ASSERT(!callInfo.constructing()); + MOZ_ASSERT(callInfo.argc() == 1); + + if (callInfo.getArg(0)->type() != MIRType::Object) + return InliningStatus_NotInlined; + + if (getInlineReturnType() != MIRType::ObjectOrNull && + getInlineReturnType() != MIRType::Object) + { + return InliningStatus_NotInlined; + } + + TemporaryTypeSet* types = callInfo.getArg(0)->resultTypeSet(); + const Class* knownClass = types ? types->getKnownClass(constraints()) : nullptr; + + if (knownClass && knownClass == clasp) { + current->push(callInfo.getArg(0)); + } else { + MGuardToClass* guardToClass = MGuardToClass::New(alloc(), callInfo.getArg(0), + clasp, getInlineReturnType()); + current->add(guardToClass); + current->push(guardToClass); + } + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + IonBuilder::InliningStatus IonBuilder::inlineGetNextEntryForIterator(CallInfo& callInfo, MGetNextEntryForIterator::Mode mode) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 2de91e2df..6ec05af76 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -13192,6 +13192,48 @@ class MHasClass } }; +class MGuardToClass + : public MUnaryInstruction, + public SingleObjectPolicy::Data +{ + const Class* class_; + + MGuardToClass(MDefinition* object, const Class* clasp, MIRType resultType) + : MUnaryInstruction(object) + , class_(clasp) + { + MOZ_ASSERT(object->type() == MIRType::Object || + (object->type() == MIRType::Value && object->mightBeType(MIRType::Object))); + MOZ_ASSERT(resultType == MIRType::Object || resultType == MIRType::ObjectOrNull); + setResultType(resultType); + setMovable(); + if (resultType == MIRType::Object) { + // We will bail out if the class type is incorrect, + // so we need to ensure we don't eliminate this instruction + setGuard(); + } + } + + public: + INSTRUCTION_HEADER(GuardToClass) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, object)) + + const Class* getClass() const { + return class_; + } + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + bool congruentTo(const MDefinition* ins) const override { + if (!ins->isGuardToClass()) + return false; + if (getClass() != ins->toGuardToClass()->getClass()) + return false; + return congruentIfOperandsEqual(ins); + } +}; + class MCheckReturn : public MBinaryInstruction, public BoxInputsPolicy::Data diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index bb2ab8190..fddc1e637 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -272,6 +272,7 @@ namespace jit { _(IsCallable) \ _(IsObject) \ _(HasClass) \ + _(GuardToClass) \ _(CopySign) \ _(Rotate) \ _(NewDerivedTypedObject) \ diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index 9dcb527c5..f4adcc63c 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -7867,6 +7867,29 @@ class LHasClass : public LInstructionHelper<1, 1, 0> } }; +class LGuardToClass : public LInstructionHelper<1, 1, 1> +{ + public: + LIR_HEADER(GuardToClass); + explicit LGuardToClass(const LAllocation& lhs, const LDefinition& temp) + { + setOperand(0, lhs); + setTemp(0, temp); + } + + const LAllocation* lhs() { + return getOperand(0); + } + + const LDefinition* temp() { + return getTemp(0); + } + + MGuardToClass* mir() const { + return mir_->toGuardToClass(); + } +}; + template class LWasmSelectBase : public LInstructionHelper { diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index 3eea1b449..fe2ab5ea3 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -386,6 +386,7 @@ _(IsObject) \ _(IsObjectAndBranch) \ _(HasClass) \ + _(GuardToClass) \ _(RecompileCheck) \ _(MemoryBarrier) \ _(AssertRangeI) \ diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 653807ce8..08670c833 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -189,6 +189,22 @@ intrinsic_IsInstanceOfBuiltin(JSContext* cx, unsigned argc, Value* vp) return true; } +template +static bool +intrinsic_GuardToBuiltin(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isObject()); + + if (args[0].toObject().is()) { + args.rval().setObject(args[0].toObject()); + return true; + } + args.rval().setNull(); + return true; +} + /** * Self-hosting intrinsic returning the original constructor for a builtin * the name of which is the first and only argument. @@ -2297,18 +2313,18 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("_SetCanonicalName", intrinsic_SetCanonicalName, 2,0), - JS_INLINABLE_FN("IsArrayIterator", - intrinsic_IsInstanceOfBuiltin, 1,0, - IntrinsicIsArrayIterator), - JS_INLINABLE_FN("IsMapIterator", - intrinsic_IsInstanceOfBuiltin, 1,0, - IntrinsicIsMapIterator), - JS_INLINABLE_FN("IsSetIterator", - intrinsic_IsInstanceOfBuiltin, 1,0, - IntrinsicIsSetIterator), - JS_INLINABLE_FN("IsStringIterator", - intrinsic_IsInstanceOfBuiltin, 1,0, - IntrinsicIsStringIterator), + JS_INLINABLE_FN("GuardToArrayIterator", + intrinsic_GuardToBuiltin, 1,0, + IntrinsicGuardToArrayIterator), + JS_INLINABLE_FN("GuardToMapIterator", + intrinsic_GuardToBuiltin, 1,0, + IntrinsicGuardToMapIterator), + JS_INLINABLE_FN("GuardToSetIterator", + intrinsic_GuardToBuiltin, 1,0, + IntrinsicGuardToSetIterator), + JS_INLINABLE_FN("GuardToStringIterator", + intrinsic_GuardToBuiltin, 1,0, + IntrinsicGuardToStringIterator), JS_INLINABLE_FN("IsListIterator", intrinsic_IsInstanceOfBuiltin, 1,0, IntrinsicIsListIterator), @@ -2412,7 +2428,12 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("CallStarGeneratorMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), + JS_INLINABLE_FN("GuardToMapObject", intrinsic_GuardToBuiltin, 1, 0, + IntrinsicGuardToMapObject), JS_FN("IsWeakSet", intrinsic_IsInstanceOfBuiltin, 1,0), + + JS_INLINABLE_FN("GuardToSetObject", intrinsic_GuardToBuiltin, 1, 0, + IntrinsicGuardToSetObject), JS_FN("CallWeakSetMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), -- cgit v1.2.3 From 0e550f2fb90ada0b608bc1e1982b100291651806 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 2 May 2018 11:07:35 -0700 Subject: Refactor structured clone JSAPI to prevent mismatched scopes. Roll-up of bugs 1442722, 1455071, 1433642, 1456604 and 1458320. --- js/public/StructuredClone.h | 207 +++++++++-- js/src/builtin/TestingFunctions.cpp | 30 +- js/src/tests/js1_8_5/extensions/clone-errors.js | 1 + .../js1_8_5/extensions/clone-transferables.js | 12 +- js/src/vm/StructuredClone.cpp | 389 ++++++++++----------- 5 files changed, 385 insertions(+), 254 deletions(-) (limited to 'js') diff --git a/js/public/StructuredClone.h b/js/public/StructuredClone.h index c48975cb9..ebff84387 100644 --- a/js/public/StructuredClone.h +++ b/js/public/StructuredClone.h @@ -9,6 +9,7 @@ #include "mozilla/Attributes.h" #include "mozilla/BufferList.h" +#include "mozilla/Move.h" #include @@ -29,7 +30,35 @@ namespace JS { enum class StructuredCloneScope : uint32_t { SameProcessSameThread, SameProcessDifferentThread, - DifferentProcess + + /** + * When writing, this means we're writing for an audience in a different + * process. Produce serialized data that can be sent to other processes, + * bitwise copied, or even stored as bytes in a database and read by later + * versions of Firefox years from now. The HTML5 spec refers to this as + * "ForStorage" as in StructuredSerializeForStorage, though we use + * DifferentProcess for IPC as well as storage. + * + * Transferable objects are limited to ArrayBuffers, whose contents are + * copied into the serialized data (rather than just writing a pointer). + */ + DifferentProcess, + + /** + * Handle a backwards-compatibility case with IndexedDB (bug 1434308): when + * reading, this means to treat legacy SameProcessSameThread data as if it + * were DifferentProcess. + * + * Do not use this for writing; use DifferentProcess instead. + */ + DifferentProcessForIndexedDB, + + /** + * Existing code wants to be able to create an uninitialized + * JSStructuredCloneData without knowing the scope, then populate it with + * data (at which point the scope *is* known.) + */ + Unassigned }; enum TransferableOwnership { @@ -89,6 +118,10 @@ class CloneDataPolicy } /* namespace JS */ +namespace js { +template struct BufferIterator; +} + /** * Read structured data from the reader r. This hook is used to read a value * previously serialized by a call to the WriteStructuredCloneOp hook. @@ -188,49 +221,152 @@ enum OwnTransferablePolicy { NoTransferables }; -class MOZ_NON_MEMMOVABLE JS_PUBLIC_API(JSStructuredCloneData) : - public mozilla::BufferList -{ - typedef js::SystemAllocPolicy AllocPolicy; - typedef mozilla::BufferList BufferList; +/** + * JSStructuredCloneData represents structured clone data together with the + * information needed to read/write/transfer/free the records within it, in the + * form of a set of callbacks. + */ +class MOZ_NON_MEMMOVABLE JS_PUBLIC_API(JSStructuredCloneData) { + public: + using BufferList = mozilla::BufferList; + using Iterator = BufferList::IterImpl; - static const size_t kInitialSize = 0; - static const size_t kInitialCapacity = 4096; + private: static const size_t kStandardCapacity = 4096; + BufferList bufList_; + + // The (address space, thread) scope within which this clone is valid. Note + // that this must be either set during construction, or start out as + // Unassigned and transition once to something else. + JS::StructuredCloneScope scope_; + const JSStructuredCloneCallbacks* callbacks_; void* closure_; OwnTransferablePolicy ownTransferables_; - void setOptionalCallbacks(const JSStructuredCloneCallbacks* callbacks, - void* closure, - OwnTransferablePolicy policy) { - callbacks_ = callbacks; - closure_ = closure; - ownTransferables_ = policy; - } - friend struct JSStructuredCloneWriter; friend class JS_PUBLIC_API(JSAutoStructuredCloneBuffer); + template friend struct js::BufferIterator; -public: - explicit JSStructuredCloneData(AllocPolicy aAP = AllocPolicy()) - : BufferList(kInitialSize, kInitialCapacity, kStandardCapacity, aAP) + public: + // The constructor must be infallible but SystemAllocPolicy is not, so both + // the initial size and initial capacity of the BufferList must be zero. + explicit JSStructuredCloneData(JS::StructuredCloneScope aScope) + : bufList_(0, 0, kStandardCapacity, js::SystemAllocPolicy()) + , scope_(aScope) , callbacks_(nullptr) , closure_(nullptr) , ownTransferables_(OwnTransferablePolicy::NoTransferables) {} - MOZ_IMPLICIT JSStructuredCloneData(BufferList&& buffers) - : BufferList(Move(buffers)) + + // Steal the raw data from a BufferList. In this case, we don't know the + // scope and none of the callback info is assigned yet. + JSStructuredCloneData(BufferList&& buffers, JS::StructuredCloneScope aScope) + : bufList_(mozilla::Move(buffers)) + , scope_(aScope) , callbacks_(nullptr) , closure_(nullptr) , ownTransferables_(OwnTransferablePolicy::NoTransferables) {} + MOZ_IMPLICIT JSStructuredCloneData(BufferList&& buffers) + : JSStructuredCloneData(mozilla::Move(buffers), JS::StructuredCloneScope::Unassigned) + {} JSStructuredCloneData(JSStructuredCloneData&& other) = default; JSStructuredCloneData& operator=(JSStructuredCloneData&& other) = default; - ~JSStructuredCloneData(); + ~JSStructuredCloneData() { discardTransferables(); } + + void setCallbacks(const JSStructuredCloneCallbacks* callbacks, + void* closure, + OwnTransferablePolicy policy) + { + callbacks_ = callbacks; + closure_ = closure; + ownTransferables_ = policy; + } + + JS::StructuredCloneScope scope() const { return scope_; } + + void initScope(JS::StructuredCloneScope aScope) { + MOZ_ASSERT(Size() == 0, "initScope() of nonempty JSStructuredCloneData"); + if (scope_ != JS::StructuredCloneScope::Unassigned) + MOZ_ASSERT(scope_ == aScope, "Cannot change scope after it has been initialized"); + scope_ = aScope; + } + + size_t Size() const { return bufList_.Size(); } + + const Iterator Start() const { return bufList_.Iter(); } + + bool Advance(Iterator& iter, size_t distance) const { + return iter.AdvanceAcrossSegments(bufList_, distance); + } + + bool ReadBytes(Iterator& iter, char* buffer, size_t size) const { + return bufList_.ReadBytes(iter, buffer, size); + } + + // Append new data to the end of the buffer. + bool AppendBytes(const char* data, size_t size) { + MOZ_ASSERT(scope_ != JS::StructuredCloneScope::Unassigned); + return bufList_.WriteBytes(data, size); + } - using BufferList::BufferList; + // Update data stored within the existing buffer. There must be at least + // 'size' bytes between the position of 'iter' and the end of the buffer. + bool UpdateBytes(Iterator& iter, const char* data, size_t size) const { + MOZ_ASSERT(scope_ != JS::StructuredCloneScope::Unassigned); + while (size > 0) { + size_t remaining = iter.RemainingInSegment(); + size_t nbytes = std::min(remaining, size); + memcpy(iter.Data(), data, nbytes); + data += nbytes; + size -= nbytes; + iter.Advance(bufList_, nbytes); + } + return true; + } + + void Clear() { + discardTransferables(); + bufList_.Clear(); + } + + // Return a new read-only JSStructuredCloneData that "borrows" the contents + // of |this|. Its lifetime should not exceed the donor's. This is only + // allowed for DifferentProcess clones, so finalization of the borrowing + // clone will do nothing. + JSStructuredCloneData Borrow(Iterator& iter, size_t size, bool* success) const + { + MOZ_ASSERT(scope_ == JS::StructuredCloneScope::DifferentProcess); + return JSStructuredCloneData(bufList_.Borrow(iter, size, success), + scope_); + } + + // Iterate over all contained data, one BufferList segment's worth at a + // time, and invoke the given FunctionToApply with the data pointer and + // size. The function should return a bool value, and this loop will exit + // with false if the function ever returns false. + template + bool ForEachDataChunk(FunctionToApply&& function) const { + Iterator iter = bufList_.Iter(); + while (!iter.Done()) { + if (!function(iter.Data(), iter.RemainingInSegment())) + return false; + iter.Advance(bufList_, iter.RemainingInSegment()); + } + return true; + } + + // Append the entire contents of other's bufList_ to our own. + bool Append(const JSStructuredCloneData& other) { + MOZ_ASSERT(scope_ == other.scope_); + return other.ForEachDataChunk([&](const char* data, size_t size) { + return AppendBytes(data, size); + }); + } + + void discardTransferables(); }; /** Note: if the *data contains transferable objects, it can be read only once. */ @@ -254,18 +390,29 @@ JS_PUBLIC_API(bool) JS_StructuredClone(JSContext* cx, JS::HandleValue v, JS::MutableHandleValue vp, const JSStructuredCloneCallbacks* optionalCallbacks, void* closure); -/** RAII sugar for JS_WriteStructuredClone. */ +/** + * The C-style API calls to read and write structured clones are fragile -- + * they rely on the caller to properly handle ownership of the clone data, and + * the handling of the input data as well as the interpretation of the contents + * of the clone buffer are dependent on the callbacks passed in. If you + * serialize and deserialize with different callbacks, the results are + * questionable. + * + * JSAutoStructuredCloneBuffer wraps things up in an RAII class for data + * management, and uses the same callbacks for both writing and reading + * (serializing and deserializing). + */ class JS_PUBLIC_API(JSAutoStructuredCloneBuffer) { const JS::StructuredCloneScope scope_; JSStructuredCloneData data_; uint32_t version_; public: - JSAutoStructuredCloneBuffer(JS::StructuredCloneScope scope, + JSAutoStructuredCloneBuffer(JS::StructuredCloneScope aScope, const JSStructuredCloneCallbacks* callbacks, void* closure) - : scope_(scope), version_(JS_STRUCTURED_CLONE_VERSION) + : scope_(aScope), data_(aScope), version_(JS_STRUCTURED_CLONE_VERSION) { - data_.setOptionalCallbacks(callbacks, closure, OwnTransferablePolicy::NoTransferables); + data_.setCallbacks(callbacks, closure, OwnTransferablePolicy::NoTransferables); } JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer&& other); @@ -276,11 +423,9 @@ class JS_PUBLIC_API(JSAutoStructuredCloneBuffer) { JSStructuredCloneData& data() { return data_; } bool empty() const { return !data_.Size(); } - void clear(const JSStructuredCloneCallbacks* optionalCallbacks=nullptr, void* closure=nullptr); + void clear(); - /** Copy some memory. It will be automatically freed by the destructor. */ - bool copy(const JSStructuredCloneData& data, uint32_t version=JS_STRUCTURED_CLONE_VERSION, - const JSStructuredCloneCallbacks* callbacks=nullptr, void* closure=nullptr); + JS::StructuredCloneScope scope() const { return scope_; } /** * Adopt some memory. It will be automatically freed by the destructor. diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 00637a7a5..373b6c9ed 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -2088,7 +2088,7 @@ class CloneBufferObject : public NativeObject { Rooted obj(cx, Create(cx)); if (!obj) return nullptr; - auto data = js::MakeUnique(); + auto data = js::MakeUnique(buffer->scope()); if (!data) { ReportOutOfMemory(cx); return nullptr; @@ -2141,8 +2141,11 @@ class CloneBufferObject : public NativeObject { return false; size_t nbytes = JS_GetStringLength(args[0].toString()); MOZ_ASSERT(nbytes % sizeof(uint64_t) == 0); - auto buf = js::MakeUnique(nbytes, nbytes, nbytes); - js_memcpy(buf->Start(), str, nbytes); + auto buf = js::MakeUnique(JS::StructuredCloneScope::DifferentProcess); + if (!buf->AppendBytes(str, nbytes)) { + ReportOutOfMemory(cx); + return false; + } JS_free(cx, str); obj->setData(buf.release()); @@ -2186,7 +2189,7 @@ class CloneBufferObject : public NativeObject { ReportOutOfMemory(cx); return false; } - auto iter = obj->data()->Iter(); + auto iter = obj->data()->Start(); obj->data()->ReadBytes(iter, buffer.get(), size); JSString* str = JS_NewStringCopyN(cx, buffer.get(), size); if (!str) @@ -2244,6 +2247,8 @@ ParseCloneScope(JSContext* cx, HandleString str) scope.emplace(JS::StructuredCloneScope::SameProcessDifferentThread); else if (strcmp(scopeStr.ptr(), "DifferentProcess") == 0) scope.emplace(JS::StructuredCloneScope::DifferentProcess); + else if (strcmp(scopeStr.ptr(), "DifferentProcessForIndexedDB") == 0) + scope.emplace(JS::StructuredCloneScope::DifferentProcessForIndexedDB); return scope; } @@ -4370,19 +4375,22 @@ JS_FN_HELP("rejectPromise", RejectPromise, 2, 0, " clone buffer object. 'policy' may be an options hash. Valid keys:\n" " 'SharedArrayBuffer' - either 'allow' (the default) or 'deny'\n" " to specify whether SharedArrayBuffers may be serialized.\n" -"\n" -" 'scope' - SameProcessSameThread, SameProcessDifferentThread, or\n" -" DifferentProcess. Determines how some values will be serialized.\n" -" Clone buffers may only be deserialized with a compatible scope."), +" 'scope' - SameProcessSameThread, SameProcessDifferentThread,\n" +" DifferentProcess, or DifferentProcessForIndexedDB. Determines how some\n" +" values will be serialized. Clone buffers may only be deserialized with a\n" +" compatible scope. NOTE - For DifferentProcess/DifferentProcessForIndexedDB,\n" +" must also set SharedArrayBuffer:'deny' if data contains any shared memory\n" +" object."), JS_FN_HELP("deserialize", Deserialize, 1, 0, "deserialize(clonebuffer[, opts])", " Deserialize data generated by serialize. 'opts' is an options hash with one\n" " recognized key 'scope', which limits the clone buffers that are considered\n" " valid. Allowed values: 'SameProcessSameThread', 'SameProcessDifferentThread',\n" -" and 'DifferentProcess'. So for example, a DifferentProcess clone buffer\n" -" may be deserialized in any scope, but a SameProcessSameThread clone buffer\n" -" cannot be deserialized in a DifferentProcess scope."), +" 'DifferentProcess', and 'DifferentProcessForIndexedDB'. So for example, a\n" +" DifferentProcessForIndexedDB clone buffer may be deserialized in any scope, but\n" +" a SameProcessSameThread clone buffer cannot be deserialized in a\n" +" DifferentProcess scope."), JS_FN_HELP("detachArrayBuffer", DetachArrayBuffer, 1, 0, "detachArrayBuffer(buffer)", diff --git a/js/src/tests/js1_8_5/extensions/clone-errors.js b/js/src/tests/js1_8_5/extensions/clone-errors.js index f65578a06..d2ccea2e8 100644 --- a/js/src/tests/js1_8_5/extensions/clone-errors.js +++ b/js/src/tests/js1_8_5/extensions/clone-errors.js @@ -25,6 +25,7 @@ check({get x() { throw new Error("fail"); }}); // Mismatched scopes. for (let [write_scope, read_scope] of [['SameProcessSameThread', 'SameProcessDifferentThread'], ['SameProcessSameThread', 'DifferentProcess'], + ['SameProcessDifferentThread', 'DifferentProcessForIndexedDB'], ['SameProcessDifferentThread', 'DifferentProcess']]) { var ab = new ArrayBuffer(12); diff --git a/js/src/tests/js1_8_5/extensions/clone-transferables.js b/js/src/tests/js1_8_5/extensions/clone-transferables.js index 673684b95..9aad27208 100644 --- a/js/src/tests/js1_8_5/extensions/clone-transferables.js +++ b/js/src/tests/js1_8_5/extensions/clone-transferables.js @@ -3,11 +3,15 @@ // http://creativecommons.org/licenses/publicdomain/ function* buffer_options() { - for (var scope of ["SameProcessSameThread", "SameProcessDifferentThread", "DifferentProcess"]) { - for (var size of [0, 8, 16, 200, 1000, 4096, 8192, 65536]) { - yield { scope, size }; + for (var scope of ["SameProcessSameThread", + "SameProcessDifferentThread", + "DifferentProcess", + "DifferentProcessForIndexedDB"]) + { + for (var size of [0, 8, 16, 200, 1000, 4096, 8192, 65536]) { + yield { scope, size }; + } } - } } diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index 3a062c3b8..42e909000 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -160,16 +160,16 @@ template struct BufferIterator { typedef mozilla::BufferList BufferList; - explicit BufferIterator(BufferList& buffer) + explicit BufferIterator(const BufferList& buffer) : mBuffer(buffer) , mIter(buffer.Iter()) { JS_STATIC_ASSERT(8 % sizeof(T) == 0); } - BufferIterator(const BufferIterator& other) - : mBuffer(other.mBuffer) - , mIter(other.mIter) + explicit BufferIterator(const JSStructuredCloneData& data) + : mBuffer(data.bufList_) + , mIter(data.Start()) { } @@ -228,17 +228,26 @@ struct BufferIterator { return mIter.HasRoomFor(sizeof(T)); } - BufferList& mBuffer; + const BufferList& mBuffer; typename BufferList::IterImpl mIter; }; +// SCOutput provides an interface to write raw data -- eg uint64_ts, doubles, +// arrays of bytes -- into a structured clone data output stream. It also knows +// how to free any transferable data within that stream. +// +// Note that it contains a full JSStructuredCloneData object, which holds the +// callbacks necessary to read/write/transfer/free the data. For the purpose of +// this class, only the freeTransfer callback is relevant; the rest of the callbacks +// are used by the higher-level JSStructuredCloneWriter interface. struct SCOutput { public: - using Iter = BufferIterator; + using Iter = BufferIterator; - explicit SCOutput(JSContext* cx); + SCOutput(JSContext* cx, JS::StructuredCloneScope scope); JSContext* context() const { return cx; } + JS::StructuredCloneScope scope() const { return buf.scope(); } bool write(uint64_t u); bool writePair(uint32_t tag, uint32_t data); @@ -251,22 +260,25 @@ struct SCOutput { template bool writeArray(const T* p, size_t nbytes); - bool extractBuffer(JSStructuredCloneData* data); - void discardTransferables(const JSStructuredCloneCallbacks* cb, void* cbClosure); + void setCallbacks(const JSStructuredCloneCallbacks* callbacks, + void* closure, + OwnTransferablePolicy policy) + { + buf.setCallbacks(callbacks, closure, policy); + } + void extractBuffer(JSStructuredCloneData* data) { *data = Move(buf); } + void discardTransferables(); uint64_t tell() const { return buf.Size(); } uint64_t count() const { return buf.Size() / sizeof(uint64_t); } - Iter iter() { - return BufferIterator(buf); - } + Iter iter() { return Iter(buf); } size_t offset(Iter dest) { return dest - iter(); } - private: JSContext* cx; - mozilla::BufferList buf; + JSStructuredCloneData buf; }; class SCInput { @@ -356,13 +368,6 @@ struct JSStructuredCloneReader { // be valid cross-process.) JS::StructuredCloneScope allowedScope; - // The scope the buffer was generated for (what sort of buffer it is.) The - // scope is not just a permissions thing; it also affects the storage - // format (eg a Transferred ArrayBuffer can be stored as a pointer for - // SameProcessSameThread but must have its contents in the clone buffer for - // DifferentProcess.) - JS::StructuredCloneScope storedScope; - // Stack of objects with properties remaining to be read. AutoValueVector objs; @@ -386,13 +391,15 @@ struct JSStructuredCloneWriter { const JSStructuredCloneCallbacks* cb, void* cbClosure, const Value& tVal) - : out(cx), scope(scope), objs(out.context()), + : out(cx, scope), objs(out.context()), counts(out.context()), entries(out.context()), - memory(out.context()), callbacks(cb), - closure(cbClosure), transferable(out.context(), tVal), + memory(out.context()), + transferable(out.context(), tVal), transferableObjects(out.context(), GCHashSet(cx)), cloneDataPolicy(cloneDataPolicy) - {} + { + out.setCallbacks(cb, cbClosure, OwnTransferablePolicy::NoTransferables); + } ~JSStructuredCloneWriter(); @@ -408,17 +415,10 @@ struct JSStructuredCloneWriter { SCOutput& output() { return out; } - bool extractBuffer(JSStructuredCloneData* data) { - bool success = out.extractBuffer(data); - if (success) { - data->setOptionalCallbacks(callbacks, closure, - OwnTransferablePolicy::OwnsTransferablesIfAny); - } - return success; + void extractBuffer(JSStructuredCloneData* newData) { + out.extractBuffer(newData); } - JS::StructuredCloneScope cloneScope() const { return scope; } - private: JSStructuredCloneWriter() = delete; JSStructuredCloneWriter(const JSStructuredCloneWriter&) = delete; @@ -449,9 +449,6 @@ struct JSStructuredCloneWriter { SCOutput out; - // The (address space, thread) scope within which this clone is valid. - JS::StructuredCloneScope scope; - // Vector of objects with properties remaining to be written. // // NB: These can span multiple compartments, so the compartment must be @@ -477,12 +474,6 @@ struct JSStructuredCloneWriter { SystemAllocPolicy>; Rooted memory; - // The user defined callbacks that will be used for cloning. - const JSStructuredCloneCallbacks* callbacks; - - // Any value passed to JS_WriteStructuredClone. - void* closure; - // Set of transferable objects RootedValue transferable; Rooted> transferableObjects; @@ -542,7 +533,12 @@ WriteStructuredClone(JSContext* cx, HandleValue v, JSStructuredCloneData* bufp, const Value& transferable) { JSStructuredCloneWriter w(cx, scope, cloneDataPolicy, cb, cbClosure, transferable); - return w.init() && w.write(v) && w.extractBuffer(bufp); + if (!w.init()) + return false; + if (!w.write(v)) + return false; + w.extractBuffer(bufp); + return true; } bool @@ -555,91 +551,15 @@ ReadStructuredClone(JSContext* cx, JSStructuredCloneData& data, return r.read(vp); } -// If the given buffer contains Transferables, free them. Note that custom -// Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to -// delete their transferables. -template -static void -DiscardTransferables(mozilla::BufferList& buffer, - const JSStructuredCloneCallbacks* cb, void* cbClosure) -{ - auto point = BufferIterator(buffer); - if (point.done()) - return; // Empty buffer - - uint32_t tag, data; - MOZ_RELEASE_ASSERT(point.canPeek()); - SCInput::getPair(point.peek(), &tag, &data); - point.next(); - - if (tag == SCTAG_HEADER) { - if (point.done()) - return; - - MOZ_RELEASE_ASSERT(point.canPeek()); - SCInput::getPair(point.peek(), &tag, &data); - point.next(); - } - - if (tag != SCTAG_TRANSFER_MAP_HEADER) - return; - - if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) - return; - - // freeTransfer should not GC - JS::AutoSuppressGCAnalysis nogc; - - if (point.done()) - return; - - uint64_t numTransferables = NativeEndian::swapFromLittleEndian(point.peek()); - point.next(); - while (numTransferables--) { - if (!point.canPeek()) - return; - - uint32_t ownership; - SCInput::getPair(point.peek(), &tag, &ownership); - point.next(); - MOZ_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY); - if (!point.canPeek()) - return; - - void* content; - SCInput::getPtr(point.peek(), &content); - point.next(); - if (!point.canPeek()) - return; - - uint64_t extraData = NativeEndian::swapFromLittleEndian(point.peek()); - point.next(); - - if (ownership < JS::SCTAG_TMO_FIRST_OWNED) - continue; - - if (ownership == JS::SCTAG_TMO_ALLOC_DATA) { - js_free(content); - } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) { - JS_ReleaseMappedArrayBufferContents(content, extraData); - } else if (cb && cb->freeTransfer) { - cb->freeTransfer(tag, JS::TransferableOwnership(ownership), content, extraData, cbClosure); - } else { - MOZ_ASSERT(false, "unknown ownership"); - } - } -} - static bool StructuredCloneHasTransferObjects(const JSStructuredCloneData& data) { - auto iter = data.Iter(); - if (data.Size() < sizeof(uint64_t)) return false; uint64_t u; - data.ReadBytes(iter, reinterpret_cast(&u), sizeof(u)); + BufferIterator iter(data); + MOZ_ALWAYS_TRUE(iter.readBytes(reinterpret_cast(&u), sizeof(u))); uint32_t tag = uint32_t(u >> 32); return (tag == SCTAG_TRANSFER_MAP_HEADER); } @@ -650,7 +570,7 @@ SCInput::SCInput(JSContext* cx, JSStructuredCloneData& data) : cx(cx), point(data) { - static_assert(JSStructuredCloneData::kSegmentAlignment % 8 == 0, + static_assert(JSStructuredCloneData::BufferList::kSegmentAlignment % 8 == 0, "structured clone buffer reads should be aligned"); MOZ_ASSERT(data.Size() % 8 == 0); } @@ -812,9 +732,8 @@ SCInput::readPtr(void** p) return true; } -SCOutput::SCOutput(JSContext* cx) - : cx(cx) - , buf(0, 0, 4096, cx) +SCOutput::SCOutput(JSContext* cx, JS::StructuredCloneScope scope) + : cx(cx), buf(scope) { } @@ -822,7 +741,11 @@ bool SCOutput::write(uint64_t u) { uint64_t v = NativeEndian::swapToLittleEndian(u); - return buf.WriteBytes(reinterpret_cast(&v), sizeof(u)); + if (!buf.AppendBytes(reinterpret_cast(&v), sizeof(u))) { + ReportOutOfMemory(context()); + return false; + } + return true; } bool @@ -883,7 +806,7 @@ SCOutput::writeArray(const T* p, size_t nelems) for (size_t i = 0; i < nelems; i++) { T value = swapToLittleEndian(p[i]); - if (!buf.WriteBytes(reinterpret_cast(&value), sizeof(value))) + if (!buf.AppendBytes(reinterpret_cast(&value), sizeof(value))) return false; } @@ -892,7 +815,7 @@ SCOutput::writeArray(const T* p, size_t nelems) size_t padbytes = sizeof(uint64_t) * nwords - sizeof(T) * nelems; char zero = 0; for (size_t i = 0; i < padbytes; i++) { - if (!buf.WriteBytes(&zero, sizeof(zero))) + if (!buf.AppendBytes(&zero, sizeof(zero))) return false; } @@ -927,34 +850,101 @@ SCOutput::writePtr(const void* p) return write(reinterpret_cast(p)); } -bool -SCOutput::extractBuffer(JSStructuredCloneData* data) -{ - bool success; - mozilla::BufferList out = - buf.MoveFallible(&success); - if (!success) { - ReportOutOfMemory(cx); - return false; - } - *data = JSStructuredCloneData(Move(out)); - return true; -} - void -SCOutput::discardTransferables(const JSStructuredCloneCallbacks* cb, void* cbClosure) +SCOutput::discardTransferables() { - DiscardTransferables(buf, cb, cbClosure); + buf.discardTransferables(); } } /* namespace js */ -JSStructuredCloneData::~JSStructuredCloneData() + +// If the buffer contains Transferables, free them. Note that custom +// Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to +// delete their transferables. +void +JSStructuredCloneData::discardTransferables() { if (!Size()) return; - if (ownTransferables_ == OwnTransferablePolicy::OwnsTransferablesIfAny) - DiscardTransferables(*this, callbacks_, closure_); + + if (ownTransferables_ != OwnTransferablePolicy::OwnsTransferablesIfAny) + return; + + // DifferentProcess clones cannot contain pointers, so nothing needs to be + // released. + if (scope_ == JS::StructuredCloneScope::DifferentProcess) + return; + + FreeTransferStructuredCloneOp freeTransfer = nullptr; + if (callbacks_) + freeTransfer = callbacks_->freeTransfer; + + auto point = BufferIterator(*this); + if (point.done()) + return; // Empty buffer + + uint32_t tag, data; + MOZ_RELEASE_ASSERT(point.canPeek()); + SCInput::getPair(point.peek(), &tag, &data); + point.next(); + + if (tag == SCTAG_HEADER) { + if (point.done()) + return; + + MOZ_RELEASE_ASSERT(point.canPeek()); + SCInput::getPair(point.peek(), &tag, &data); + point.next(); + } + + if (tag != SCTAG_TRANSFER_MAP_HEADER) + return; + + if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) + return; + + // freeTransfer should not GC + JS::AutoSuppressGCAnalysis nogc; + + if (point.done()) + return; + + uint64_t numTransferables = NativeEndian::swapFromLittleEndian(point.peek()); + point.next(); + while (numTransferables--) { + if (!point.canPeek()) + return; + + uint32_t ownership; + SCInput::getPair(point.peek(), &tag, &ownership); + point.next(); + MOZ_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY); + if (!point.canPeek()) + return; + + void* content; + SCInput::getPtr(point.peek(), &content); + point.next(); + if (!point.canPeek()) + return; + + uint64_t extraData = NativeEndian::swapFromLittleEndian(point.peek()); + point.next(); + + if (ownership < JS::SCTAG_TMO_FIRST_OWNED) + continue; + + if (ownership == JS::SCTAG_TMO_ALLOC_DATA) { + js_free(content); + } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) { + JS_ReleaseMappedArrayBufferContents(content, extraData); + } else if (freeTransfer) { + freeTransfer(tag, JS::TransferableOwnership(ownership), content, extraData, closure_); + } else { + MOZ_ASSERT(false, "unknown ownership"); + } + } } JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX); @@ -962,9 +952,8 @@ JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX); JSStructuredCloneWriter::~JSStructuredCloneWriter() { // Free any transferable data left lying around in the buffer - if (out.count()) { - out.discardTransferables(callbacks, closure); - } + if (out.count()) + out.discardTransferables(); } bool @@ -1038,7 +1027,7 @@ JSStructuredCloneWriter::parseTransferable() bool JSStructuredCloneWriter::reportDataCloneError(uint32_t errorId) { - ReportDataCloneError(context(), callbacks, errorId); + ReportDataCloneError(context(), out.buf.callbacks_, errorId); return false; } @@ -1454,8 +1443,8 @@ JSStructuredCloneWriter::startWrite(HandleValue v) return traverseSavedFrame(obj); } - if (callbacks && callbacks->write) - return callbacks->write(context(), this, obj, closure); + if (out.buf.callbacks_ && out.buf.callbacks_->write) + return out.buf.callbacks_->write(context(), this, obj, out.buf.closure_); /* else fall through */ } @@ -1465,7 +1454,7 @@ JSStructuredCloneWriter::startWrite(HandleValue v) bool JSStructuredCloneWriter::writeHeader() { - return out.writePair(SCTAG_HEADER, (uint32_t)scope); + return out.writePair(SCTAG_HEADER, (uint32_t)output().scope()); } bool @@ -1523,6 +1512,7 @@ JSStructuredCloneWriter::transferOwnership() JSContext* cx = context(); RootedObject obj(cx); + JS::StructuredCloneScope scope = output().scope(); for (auto tr = transferableObjects.all(); !tr.empty(); tr.popFront()) { obj = tr.front(); @@ -1555,7 +1545,9 @@ JSStructuredCloneWriter::transferOwnership() return false; } - if (scope == JS::StructuredCloneScope::DifferentProcess) { + if (scope == JS::StructuredCloneScope::DifferentProcess || + scope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) + { // Write Transferred ArrayBuffers in DifferentProcess scope at // the end of the clone buffer, and store the offset within the // buffer to where the ArrayBuffer was written. Note that this @@ -1592,9 +1584,9 @@ JSStructuredCloneWriter::transferOwnership() extraData = nbytes; } } else { - if (!callbacks || !callbacks->writeTransfer) + if (!out.buf.callbacks_ || !out.buf.callbacks_->writeTransfer) return reportDataCloneError(JS_SCERR_TRANSFERABLE); - if (!callbacks->writeTransfer(cx, obj, closure, &tag, &ownership, &content, &extraData)) + if (!out.buf.callbacks_->writeTransfer(cx, obj, out.buf.closure_, &tag, &ownership, &content, &extraData)) return false; MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY); } @@ -2187,25 +2179,33 @@ JSStructuredCloneReader::readHeader() if (!in.getPair(&tag, &data)) return in.reportTruncated(); - if (tag != SCTAG_HEADER) { + JS::StructuredCloneScope storedScope; + if (tag == SCTAG_HEADER) { + MOZ_ALWAYS_TRUE(in.readPair(&tag, &data)); + storedScope = JS::StructuredCloneScope(data); + } else { // Old structured clone buffer. We must have read it from disk. - storedScope = JS::StructuredCloneScope::DifferentProcess; - return true; + storedScope = JS::StructuredCloneScope::DifferentProcessForIndexedDB; } - MOZ_ALWAYS_TRUE(in.readPair(&tag, &data)); - storedScope = JS::StructuredCloneScope(data); - - if (data != uint32_t(JS::StructuredCloneScope::SameProcessSameThread) && - data != uint32_t(JS::StructuredCloneScope::SameProcessDifferentThread) && - data != uint32_t(JS::StructuredCloneScope::DifferentProcess)) + if (storedScope < JS::StructuredCloneScope::SameProcessSameThread || + storedScope > JS::StructuredCloneScope::DifferentProcessForIndexedDB) { JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA, "invalid structured clone scope"); return false; } + + if (allowedScope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) { + // Bug 1434308 and bug 1458320 - the scopes stored in old IndexedDB + // clones are incorrect. Treat them as if they were DifferentProcess. + allowedScope = JS::StructuredCloneScope::DifferentProcess; + return true; + } + if (storedScope < allowedScope) { - JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA, + JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, + JSMSG_SC_BAD_SERIALIZED_DATA, "incompatible structured clone scope"); return false; } @@ -2249,10 +2249,14 @@ JSStructuredCloneReader::readTransferMap() return false; if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) { - if (storedScope == JS::StructuredCloneScope::DifferentProcess) { + if (allowedScope == JS::StructuredCloneScope::DifferentProcess || + allowedScope == JS::StructuredCloneScope::DifferentProcessForIndexedDB) + { // Transferred ArrayBuffers in a DifferentProcess clone buffer - // are treated as if they weren't Transferred at all. - continue; + // are treated as if they weren't Transferred at all. We should + // only see SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER. + ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE); + return false; } size_t nbytes = extraData; @@ -2586,7 +2590,7 @@ JS_StructuredClone(JSContext* cx, HandleValue value, MutableHandleValue vp, } JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer&& other) - : scope_(other.scope_) + : scope_(other.scope()), data_(other.scope()) { data_.ownTransferables_ = other.data_.ownTransferables_; other.steal(&data_, &version_, &data_.callbacks_, &data_.closure_); @@ -2604,45 +2608,14 @@ JSAutoStructuredCloneBuffer::operator=(JSAutoStructuredCloneBuffer&& other) } void -JSAutoStructuredCloneBuffer::clear(const JSStructuredCloneCallbacks* optionalCallbacks, - void* optionalClosure) +JSAutoStructuredCloneBuffer::clear() { - if (!data_.Size()) - return; - - const JSStructuredCloneCallbacks* callbacks = - optionalCallbacks ? optionalCallbacks : data_.callbacks_; - void* closure = optionalClosure ? optionalClosure : data_.closure_; - - if (data_.ownTransferables_ == OwnTransferablePolicy::OwnsTransferablesIfAny) - DiscardTransferables(data_, callbacks, closure); + data_.discardTransferables(); data_.ownTransferables_ = OwnTransferablePolicy::NoTransferables; data_.Clear(); version_ = 0; } -bool -JSAutoStructuredCloneBuffer::copy(const JSStructuredCloneData& srcData, uint32_t version, - const JSStructuredCloneCallbacks* callbacks, - void* closure) -{ - // transferable objects cannot be copied - if (StructuredCloneHasTransferObjects(srcData)) - return false; - - clear(); - - auto iter = srcData.Iter(); - while (!iter.Done()) { - data_.WriteBytes(iter.Data(), iter.RemainingInSegment()); - iter.Advance(srcData, iter.RemainingInSegment()); - } - - version_ = version; - data_.setOptionalCallbacks(callbacks, closure, OwnTransferablePolicy::NoTransferables); - return true; -} - void JSAutoStructuredCloneBuffer::adopt(JSStructuredCloneData&& data, uint32_t version, const JSStructuredCloneCallbacks* callbacks, @@ -2651,7 +2624,7 @@ JSAutoStructuredCloneBuffer::adopt(JSStructuredCloneData&& data, uint32_t versio clear(); data_ = Move(data); version_ = version; - data_.setOptionalCallbacks(callbacks, closure, OwnTransferablePolicy::OwnsTransferablesIfAny); + data_.setCallbacks(callbacks, closure, OwnTransferablePolicy::OwnsTransferablesIfAny); } void @@ -2668,7 +2641,7 @@ JSAutoStructuredCloneBuffer::steal(JSStructuredCloneData* data, uint32_t* versio *data = Move(data_); version_ = 0; - data_.setOptionalCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables); + data_.setCallbacks(nullptr, nullptr, OwnTransferablePolicy::NoTransferables); } bool @@ -2782,5 +2755,5 @@ JS_ObjectNotWritten(JSStructuredCloneWriter* w, HandleObject obj) JS_PUBLIC_API(JS::StructuredCloneScope) JS_GetStructuredCloneScope(JSStructuredCloneWriter* w) { - return w->cloneScope(); + return w->output().scope(); } -- cgit v1.2.3 From 240f52db8e97c9c65592771ddffb6efbdd210c15 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 7 Jun 2018 13:21:06 +0200 Subject: Reinstate string.prototype.contains() This adds a compatibility function aliased to string.prototype.includes(). --- js/src/jsstr.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'js') diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 01b407626..4151d012b 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -1534,7 +1534,7 @@ RopeMatch(JSContext* cx, JSRope* text, JSLinearString* pat, int* match) return true; } -/* ES6 draft rc4 21.1.3.7. */ +/* ES6 2015 ST 21.1.3.7 String.prototype.includes */ bool js::str_includes(JSContext* cx, unsigned argc, Value* vp) { @@ -1591,6 +1591,13 @@ js::str_includes(JSContext* cx, unsigned argc, Value* vp) return true; } +/* ES6 draft Date: Thu, 7 Jun 2018 15:41:21 +0200 Subject: Fix count of compacting update tasks started. --- js/src/jsgc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js') diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 45301dac8..fb10797d5 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -2276,7 +2276,7 @@ GCRuntime::updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, s for (size_t i = 0; i < bgTaskCount && !bgArenas.done(); i++) { bgTasks[i].emplace(rt, &bgArenas, lock); startTask(*bgTasks[i], gcstats::PHASE_COMPACT_UPDATE_CELLS, lock); - tasksStarted = i; + tasksStarted++; } } -- cgit v1.2.3 From c951c985c1738a951a0e851710cf6c355671afd1 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Thu, 10 May 2018 10:09:31 +0100 Subject: Bug 1465108 - Use function pointers rather than virtual run method for GC parallel tasks r=sfink a=abillings a=RyanVM --- js/src/gc/GCRuntime.h | 18 ++++++++-------- js/src/gc/Nursery.cpp | 8 +++---- js/src/gc/Statistics.h | 3 --- js/src/jsgc.cpp | 25 +++++++++++----------- js/src/jsgc.h | 51 ++++++++++++++++++++++++++++++++++++++------- js/src/vm/HelperThreads.cpp | 4 ++-- 6 files changed, 72 insertions(+), 37 deletions(-) (limited to 'js') diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index 19737c9ee..5c2576efd 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -73,7 +73,7 @@ class ChunkPool // Performs extra allocation off the main thread so that when memory is // required on the main thread it will already be available and waiting. -class BackgroundAllocTask : public GCParallelTask +class BackgroundAllocTask : public GCParallelTaskHelper { // Guarded by the GC lock. JSRuntime* runtime; @@ -85,12 +85,11 @@ class BackgroundAllocTask : public GCParallelTask BackgroundAllocTask(JSRuntime* rt, ChunkPool& pool); bool enabled() const { return enabled_; } - protected: - void run() override; + void run(); }; -// Search the provided Chunks for free arenas and decommit them. -class BackgroundDecommitTask : public GCParallelTask +// Search the provided Chunks for free arenas and recommit them. +class BackgroundDecommitTask : public GCParallelTaskHelper { public: using ChunkVector = mozilla::Vector; @@ -98,8 +97,7 @@ class BackgroundDecommitTask : public GCParallelTask explicit BackgroundDecommitTask(JSRuntime *rt) : runtime(rt) {} void setChunksToScan(ChunkVector &chunks); - protected: - void run() override; + void run(); private: JSRuntime* runtime; @@ -1171,8 +1169,10 @@ class GCRuntime /* * Concurrent sweep infrastructure. */ - void startTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked); - void joinTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked); + void startTask(GCParallelTask& task, gcstats::Phase phase, + AutoLockHelperThreadState& locked); + void joinTask(GCParallelTask& task, gcstats::Phase phase, + AutoLockHelperThreadState& locked); /* * List head of arenas allocated during the sweep phase. diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index aa50bf29e..55ca5a059 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -43,19 +43,19 @@ using mozilla::PodZero; static const uintptr_t CanaryMagicValue = 0xDEADB15D; -struct js::Nursery::FreeMallocedBuffersTask : public GCParallelTask +struct js::Nursery::FreeMallocedBuffersTask : public GCParallelTaskHelper { explicit FreeMallocedBuffersTask(FreeOp* fop) : fop_(fop) {} bool init() { return buffers_.init(); } void transferBuffersToFree(MallocedBuffersSet& buffersToFree, const AutoLockHelperThreadState& lock); - ~FreeMallocedBuffersTask() override { join(); } + ~FreeMallocedBuffersTask() { join(); } + + void run(); private: FreeOp* fop_; MallocedBuffersSet buffers_; - - virtual void run() override; }; struct js::Nursery::SweepAction diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index c9e5871e3..ca1969b2c 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -22,9 +22,6 @@ using mozilla::Maybe; namespace js { - -class GCParallelTask; - namespace gcstats { enum Phase : uint8_t { diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index fb10797d5..3d4dae9bb 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -2156,7 +2156,7 @@ ArenasToUpdate::getArenasToUpdate(AutoLockHelperThreadState& lock, unsigned maxL return { begin, last->next }; } -struct UpdatePointersTask : public GCParallelTask +struct UpdatePointersTask : public GCParallelTaskHelper { // Maximum number of arenas to update in one block. #ifdef DEBUG @@ -2172,14 +2172,13 @@ struct UpdatePointersTask : public GCParallelTask arenas_.end = nullptr; } - ~UpdatePointersTask() override { join(); } + void run(); private: JSRuntime* rt_; ArenasToUpdate* source_; ArenaListSegment arenas_; - virtual void run() override; bool getArenasToUpdate(); void updateArenas(); }; @@ -2985,7 +2984,6 @@ js::gc::BackgroundDecommitTask::run() AutoLockGC lock(runtime); for (Chunk* chunk : toDecommit) { - // The arena list is not doubly-linked, so we have to work in the free // list order and not in the natural order. while (chunk->info.numArenasFreeCommitted) { @@ -4359,7 +4357,8 @@ GCRuntime::endMarkingZoneGroup() marker.setMarkColorBlack(); } -class GCSweepTask : public GCParallelTask +template +class GCSweepTask : public GCParallelTaskHelper { GCSweepTask(const GCSweepTask&) = delete; @@ -4369,13 +4368,13 @@ class GCSweepTask : public GCParallelTask public: explicit GCSweepTask(JSRuntime* rt) : runtime(rt) {} GCSweepTask(GCSweepTask&& other) - : GCParallelTask(mozilla::Move(other)), + : GCParallelTaskHelper(mozilla::Move(other)), runtime(other.runtime) {} }; // Causes the given WeakCache to be swept when run. -class SweepWeakCacheTask : public GCSweepTask +class SweepWeakCacheTask : public GCSweepTask { JS::WeakCache& cache; @@ -4387,15 +4386,15 @@ class SweepWeakCacheTask : public GCSweepTask : GCSweepTask(mozilla::Move(other)), cache(other.cache) {} - void run() override { + void run() { cache.sweep(); } }; #define MAKE_GC_SWEEP_TASK(name) \ - class name : public GCSweepTask { \ - void run() override; \ + class name : public GCSweepTask { \ public: \ + void run(); \ explicit name (JSRuntime* rt) : GCSweepTask(rt) {} \ } MAKE_GC_SWEEP_TASK(SweepAtomsTask); @@ -4447,7 +4446,8 @@ SweepMiscTask::run() } void -GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked) +GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase, + AutoLockHelperThreadState& locked) { if (!task.startWithLockHeld(locked)) { AutoUnlockHelperThreadState unlock(locked); @@ -4457,7 +4457,8 @@ GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperT } void -GCRuntime::joinTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked) +GCRuntime::joinTask(GCParallelTask& task, gcstats::Phase phase, + AutoLockHelperThreadState& locked) { gcstats::AutoPhase ap(stats, task, phase); task.joinWithLockHeld(locked); diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 7ad176d84..d3cf31fe7 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -12,6 +12,7 @@ #include "mozilla/Atomics.h" #include "mozilla/EnumeratedArray.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" #include "mozilla/TypeTraits.h" #include "js/GCAPI.h" @@ -936,10 +937,19 @@ class GCHelperState }; // A generic task used to dispatch work to the helper thread system. -// Users should derive from GCParallelTask add what data they need and -// override |run|. +// Users supply a function pointer to call. +// +// Note that we don't use virtual functions here because destructors can write +// the vtable pointer on entry, which can causes races if synchronization +// happens there. class GCParallelTask { + public: + using TaskFunc = void (*)(GCParallelTask*); + + private: + TaskFunc func_; + // The state of the parallel computation. enum TaskState { NotStarted, @@ -956,19 +966,24 @@ class GCParallelTask // A flag to signal a request for early completion of the off-thread task. mozilla::Atomic cancel_; - virtual void run() = 0; - public: - GCParallelTask() : state(NotStarted), duration_(0) {} + explicit GCParallelTask(TaskFunc func) + : func_(func), + state(NotStarted), + duration_(0), + cancel_(false) + {} + GCParallelTask(GCParallelTask&& other) - : state(other.state), + : func_(other.func_), + state(other.state), duration_(0), cancel_(false) {} // Derived classes must override this to ensure that join() gets called // before members get destructed. - virtual ~GCParallelTask(); + ~GCParallelTask(); // Time spent in the most recent invocation of this task. int64_t duration() const { return duration_; } @@ -997,12 +1012,34 @@ class GCParallelTask bool isRunningWithLockHeld(const AutoLockHelperThreadState& locked) const; bool isRunning() const; + void runTask() { + func_(this); + } + // This should be friended to HelperThread, but cannot be because it // would introduce several circular dependencies. public: void runFromHelperThread(AutoLockHelperThreadState& locked); }; +// CRTP template to handle cast to derived type when calling run(). +template +class GCParallelTaskHelper : public GCParallelTask +{ + public: + GCParallelTaskHelper() + : GCParallelTask(&runTaskTyped) + {} + GCParallelTaskHelper(GCParallelTaskHelper&& other) + : GCParallelTask(mozilla::Move(other)) + {} + + private: + static void runTaskTyped(GCParallelTask* task) { + static_cast(task)->run(); + } +}; + typedef void (*IterateChunkCallback)(JSRuntime* rt, void* data, gc::Chunk* chunk); typedef void (*IterateZoneCallback)(JSRuntime* rt, void* data, JS::Zone* zone); typedef void (*IterateArenaCallback)(JSRuntime* rt, void* data, gc::Arena* arena, diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 7381a97b5..bd29d0c79 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -1144,7 +1144,7 @@ js::GCParallelTask::runFromMainThread(JSRuntime* rt) MOZ_ASSERT(state == NotStarted); MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(rt)); uint64_t timeStart = PRMJ_Now(); - run(); + runTask(); duration_ = PRMJ_Now() - timeStart; } @@ -1155,7 +1155,7 @@ js::GCParallelTask::runFromHelperThread(AutoLockHelperThreadState& locked) AutoUnlockHelperThreadState parallelSection(locked); gc::AutoSetThreadIsPerformingGC performingGC; uint64_t timeStart = PRMJ_Now(); - run(); + runTask(); duration_ = PRMJ_Now() - timeStart; } -- cgit v1.2.3 From f3657d6b4d4b30574a43a886bed6945590bf1508 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Thu, 31 May 2018 14:22:14 +0200 Subject: Bug 1464829 - Ensure the recover instruction vector has the expected size. --- js/src/jit/JitFrameIterator.h | 2 -- js/src/jit/JitFrames.cpp | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) (limited to 'js') diff --git a/js/src/jit/JitFrameIterator.h b/js/src/jit/JitFrameIterator.h index ba5efef6a..3620badbd 100644 --- a/js/src/jit/JitFrameIterator.h +++ b/js/src/jit/JitFrameIterator.h @@ -322,9 +322,7 @@ class RInstructionResults MOZ_MUST_USE bool init(JSContext* cx, uint32_t numResults); bool isInitialized() const; -#ifdef DEBUG size_t length() const; -#endif JitFrameLayout* frame() const; diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index f11f17225..019be46dd 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -1688,13 +1688,11 @@ RInstructionResults::isInitialized() const return initialized_; } -#ifdef DEBUG size_t RInstructionResults::length() const { return results_->length(); } -#endif JitFrameLayout* RInstructionResults::frame() const @@ -2150,7 +2148,7 @@ SnapshotIterator::initInstructionResults(MaybeReadFallback& fallback) } MOZ_ASSERT(results->isInitialized()); - MOZ_ASSERT(results->length() == recover_.numInstructions() - 1); + MOZ_RELEASE_ASSERT(results->length() == recover_.numInstructions() - 1); instructionResults_ = results; return true; } -- cgit v1.2.3 From 9ee07e9b8894de3aec81689dba5dbc0fc025bb83 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Mon, 2 Jul 2018 09:46:06 +0200 Subject: Issue #578: Applications cannot start without /proc (chroot). UXP uses the current stack frame address and the stack size as a sort of heuristic for various things in the JavaScript engine. The js::GetNativeStackBaseImpl() function is used to get the base stack address (i.e. the address from which the stack grows, so this can be either the first or last memory address of the stack memory space depending on the CPU architecture). On Linux, this function is implemented using the pthreads APIs. For non-main threads, the queried thread info is stored in memory. The main thread does not have this information on hand, so it gets the stack memory range via the /proc/self/maps file (see glibc's pthread_get_attr_np.c). Fortunately (per discussions with the firefox devs in #jsapi) the base address only needs to be approximate. In reality, environment variables, args, and other things are stored in stack space between the end/beginning of the mapped stack memory and the 'top' of the stack space used by stack frames. When using glibc, we can get the top of this usable stack from __libc_stack_end, which is a void* set by glibc during program initialization, avoiding the need to access /proc. Non-main threads still get their stack-base through the usual pthreads APIs. Other libc implementations like musl will fall back to the standard UNIX-like implementation which calls pthread's pthread_attr_getstack() also from the main thread, which may imply /proc access and not work in restricted environments. --- js/src/jsnativestack.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 3 deletions(-) (limited to 'js') diff --git a/js/src/jsnativestack.cpp b/js/src/jsnativestack.cpp index 05928ea3d..166a5a4f7 100644 --- a/js/src/jsnativestack.cpp +++ b/js/src/jsnativestack.cpp @@ -21,6 +21,18 @@ # include # endif +# if defined(XP_LINUX) && !defined(ANDROID) && defined(__GLIBC__) +# include +# include +# include +# include +static pid_t +gettid() +{ + return syscall(__NR_gettid); +} +# endif + #else # error "Unsupported platform" @@ -88,6 +100,52 @@ js::GetNativeStackBaseImpl() context.uc_stack.ss_size; } +#elif defined(XP_LINUX) && !defined(ANDROID) && defined(__GLIBC__) +void* +js::GetNativeStackBaseImpl() +{ + // On the main thread, get stack base from glibc's __libc_stack_end rather than pthread APIs + // to avoid filesystem calls /proc/self/maps. Non-main threads spawned with pthreads can read + // this information directly from their pthread struct, but when using the pthreads API, the + // main thread must go parse /proc/self/maps to figure the mapped stack address space ranges. + // We want to avoid reading from /proc/ so that the application can run in restricted + // environments where /proc may not be mounted (e.g. chroot). + if (gettid() == getpid()) { + void** pLibcStackEnd = (void**)dlsym(RTLD_DEFAULT, "__libc_stack_end"); + + // If __libc_stack_end is not found, architecture specific frame pointer hopping will need + // to be implemented. + MOZ_RELEASE_ASSERT(pLibcStackEnd, "__libc_stack_end unavailable, unable to setup stack range for JS."); + void* stackBase = *pLibcStackEnd; + MOZ_RELEASE_ASSERT(stackBase, "Invalid stack base, unable to setup stack range for JS."); + + // We don't need to fix stackBase, as it already roughly points to beginning of the stack. + return stackBase; + } + + // Non-main threads have the required info stored in memory, so no filesystem calls are made. + pthread_t thread = pthread_self(); + pthread_attr_t sattr; + pthread_attr_init(&sattr); + pthread_getattr_np(thread, &sattr); + + // stackBase will be the *lowest* address on all architectures. + void* stackBase = nullptr; + size_t stackSize = 0; + int rc = pthread_attr_getstack(&sattr, &stackBase, &stackSize); + if (rc) { + MOZ_CRASH("Call to pthread_attr_getstack failed, unable to setup stack range for JS."); + } + MOZ_RELEASE_ASSERT(stackBase, "Invalid stack base, unable to setup stack range for JS."); + pthread_attr_destroy(&sattr); + +# if JS_STACK_GROWTH_DIRECTION > 0 + return stackBase; +# else + return static_cast(stackBase) + stackSize; +# endif +} + #else /* XP_UNIX */ void* @@ -156,11 +214,15 @@ js::GetNativeStackBaseImpl() // the truth. rc = pthread_attr_getstack(&sattr, &stackBase, &stackSize); # else + // Use the default pthread_attr_getstack() call. Note that this function + // differs between libc implementations and could imply /proc access etc. + // which may not work in restricted environments. rc = pthread_attr_getstack(&sattr, &stackBase, &stackSize); # endif - if (rc) - MOZ_CRASH(); - MOZ_ASSERT(stackBase); + if (rc) { + MOZ_CRASH("Call to pthread_attr_getstack failed, unable to setup stack range for JS."); + } + MOZ_RELEASE_ASSERT(stackBase, "Invalid stack base, unable to setup stack range for JS."); pthread_attr_destroy(&sattr); # if JS_STACK_GROWTH_DIRECTION > 0 -- cgit v1.2.3 From 71a1c47759f30f114ad4e5634ba5224744b7c18c Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Thu, 12 Jul 2018 12:34:59 +0200 Subject: Fix Build Bustage - with "--enable-debug" --- js/src/vm/EnvironmentObject.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'js') diff --git a/js/src/vm/EnvironmentObject.h b/js/src/vm/EnvironmentObject.h index d457ca839..032286116 100644 --- a/js/src/vm/EnvironmentObject.h +++ b/js/src/vm/EnvironmentObject.h @@ -930,6 +930,9 @@ class DebugEnvironments void mark(JSTracer* trc); void sweep(JSRuntime* rt); void finish(); +#ifdef JSGC_HASH_TABLE_CHECKS + void checkHashTablesAfterMovingGC(JSRuntime* runtime); +#endif // If a live frame has a synthesized entry in missingEnvs, make sure it's not // collected. -- cgit v1.2.3 From 4103fc0a8a054ba6126d2bbcc797219f8b2ebf2e Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Fri, 13 Jul 2018 06:11:55 +0200 Subject: Remove recover instruction results after bailouts. --- js/src/jit/BaselineBailouts.cpp | 56 ++++++++++++++--------------------------- js/src/jit/JitFrameIterator.h | 2 +- 2 files changed, 20 insertions(+), 38 deletions(-) (limited to 'js') diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 3ab722b3d..ad2757ae1 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -419,41 +419,6 @@ struct BaselineStackBuilder } }; -// Ensure that all value locations are readable from the SnapshotIterator. -// Remove RInstructionResults from the JitActivation if the frame got recovered -// ahead of the bailout. -class SnapshotIteratorForBailout : public SnapshotIterator -{ - JitActivation* activation_; - JitFrameIterator& iter_; - - public: - SnapshotIteratorForBailout(JitActivation* activation, JitFrameIterator& iter) - : SnapshotIterator(iter, activation->bailoutData()->machineState()), - activation_(activation), - iter_(iter) - { - MOZ_ASSERT(iter.isBailoutJS()); - } - - ~SnapshotIteratorForBailout() { - // The bailout is complete, we no longer need the recover instruction - // results. - activation_->removeIonFrameRecovery(fp_); - } - - // Take previously computed result out of the activation, or compute the - // results of all recover instructions contained in the snapshot. - MOZ_MUST_USE bool init(JSContext* cx) { - - // Under a bailout, there is no need to invalidate the frame after - // evaluating the recover instruction, as the invalidation is only - // needed to cause of the frame which has been introspected. - MaybeReadFallback recoverBailout(cx, activation_, &iter_, MaybeReadFallback::Fallback_DoNothing); - return initInstructionResults(recoverBailout); - } -}; - #ifdef DEBUG static inline bool IsInlinableFallback(ICFallbackStub* icEntry) @@ -1476,6 +1441,7 @@ jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation, JitFrameIter { MOZ_ASSERT(bailoutInfo != nullptr); MOZ_ASSERT(*bailoutInfo == nullptr); + MOZ_ASSERT(iter.isBailoutJS()); TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); TraceLogStopEvent(logger, TraceLogger_IonMonkey); @@ -1488,6 +1454,12 @@ jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation, JitFrameIter activation->removeRematerializedFramesFromDebugger(cx, iter.fp()); }); + // Always remove the RInstructionResults from the JitActivation, even in + // case of failures as the stack frame is going away after the bailout. + auto removeIonFrameRecovery = mozilla::MakeScopeExit([&] { + activation->removeIonFrameRecovery(iter.jsFrame()); + }); + // The caller of the top frame must be one of the following: // IonJS - Ion calling into Ion. // BaselineStub - Baseline calling into Ion. @@ -1561,9 +1533,19 @@ jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation, JitFrameIter } JitSpew(JitSpew_BaselineBailouts, " Incoming frame ptr = %p", builder.startFrame()); - SnapshotIteratorForBailout snapIter(activation, iter); - if (!snapIter.init(cx)) + // Under a bailout, there is no need to invalidate the frame after + // evaluating the recover instruction, as the invalidation is only needed in + // cases where the frame is introspected ahead of the bailout. + MaybeReadFallback recoverBailout(cx, activation, &iter, MaybeReadFallback::Fallback_DoNothing); + + // Ensure that all value locations are readable from the SnapshotIterator. + // Get the RInstructionResults from the JitActivation if the frame got + // recovered ahead of the bailout. + SnapshotIterator snapIter(iter, activation->bailoutData()->machineState()); + if (!snapIter.initInstructionResults(recoverBailout)) { + ReportOutOfMemory(cx); return BAILOUT_RETURN_FATAL_ERROR; + } #ifdef TRACK_SNAPSHOTS snapIter.spewBailingFrom(); diff --git a/js/src/jit/JitFrameIterator.h b/js/src/jit/JitFrameIterator.h index 3620badbd..76d04d092 100644 --- a/js/src/jit/JitFrameIterator.h +++ b/js/src/jit/JitFrameIterator.h @@ -509,13 +509,13 @@ class SnapshotIterator return recover_.moreInstructions(); } - protected: // Register a vector used for storing the results of the evaluation of // recover instructions. This vector should be registered before the // beginning of the iteration. This function is in charge of allocating // enough space for all instructions results, and return false iff it fails. MOZ_MUST_USE bool initInstructionResults(MaybeReadFallback& fallback); + protected: // This function is used internally for computing the result of the recover // instructions. MOZ_MUST_USE bool computeInstructionResults(JSContext* cx, RInstructionResults* results) const; -- cgit v1.2.3