diff --git a/lib/Backend/BackwardPass.cpp b/lib/Backend/BackwardPass.cpp index f5f25a90a46..3c1652c87fa 100644 --- a/lib/Backend/BackwardPass.cpp +++ b/lib/Backend/BackwardPass.cpp @@ -2535,6 +2535,8 @@ BackwardPass::ProcessBlock(BasicBlock * block) } #endif + AssertOrFailFastMsg(!instr->IsLowered(), "Lowered instruction detected in pre-lower context!"); + this->currentInstr = instr; this->currentRegion = this->currentBlock->GetFirstInstr()->AsLabelInstr()->GetRegion(); diff --git a/lib/Backend/GlobOptFields.cpp b/lib/Backend/GlobOptFields.cpp index c605ae83225..6e85ba6fd88 100644 --- a/lib/Backend/GlobOptFields.cpp +++ b/lib/Backend/GlobOptFields.cpp @@ -3194,8 +3194,6 @@ GlobOpt::UpdateObjPtrValueType(IR::Opnd * opnd, IR::Instr * instr) } break; case Js::TypeIds_Array: - case Js::TypeIds_NativeFloatArray: - case Js::TypeIds_NativeIntArray: // Because array can change type id, we can only make it definite if we are doing array check hoist // so that implicit call will be installed between the array checks. if (!DoArrayCheckHoist() || diff --git a/lib/Backend/IR.h b/lib/Backend/IR.h index e7ec6e36f07..3adb0844363 100644 --- a/lib/Backend/IR.h +++ b/lib/Backend/IR.h @@ -488,6 +488,7 @@ class Instr bool dstIsAlwaysConvertedToNumber : 1; bool isCallInstrProtectedByNoProfileBailout : 1; bool hasSideEffects : 1; // The instruction cannot be dead stored + bool isNonFastPathFrameDisplay : 1; protected: bool isCloned:1; bool hasBailOutInfo:1; diff --git a/lib/Backend/IRBuilder.cpp b/lib/Backend/IRBuilder.cpp index 817965c6033..2e898452346 100644 --- a/lib/Backend/IRBuilder.cpp +++ b/lib/Backend/IRBuilder.cpp @@ -704,7 +704,7 @@ IRBuilder::Build() } } #endif - AssertMsg(Js::OpCodeUtil::IsValidByteCodeOpcode(newOpcode), "Error getting opcode from m_jnReader.Op()"); + AssertOrFailFastMsg(Js::OpCodeUtil::IsValidByteCodeOpcode(newOpcode), "Error getting opcode from m_jnReader.Op()"); uint layoutAndSize = layoutSize * Js::OpLayoutType::Count + Js::OpCodeUtil::GetOpCodeLayout(newOpcode); switch(layoutAndSize) @@ -6824,22 +6824,16 @@ IRBuilder::BuildEmpty(Js::OpCode newOpcode, uint32 offset) Js::Constants::NoByteCodeOffset); } - IR::RegOpnd* tempRegOpnd = IR::RegOpnd::New(StackSym::New(this->m_func), TyVar, this->m_func); + IR::Instr* lfd = IR::Instr::New( + Js::OpCode::LdFrameDisplay, + this->BuildDstOpnd(this->m_func->GetJITFunctionBody()->GetLocalFrameDisplayReg()), + this->BuildDstOpnd(this->m_func->GetJITFunctionBody()->GetLocalClosureReg()), + this->BuildDstOpnd(this->m_func->GetJITFunctionBody()->GetLocalFrameDisplayReg()), + this->m_func); this->AddInstr( - IR::Instr::New( - Js::OpCode::LdFrameDisplay, - tempRegOpnd, - this->BuildSrcOpnd(this->m_func->GetJITFunctionBody()->GetLocalClosureReg()), - this->BuildSrcOpnd(this->m_func->GetJITFunctionBody()->GetLocalFrameDisplayReg()), - this->m_func), - Js::Constants::NoByteCodeOffset); - this->AddInstr( - IR::Instr::New( - Js::OpCode::MOV, - this->BuildDstOpnd(this->m_func->GetJITFunctionBody()->GetLocalFrameDisplayReg()), - tempRegOpnd, - this->m_func), + lfd, Js::Constants::NoByteCodeOffset); + lfd->isNonFastPathFrameDisplay = true; } break; diff --git a/lib/Backend/IRBuilderAsmJs.cpp b/lib/Backend/IRBuilderAsmJs.cpp index 9184d442e74..96a425cbfcd 100644 --- a/lib/Backend/IRBuilderAsmJs.cpp +++ b/lib/Backend/IRBuilderAsmJs.cpp @@ -144,7 +144,7 @@ IRBuilderAsmJs::Build() { Assert(newOpcode != Js::OpCodeAsmJs::EndOfBlock); - AssertMsg(Js::OpCodeUtilAsmJs::IsValidByteCodeOpcode(newOpcode), "Error getting opcode from m_jnReader.Op()"); + AssertOrFailFastMsg(Js::OpCodeUtilAsmJs::IsValidByteCodeOpcode(newOpcode), "Error getting opcode from m_jnReader.Op()"); uint layoutAndSize = layoutSize * Js::OpLayoutTypeAsmJs::Count + Js::OpCodeUtilAsmJs::GetOpCodeLayout(newOpcode); switch (layoutAndSize) diff --git a/lib/Backend/Lower.cpp b/lib/Backend/Lower.cpp index 3f52775e09d..3ef17f7059f 100644 --- a/lib/Backend/Lower.cpp +++ b/lib/Backend/Lower.cpp @@ -12956,13 +12956,13 @@ void Lowerer::LowerBoundCheck(IR::Instr *const instr) // jo $bailOut // $bailOut: (insertBeforeInstr) Assert(!offsetOpnd || offsetOpnd->GetValue() == offset); - IR::RegOpnd *const addResultOpnd = IR::RegOpnd::New(TyMachReg, func); + IR::RegOpnd *const addResultOpnd = IR::RegOpnd::New(TyInt32, func); autoReuseAddResultOpnd.Initialize(addResultOpnd, func); InsertAdd( true, addResultOpnd, rightOpnd, - offsetOpnd ? offsetOpnd->UseWithNewType(TyMachReg, func) : IR::IntConstOpnd::New(offset, TyMachReg, func, true), + offsetOpnd ? offsetOpnd->UseWithNewType(TyInt32, func) : IR::IntConstOpnd::New(offset, TyInt32, func, true), insertBeforeInstr); InsertBranch(LowererMD::MDOverflowBranchOpcode, bailOutLabel, insertBeforeInstr); @@ -17186,7 +17186,8 @@ Lowerer::GenerateFastStElemI(IR::Instr *& stElem, bool *instrIsInHelperBlockRef) // Convert reg to int32 // Note: ToUint32 is implemented as (uint32)ToInt32() - m_lowererMD.EmitLoadInt32(instr, true /*conversionFromObjectAllowed*/); + bool bailOutOnHelperCall = (stElem->HasBailOutInfo() && (stElem->GetBailOutKind() & IR::BailOutOnArrayAccessHelperCall)); + m_lowererMD.EmitLoadInt32(instr, true /*conversionFromObjectAllowed*/, bailOutOnHelperCall, labelHelper); // MOV indirOpnd, reg InsertMove(indirOpnd, reg, stElem); @@ -23594,7 +23595,7 @@ void Lowerer::LowerLdFrameDisplay(IR::Instr *instr, bool doStackFrameDisplay) // If the dst opnd is a byte code temp, that indicates we're prepending a block scope or some such and // shouldn't attempt to do this. if (envDepth == (uint16)-1 || - (!doStackFrameDisplay && instr->GetDst()->AsRegOpnd()->m_sym->IsTempReg(instr->m_func)) || + (!doStackFrameDisplay && (instr->isNonFastPathFrameDisplay || instr->GetDst()->AsRegOpnd()->m_sym->IsTempReg(instr->m_func))) || PHASE_OFF(Js::FrameDisplayFastPathPhase, func)) { if (isStrict) diff --git a/lib/Backend/LowerMDShared.cpp b/lib/Backend/LowerMDShared.cpp index dadb44f5ef3..3d2496d1cf7 100644 --- a/lib/Backend/LowerMDShared.cpp +++ b/lib/Backend/LowerMDShared.cpp @@ -7996,9 +7996,9 @@ LowererMD::EmitLoadVar(IR::Instr *instrLoad, bool isFromUint32, bool isHelper) } bool -LowererMD::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed) +LowererMD::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed, bool bailOutOnHelper, IR::LabelInstr * labelBailOut) { - return lowererMDArch.EmitLoadInt32(instrLoad, conversionFromObjectAllowed); + return lowererMDArch.EmitLoadInt32(instrLoad, conversionFromObjectAllowed, bailOutOnHelper, labelBailOut); } void diff --git a/lib/Backend/LowerMDShared.h b/lib/Backend/LowerMDShared.h index 82cf1342cef..57844f085c7 100644 --- a/lib/Backend/LowerMDShared.h +++ b/lib/Backend/LowerMDShared.h @@ -210,7 +210,7 @@ class LowererMD static void EmitPtrInstr(IR::Instr *instr); void EmitLoadVar(IR::Instr *instr, bool isFromUint32 = false, bool isHelper = false); void EmitLoadVarNoCheck(IR::RegOpnd * dst, IR::RegOpnd * src, IR::Instr *instrLoad, bool isFromUint32, bool isHelper); - bool EmitLoadInt32(IR::Instr *instr, bool conversionFromObjectAllowed); + bool EmitLoadInt32(IR::Instr *instr, bool conversionFromObjectAllowed, bool bailOutOnHelper = false, IR::LabelInstr * labelBailOut = nullptr); void EmitIntToFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); void EmitUIntToFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); void EmitIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); diff --git a/lib/Backend/amd64/LowererMDArch.cpp b/lib/Backend/amd64/LowererMDArch.cpp index 87efe4e7946..fbb29df0047 100644 --- a/lib/Backend/amd64/LowererMDArch.cpp +++ b/lib/Backend/amd64/LowererMDArch.cpp @@ -1148,7 +1148,9 @@ LowererMDArch::LowerAsmJsLdElemHelper(IR::Instr * instr, bool isSimdLoad /*= fal // MOV tmp, cmpOnd Lowerer::InsertMove(tmp, cmpOpnd, helperLabel); // ADD tmp, dataWidth - Lowerer::InsertAdd(false, tmp, tmp, IR::IntConstOpnd::New((uint32)dataWidth, tmp->GetType(), m_func, true), helperLabel); + Lowerer::InsertAdd(true, tmp, tmp, IR::IntConstOpnd::New((uint32)dataWidth, tmp->GetType(), m_func, true), helperLabel); + // JB helper + Lowerer::InsertBranch(Js::OpCode::JB, helperLabel, helperLabel); // CMP tmp, size // JG $helper lowererMD->m_lowerer->InsertCompareBranch(tmp, instr->UnlinkSrc2(), Js::OpCode::BrGt_A, true, helperLabel, helperLabel); @@ -1224,7 +1226,9 @@ LowererMDArch::LowerAsmJsStElemHelper(IR::Instr * instr, bool isSimdStore /*= fa // MOV tmp, cmpOnd Lowerer::InsertMove(tmp, cmpOpnd, helperLabel); // ADD tmp, dataWidth - Lowerer::InsertAdd(false, tmp, tmp, IR::IntConstOpnd::New((uint32)dataWidth, tmp->GetType(), m_func, true), helperLabel); + Lowerer::InsertAdd(true, tmp, tmp, IR::IntConstOpnd::New((uint32)dataWidth, tmp->GetType(), m_func, true), helperLabel); + // JB helper + Lowerer::InsertBranch(Js::OpCode::JB, helperLabel, helperLabel); // CMP tmp, size // JG $helper lowererMD->m_lowerer->InsertCompareBranch(tmp, instr->UnlinkSrc2(), Js::OpCode::BrGt_A, true, helperLabel, helperLabel); @@ -2576,7 +2580,7 @@ LowererMDArch::EmitUIntToFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrIns } bool -LowererMDArch::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed) +LowererMDArch::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed, bool bailOutOnHelper, IR::LabelInstr * labelBailOut) { // // r1 = MOV src1 @@ -2629,8 +2633,29 @@ LowererMDArch::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllo (src1ValueType.IsLikelyFloat() || src1ValueType.IsLikelyUntaggedInt()) && !(instrLoad->HasBailOutInfo() && (instrLoad->GetBailOutKind() == IR::BailOutIntOnly || instrLoad->GetBailOutKind() == IR::BailOutExpectingInteger)); - if (!isNotInt) + if (isNotInt) { + // Known to be non-integer. If we are required to bail out on helper call, just re-jit. + if (!doFloatToIntFastPath && bailOutOnHelper) + { + if(!GlobOpt::DoAggressiveIntTypeSpec(this->m_func)) + { + // Aggressive int type specialization is already off for some reason. Prevent trying to rejit again + // because it won't help and the same thing will happen again. Just abort jitting this function. + if(PHASE_TRACE(Js::BailOutPhase, this->m_func)) + { + Output::Print(_u(" Aborting JIT because AggressiveIntTypeSpec is already off\n")); + Output::Flush(); + } + throw Js::OperationAbortedException(); + } + + throw Js::RejitException(RejitReason::AggressiveIntTypeSpecDisabled); + } + } + else + { + // It could be an integer in this case. if (!isInt) { if(doFloatToIntFastPath) @@ -2718,7 +2743,13 @@ LowererMDArch::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllo return true; } - if (conversionFromObjectAllowed) + if (bailOutOnHelper) + { + Assert(labelBailOut); + lowererMD->m_lowerer->InsertBranch(Js::OpCode::Br, labelBailOut, instrLoad); + instrLoad->Remove(); + } + else if (conversionFromObjectAllowed) { lowererMD->m_lowerer->LowerUnaryHelperMem(instrLoad, IR::HelperConv_ToInt32); } diff --git a/lib/Backend/amd64/LowererMDArch.h b/lib/Backend/amd64/LowererMDArch.h index d59325c5122..15eed244e42 100644 --- a/lib/Backend/amd64/LowererMDArch.h +++ b/lib/Backend/amd64/LowererMDArch.h @@ -96,7 +96,7 @@ class LowererMDArch void EmitIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); void EmitUIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); void EmitLongToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); - bool EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed); + bool EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed, bool bailOnHelperCall, IR::LabelInstr * labelBailOut); IR::Instr * LoadCheckedFloat(IR::RegOpnd *opndOrig, IR::RegOpnd *opndFloat, IR::LabelInstr *labelInline, IR::LabelInstr *labelHelper, IR::Instr *instrInsert, const bool checkForNullInLoopBody = false); diff --git a/lib/Backend/arm/LowerMD.cpp b/lib/Backend/arm/LowerMD.cpp index 8b23e3c2e19..884e103b61e 100644 --- a/lib/Backend/arm/LowerMD.cpp +++ b/lib/Backend/arm/LowerMD.cpp @@ -7320,7 +7320,7 @@ LowererMD::EmitLoadVarNoCheck(IR::RegOpnd * dst, IR::RegOpnd * src, IR::Instr *i } bool -LowererMD::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed) +LowererMD::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed, bool bailOutOnHelper, IR::LabelInstr * labelBailOut) { // isInt: // dst = ASR r1, AtomTag @@ -7364,8 +7364,29 @@ LowererMD::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed) (src1ValueType.IsLikelyFloat() || src1ValueType.IsLikelyUntaggedInt()) && !(instrLoad->HasBailOutInfo() && (instrLoad->GetBailOutKind() == IR::BailOutIntOnly || instrLoad->GetBailOutKind() == IR::BailOutExpectingInteger)); - if (!isNotInt) + if (isNotInt) { + // Known to be non-integer. If we are required to bail out on helper call, just re-jit. + if (!doFloatToIntFastPath && bailOutOnHelper) + { + if(!GlobOpt::DoAggressiveIntTypeSpec(this->m_func)) + { + // Aggressive int type specialization is already off for some reason. Prevent trying to rejit again + // because it won't help and the same thing will happen again. Just abort jitting this function. + if(PHASE_TRACE(Js::BailOutPhase, this->m_func)) + { + Output::Print(_u(" Aborting JIT because AggressiveIntTypeSpec is already off\n")); + Output::Flush(); + } + throw Js::OperationAbortedException(); + } + + throw Js::RejitException(RejitReason::AggressiveIntTypeSpecDisabled); + } + } + else + { + // Could be an integer in this case. if (!isInt) { if(doFloatToIntFastPath) @@ -7422,7 +7443,13 @@ LowererMD::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed) return true; } - if (conversionFromObjectAllowed) + if (bailOutOnHelper) + { + Assert(labelBailOut); + this->m_lowerer->InsertBranch(Js::OpCode::Br, labelBailOut, instrLoad); + instrLoad->Remove(); + } + else if (conversionFromObjectAllowed) { this->m_lowerer->LowerUnaryHelperMem(instrLoad, IR::HelperConv_ToInt32); } diff --git a/lib/Backend/arm/LowerMD.h b/lib/Backend/arm/LowerMD.h index 59e8e0cd57b..b7d21f8dcb8 100644 --- a/lib/Backend/arm/LowerMD.h +++ b/lib/Backend/arm/LowerMD.h @@ -146,7 +146,7 @@ class LowererMD static void EmitInt4Instr(IR::Instr *instr); static void EmitPtrInstr(IR::Instr *instr); void EmitLoadVar(IR::Instr *instr, bool isFromUint32 = false, bool isHelper = false); - bool EmitLoadInt32(IR::Instr *instr, bool conversionFromObjectAllowed); + bool EmitLoadInt32(IR::Instr *instr, bool conversionFromObjectAllowed, bool bailOutOnHelper = false, IR::LabelInstr * labelBailOut = nullptr); IR::Instr * LowerInt64Assign(IR::Instr * instr) { Assert(UNREACHED); return nullptr; } static void LowerInt4NegWithBailOut(IR::Instr *const instr, const IR::BailOutKind bailOutKind, IR::LabelInstr *const bailOutLabel, IR::LabelInstr *const skipBailOutLabel); diff --git a/lib/Backend/arm64/LowerMD.h b/lib/Backend/arm64/LowerMD.h index bef2cc92517..e177840cafa 100644 --- a/lib/Backend/arm64/LowerMD.h +++ b/lib/Backend/arm64/LowerMD.h @@ -142,7 +142,7 @@ class LowererMD static void EmitInt4Instr(IR::Instr *instr) { __debugbreak(); } static void EmitPtrInstr(IR::Instr *instr) { __debugbreak(); } void EmitLoadVar(IR::Instr *instr, bool isFromUint32 = false, bool isHelper = false) { __debugbreak(); } - bool EmitLoadInt32(IR::Instr *instr, bool conversionFromObjectAllowed) { __debugbreak(); return 0; } + bool EmitLoadInt32(IR::Instr *instr, bool conversionFromObjectAllowed, bool bailOutOnHelper = false, IR::LabelInstr * labelBailOut = nullptr) { __debugbreak(); return 0; } IR::Instr * LowerInt64Assign(IR::Instr * instr) { __debugbreak(); return nullptr; } static void LowerInt4NegWithBailOut(IR::Instr *const instr, const IR::BailOutKind bailOutKind, IR::LabelInstr *const bailOutLabel, IR::LabelInstr *const skipBailOutLabel) { __debugbreak(); } diff --git a/lib/Backend/i386/LowererMDArch.cpp b/lib/Backend/i386/LowererMDArch.cpp index 022971184ed..ce3edb4fe6c 100644 --- a/lib/Backend/i386/LowererMDArch.cpp +++ b/lib/Backend/i386/LowererMDArch.cpp @@ -977,7 +977,9 @@ LowererMDArch::LowerAsmJsLdElemHelper(IR::Instr * instr, bool isSimdLoad /*= fal // MOV tmp, cmpOnd Lowerer::InsertMove(tmp, cmpOpnd, helperLabel); // ADD tmp, dataWidth - Lowerer::InsertAdd(false, tmp, tmp, IR::IntConstOpnd::New((uint32)dataWidth, tmp->GetType(), m_func, true), helperLabel); + Lowerer::InsertAdd(true, tmp, tmp, IR::IntConstOpnd::New((uint32)dataWidth, tmp->GetType(), m_func, true), helperLabel); + // JB helper + Lowerer::InsertBranch(Js::OpCode::JB, helperLabel, helperLabel); // CMP tmp, size // JG $helper lowererMD->m_lowerer->InsertCompareBranch(tmp, instr->UnlinkSrc2(), Js::OpCode::BrGt_A, true, helperLabel, helperLabel); @@ -1037,7 +1039,9 @@ LowererMDArch::LowerAsmJsStElemHelper(IR::Instr * instr, bool isSimdStore /*= fa // MOV tmp, cmpOnd Lowerer::InsertMove(tmp, cmpOpnd, helperLabel); // ADD tmp, dataWidth - Lowerer::InsertAdd(false, tmp, tmp, IR::IntConstOpnd::New((uint32)dataWidth, tmp->GetType(), m_func, true), helperLabel); + Lowerer::InsertAdd(true, tmp, tmp, IR::IntConstOpnd::New((uint32)dataWidth, tmp->GetType(), m_func, true), helperLabel); + // JB helper + Lowerer::InsertBranch(Js::OpCode::JB, helperLabel, helperLabel); // CMP tmp, size // JG $helper lowererMD->m_lowerer->InsertCompareBranch(tmp, instr->UnlinkSrc2(), Js::OpCode::BrGt_A, true, helperLabel, helperLabel); @@ -2555,7 +2559,7 @@ LowererMDArch::EmitLongToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInser } bool -LowererMDArch::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed) +LowererMDArch::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed, bool bailOutOnHelper, IR::LabelInstr * labelBailOut) { // if(doShiftFirst) // { @@ -2613,9 +2617,29 @@ LowererMDArch::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllo instrLoad->InsertBefore(instr); } - // It could be an integer in this case - if (!isNotInt) + if (isNotInt) { + // Known to be non-integer. If we are required to bail out on helper call, just re-jit. + if (!doFloatToIntFastPath && bailOutOnHelper) + { + if(!GlobOpt::DoAggressiveIntTypeSpec(this->m_func)) + { + // Aggressive int type specialization is already off for some reason. Prevent trying to rejit again + // because it won't help and the same thing will happen again. Just abort jitting this function. + if(PHASE_TRACE(Js::BailOutPhase, this->m_func)) + { + Output::Print(_u(" Aborting JIT because AggressiveIntTypeSpec is already off\n")); + Output::Flush(); + } + throw Js::OperationAbortedException(); + } + + throw Js::RejitException(RejitReason::AggressiveIntTypeSpecDisabled); + } + } + else + { + // It could be an integer in this case if(doShiftFirst) { // r1 = SAR r1, VarTag_Shift (move last-shifted bit into CF) @@ -2731,7 +2755,13 @@ LowererMDArch::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllo return true; } - if (conversionFromObjectAllowed) + if (bailOutOnHelper) + { + Assert(labelBailOut); + lowererMD->m_lowerer->InsertBranch(Js::OpCode::Br, labelBailOut, instrLoad); + instrLoad->Remove(); + } + else if (conversionFromObjectAllowed) { lowererMD->m_lowerer->LowerUnaryHelperMem(instrLoad, IR::HelperConv_ToInt32); } diff --git a/lib/Backend/i386/LowererMDArch.h b/lib/Backend/i386/LowererMDArch.h index a9b59ebbfa2..6e2a68d704d 100644 --- a/lib/Backend/i386/LowererMDArch.h +++ b/lib/Backend/i386/LowererMDArch.h @@ -84,7 +84,7 @@ class LowererMDArch void EmitIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); void EmitUIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); void EmitLongToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); - bool EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed); + bool EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed, bool bailOutOnHelper = false, IR::LabelInstr * labelBailOut = nullptr); IR::Instr * LoadCheckedFloat(IR::RegOpnd *opndOrig, IR::RegOpnd *opndFloat, IR::LabelInstr *labelInline, IR::LabelInstr *labelHelper, IR::Instr *instrInsert, const bool checkForNullInLoopBody = false); diff --git a/lib/Parser/Parse.cpp b/lib/Parser/Parse.cpp index 457b91230ae..701b8f6c996 100644 --- a/lib/Parser/Parse.cpp +++ b/lib/Parser/Parse.cpp @@ -2863,6 +2863,7 @@ ParseNodePtr Parser::ParseTerm(BOOL fAllowCall, _Inout_opt_ charcount_t *plastRParen /*= nullptr*/) { ParseNodePtr pnode = nullptr; + PidRefStack *savedTopAsyncRef = nullptr; charcount_t ichMin = 0; size_t iecpMin = 0; size_t iuMin; @@ -2915,6 +2916,13 @@ ParseNodePtr Parser::ParseTerm(BOOL fAllowCall, isLambdaExpr = true; goto LFunction; } + else if (m_token.tk == tkLParen) + { + // This is potentially an async arrow function. Save the state of the async references + // in case it needs to be restored. (Note that the case of a single parameter with no ()'s + // is detected upstream and need not be handled here.) + savedTopAsyncRef = pid->GetTopRef(); + } } // Don't push a reference if this is a single lambda parameter, because we'll reparse with @@ -3269,6 +3277,18 @@ LFunction : pnode = ParsePostfixOperators(pnode, fAllowCall, fInNew, isAsyncExpr, &fCanAssign, &term, pfIsDotOrIndex); + if (savedTopAsyncRef != nullptr && + this->m_token.tk == tkDArrow) + { + // This is an async arrow function; we're going to back up and reparse it. + // Make sure we don't leave behind a bogus reference to the 'async' identifier. + for (IdentPtr pid = wellKnownPropertyPids.async; pid->GetTopRef() != savedTopAsyncRef;) + { + Assert(pid->GetTopRef() != nullptr); + pid->RemovePrevPidRef(nullptr); + } + } + // Pass back identifier if requested if (pToken && term.tk == tkID) { @@ -5150,6 +5170,19 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho paramScope->SetCannotMergeWithBodyScope(); } } + if (paramScope->GetCanMergeWithBodyScope() && !fDeclaration && pnodeFnc->sxFnc.pnodeName != nullptr) + { + Symbol* funcSym = pnodeFnc->sxFnc.pnodeName->sxVar.sym; + if (funcSym->GetPid()->GetTopRef()->GetFuncScopeId() > pnodeFnc->sxFnc.functionId) + { + // This is a function expression with name captured in the param scope. In non-eval, non-split cases the function + // name symbol is added to the body scope to make it accessible in the body. But if there is a function or var + // declaration with the same name in the body then adding to the body will fail. So in this case we have to add + // the name symbol to the param scope by splitting it. + paramScope->SetCannotMergeWithBodyScope(); + } + } + } } diff --git a/lib/Runtime/Base/Debug.cpp b/lib/Runtime/Base/Debug.cpp index c31ff747def..4acd2761a92 100644 --- a/lib/Runtime/Base/Debug.cpp +++ b/lib/Runtime/Base/Debug.cpp @@ -32,16 +32,16 @@ WCHAR* DumpCallStackFull(uint frameCount, bool print) Js::JavascriptFunction *jsFunc = walker.GetCurrentFunction(); Js::FunctionBody * jsBody = jsFunc->GetFunctionBody(); - Js::CallInfo const * callInfo = walker.GetCallInfo(); + const Js::CallInfo callInfo = walker.GetCallInfo(); const WCHAR* sourceFileName = _u("NULL"); ULONG line = 0; LONG column = 0; walker.GetSourcePosition(&sourceFileName, &line, &column); StringCchPrintf(buffer, _countof(buffer), _u("%s [%s] (0x%p, Args=%u"), jsBody->GetDisplayName(), jsBody->GetDebugNumberSet(debugStringBuffer), jsFunc, - callInfo->Count); + callInfo.Count); sb.AppendSz(buffer); - for (uint i = 0; i < callInfo->Count; i++) + for (uint i = 0; i < callInfo.Count; i++) { StringCchPrintf(buffer, _countof(buffer), _u(", 0x%p"), walker.GetJavascriptArgs()[i]); sb.AppendSz(buffer); diff --git a/lib/Runtime/Base/FunctionBody.cpp b/lib/Runtime/Base/FunctionBody.cpp index e2361460b42..be5c24b9bfc 100644 --- a/lib/Runtime/Base/FunctionBody.cpp +++ b/lib/Runtime/Base/FunctionBody.cpp @@ -2152,6 +2152,7 @@ namespace Js return GetFunctionBody(); } + bool asmjsParseFailed = false; BOOL fParsed = FALSE; FunctionBody* returnFunctionBody = nullptr; ENTER_PINNED_SCOPE(Js::PropertyRecordList, propertyRecordList); @@ -2161,227 +2162,232 @@ namespace Js bool isDebugOrAsmJsReparse = false; FunctionBody* funcBody = nullptr; - AutoRestoreFunctionInfo autoRestoreFunctionInfo(this, DefaultEntryThunk); - - // If m_hasBeenParsed = true, one of the following things happened: - // - We had multiple function objects which were all defer-parsed, but with the same function body and one of them - // got the body to be parsed before another was called - // - We are in debug mode and had our thunks switched to DeferParseThunk - // - This is an already parsed asm.js module, which has been invalidated at link time and must be reparsed as a non-asm.js function - if (!this->m_hasBeenParsed) { + AutoRestoreFunctionInfo autoRestoreFunctionInfo(this, DefaultEntryThunk); + + + // If m_hasBeenParsed = true, one of the following things happened things happened: + // - We had multiple function objects which were all defer-parsed, but with the same function body and one of them + // got the body to be parsed before another was called + // - We are in debug mode and had our thunks switched to DeferParseThunk + // - This is an already parsed asm.js module, which has been invalidated at link time and must be reparsed as a non-asm.js function + if (!this->m_hasBeenParsed) + { this->GetUtf8SourceInfo()->StopTrackingDeferredFunction(this->GetLocalFunctionId()); funcBody = FunctionBody::NewFromParseableFunctionInfo(this, propertyRecordList); - autoRestoreFunctionInfo.funcBody = funcBody; + autoRestoreFunctionInfo.funcBody = funcBody; - PERF_COUNTER_DEC(Code, DeferredFunction); - - if (!this->GetSourceContextInfo()->IsDynamic()) - { - PHASE_PRINT_TESTTRACE1(Js::DeferParsePhase, _u("TestTrace: Deferred function parsed - ID: %d; Display Name: %s; Length: %d; Nested Function Count: %d; Utf8SourceInfo: %d; Source Length: %d; Is Top Level: %s; Source Url: %s\n"), m_functionNumber, this->GetDisplayName(), this->m_cchLength, this->GetNestedCount(), this->m_utf8SourceInfo->GetSourceInfoId(), this->m_utf8SourceInfo->GetCchLength(), this->GetIsTopLevel() ? _u("True") : _u("False"), this->GetSourceContextInfo()->url); - } - else - { - PHASE_PRINT_TESTTRACE1(Js::DeferParsePhase, _u("TestTrace: Deferred function parsed - ID: %d; Display Name: %s; Length: %d; Nested Function Count: %d; Utf8SourceInfo: %d; Source Length: %d\n; Is Top Level: %s;"), m_functionNumber, this->GetDisplayName(), this->m_cchLength, this->GetNestedCount(), this->m_utf8SourceInfo->GetSourceInfoId(), this->m_utf8SourceInfo->GetCchLength(), this->GetIsTopLevel() ? _u("True") : _u("False")); - } + PERF_COUNTER_DEC(Code, DeferredFunction); - if (!this->GetIsTopLevel() && - !this->GetSourceContextInfo()->IsDynamic() && - this->m_scriptContext->DoUndeferGlobalFunctions()) - { - this->GetUtf8SourceInfo()->UndeferGlobalFunctions([this](const Utf8SourceInfo::DeferredFunctionsDictionary::EntryType& func) + if (!this->GetSourceContextInfo()->IsDynamic()) + { + PHASE_PRINT_TESTTRACE1(Js::DeferParsePhase, _u("TestTrace: Deferred function parsed - ID: %d; Display Name: %s; Length: %d; Nested Function Count: %d; Utf8SourceInfo: %d; Source Length: %d; Is Top Level: %s; Source Url: %s\n"), m_functionNumber, this->GetDisplayName(), this->m_cchLength, this->GetNestedCount(), this->m_utf8SourceInfo->GetSourceInfoId(), this->m_utf8SourceInfo->GetCchLength(), this->GetIsTopLevel() ? _u("True") : _u("False"), this->GetSourceContextInfo()->url); + } + else { - Js::ParseableFunctionInfo *nextFunc = func.Value(); - JavascriptExceptionObject* pExceptionObject = nullptr; + PHASE_PRINT_TESTTRACE1(Js::DeferParsePhase, _u("TestTrace: Deferred function parsed - ID: %d; Display Name: %s; Length: %d; Nested Function Count: %d; Utf8SourceInfo: %d; Source Length: %d\n; Is Top Level: %s;"), m_functionNumber, this->GetDisplayName(), this->m_cchLength, this->GetNestedCount(), this->m_utf8SourceInfo->GetSourceInfoId(), this->m_utf8SourceInfo->GetCchLength(), this->GetIsTopLevel() ? _u("True") : _u("False")); + } - if (nextFunc != nullptr && this != nextFunc) + if (!this->GetIsTopLevel() && + !this->GetSourceContextInfo()->IsDynamic() && + this->m_scriptContext->DoUndeferGlobalFunctions()) + { + this->GetUtf8SourceInfo()->UndeferGlobalFunctions([this](const Utf8SourceInfo::DeferredFunctionsDictionary::EntryType& func) { - try - { - nextFunc->Parse(); - } - catch (OutOfMemoryException) {} - catch (StackOverflowException) {} - catch (const Js::JavascriptException& err) - { - pExceptionObject = err.GetAndClear(); - } + Js::ParseableFunctionInfo *nextFunc = func.Value(); + JavascriptExceptionObject* pExceptionObject = nullptr; - // Do not do anything with an OOM or SOE, returning true is fine, it will then be undeferred (or attempted to again when called) - if(pExceptionObject) + if (nextFunc != nullptr && this != nextFunc) { - if(pExceptionObject != ThreadContext::GetContextForCurrentThread()->GetPendingOOMErrorObject() && - pExceptionObject != ThreadContext::GetContextForCurrentThread()->GetPendingSOErrorObject()) + try { - JavascriptExceptionOperators::DoThrow(pExceptionObject, /*scriptContext*/nullptr); + nextFunc->Parse(); + } + catch (OutOfMemoryException) {} + catch (StackOverflowException) {} + catch (const Js::JavascriptException& err) + { + pExceptionObject = err.GetAndClear(); + } + + // Do not do anything with an OOM or SOE, returning true is fine, it will then be undeferred (or attempted to again when called) + if (pExceptionObject) + { + if (pExceptionObject != ThreadContext::GetContextForCurrentThread()->GetPendingOOMErrorObject() && + pExceptionObject != ThreadContext::GetContextForCurrentThread()->GetPendingSOErrorObject()) + { + JavascriptExceptionOperators::DoThrow(pExceptionObject, /*scriptContext*/nullptr); + } } } - } - return true; - }); + return true; + }); + } } - } - else - { - bool isDebugReparse = m_scriptContext->IsScriptContextInSourceRundownOrDebugMode() && !this->GetUtf8SourceInfo()->GetIsLibraryCode(); - bool isAsmJsReparse = m_isAsmjsMode && !isDebugReparse; - - isDebugOrAsmJsReparse = isAsmJsReparse || isDebugReparse; + else + { + bool isDebugReparse = m_scriptContext->IsScriptContextInSourceRundownOrDebugMode() && !this->GetUtf8SourceInfo()->GetIsLibraryCode(); + bool isAsmJsReparse = m_isAsmjsMode && !isDebugReparse; - funcBody = this->GetFunctionBody(); + isDebugOrAsmJsReparse = isAsmJsReparse || isDebugReparse; - if (isDebugOrAsmJsReparse) - { - #if ENABLE_DEBUG_CONFIG_OPTIONS - char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; - #endif - #if DBG - Assert( - funcBody->IsReparsed() - || m_scriptContext->IsScriptContextInSourceRundownOrDebugMode() - || m_isAsmjsMode); - #endif - OUTPUT_TRACE(Js::DebuggerPhase, _u("Full nested reparse of function: %s (%s)\n"), funcBody->GetDisplayName(), funcBody->GetDebugNumberSet(debugStringBuffer)); + funcBody = this->GetFunctionBody(); - if (funcBody->GetByteCode()) + if (isDebugOrAsmJsReparse) { - // The current function needs to be cleaned up before getting generated in the debug mode. - funcBody->CleanupToReparse(); - } +#if ENABLE_DEBUG_CONFIG_OPTIONS + char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; +#endif +#if DBG + Assert( + funcBody->IsReparsed() + || m_scriptContext->IsScriptContextInSourceRundownOrDebugMode() + || m_isAsmjsMode); +#endif + OUTPUT_TRACE(Js::DebuggerPhase, _u("Full nested reparse of function: %s (%s)\n"), funcBody->GetDisplayName(), funcBody->GetDebugNumberSet(debugStringBuffer)); + if (funcBody->GetByteCode()) + { + // The current function needs to be cleaned up before getting generated in the debug mode. + funcBody->CleanupToReparse(); + } + + } } - } - // Note that we may be trying to re-gen an already-completed function. (This can happen, for instance, - // in the case of named function expressions inside "with" statements in compat mode.) - // In such a case, there's no work to do. - if (funcBody->GetByteCode() == nullptr) - { + // Note that we may be trying to re-gen an already-completed function. (This can happen, for instance, + // in the case of named function expressions inside "with" statements in compat mode.) + // In such a case, there's no work to do. + if (funcBody->GetByteCode() == nullptr) + { #if ENABLE_PROFILE_INFO - Assert(!funcBody->HasExecutionDynamicProfileInfo()); + Assert(!funcBody->HasExecutionDynamicProfileInfo()); #endif - // In debug or asm.js mode, the scriptlet will be asked to recompile again. - AssertMsg(isDebugOrAsmJsReparse || funcBody->GetGrfscr() & fscrGlobalCode || CONFIG_FLAG(DeferNested), "Deferred parsing of non-global procedure?"); - - HRESULT hr = NO_ERROR; - HRESULT hrParser = NO_ERROR; - HRESULT hrParseCodeGen = NO_ERROR; + // In debug or asm.js mode, the scriptlet will be asked to recompile again. + AssertMsg(isDebugOrAsmJsReparse || funcBody->GetGrfscr() & fscrGlobalCode || CONFIG_FLAG(DeferNested), "Deferred parsing of non-global procedure?"); - BEGIN_LEAVE_SCRIPT_INTERNAL(m_scriptContext) - { - bool isCesu8 = m_scriptContext->GetSource(funcBody->GetSourceIndex())->IsCesu8(); - - size_t offset = this->StartOffset(); - charcount_t charOffset = this->StartInDocument(); - size_t length = this->LengthInBytes(); - - LPCUTF8 pszStart = this->GetStartOfDocument(); - - uint32 grfscr = funcBody->GetGrfscr() | fscrDeferredFnc; + HRESULT hr = NO_ERROR; + HRESULT hrParser = NO_ERROR; + HRESULT hrParseCodeGen = NO_ERROR; - // For the global function we want to re-use the glo functionbody which is already created in the non-debug mode - if (!funcBody->GetIsGlobalFunc()) + BEGIN_LEAVE_SCRIPT_INTERNAL(m_scriptContext) { - grfscr &= ~fscrGlobalCode; - } + bool isCesu8 = m_scriptContext->GetSource(funcBody->GetSourceIndex())->IsCesu8(); - if (!funcBody->GetIsDeclaration() && !funcBody->GetIsGlobalFunc()) // No refresh may reparse global function (e.g. eval code) - { - // Notify the parser that the top-level function was defined in an expression, - // (not a function declaration statement). - grfscr |= fscrDeferredFncExpression; - } - if (!CONFIG_FLAG(DeferNested) || isDebugOrAsmJsReparse) - { - grfscr &= ~fscrDeferFncParse; // Disable deferred parsing if not DeferNested, or doing a debug/asm.js re-parse - } + size_t offset = this->StartOffset(); + charcount_t charOffset = this->StartInDocument(); + size_t length = this->LengthInBytes(); - if (isDebugOrAsmJsReparse) - { - grfscr |= fscrNoAsmJs; // Disable asm.js when debugging or if linking failed - } + LPCUTF8 pszStart = this->GetStartOfDocument(); - BEGIN_TRANSLATE_EXCEPTION_TO_HRESULT - { - CompileScriptException se; - Parser ps(m_scriptContext, funcBody->GetIsStrictMode() ? TRUE : FALSE); - ParseNodePtr parseTree; + uint32 grfscr = funcBody->GetGrfscr() | fscrDeferredFnc; - uint nextFunctionId = funcBody->GetLocalFunctionId(); - hrParser = ps.ParseSourceWithOffset(&parseTree, pszStart, offset, length, charOffset, isCesu8, grfscr, &se, - &nextFunctionId, funcBody->GetRelativeLineNumber(), funcBody->GetSourceContextInfo(), - funcBody); -// Assert(FAILED(hrParser) || nextFunctionId == funcBody->deferredParseNextFunctionId || isDebugOrAsmJsReparse || isByteCodeDeserialization); + // For the global function we want to re-use the glo functionbody which is already created in the non-debug mode + if (!funcBody->GetIsGlobalFunc()) + { + grfscr &= ~fscrGlobalCode; + } - if (FAILED(hrParser)) + if (!funcBody->GetIsDeclaration() && !funcBody->GetIsGlobalFunc()) // No refresh may reparse global function (e.g. eval code) { - hrParseCodeGen = MapDeferredReparseError(hrParser, se); // Map certain errors like OOM/SOE - AssertMsg(FAILED(hrParseCodeGen) && SUCCEEDED(hrParser), "Syntax errors should never be detected on deferred re-parse"); + // Notify the parser that the top-level function was defined in an expression, + // (not a function declaration statement). + grfscr |= fscrDeferredFncExpression; } - else + if (!CONFIG_FLAG(DeferNested) || isDebugOrAsmJsReparse) { - TRACE_BYTECODE(_u("\nDeferred parse %s\n"), funcBody->GetDisplayName()); - Js::AutoDynamicCodeReference dynamicFunctionReference(m_scriptContext); + grfscr &= ~fscrDeferFncParse; // Disable deferred parsing if not DeferNested, or doing a debug/asm.js re-parse + } - bool forceNoNative = isDebugOrAsmJsReparse ? this->GetScriptContext()->IsInterpreted() : false; + if (isDebugOrAsmJsReparse) + { + grfscr |= fscrNoAsmJs; // Disable asm.js when debugging or if linking failed + } - ParseableFunctionInfo* rootFunc = funcBody->GetParseableFunctionInfo(); - hrParseCodeGen = GenerateByteCode(parseTree, grfscr, m_scriptContext, - &rootFunc, funcBody->GetSourceIndex(), - forceNoNative, &ps, &se, funcBody->GetScopeInfo(), functionRef); - funcBody->SetParseableFunctionInfo(rootFunc); + BEGIN_TRANSLATE_EXCEPTION_TO_HRESULT + { + CompileScriptException se; + Parser ps(m_scriptContext, funcBody->GetIsStrictMode() ? TRUE : FALSE); + ParseNodePtr parseTree; - if (se.ei.scode == JSERR_AsmJsCompileError) - { - // if asm.js compilation failed, reparse without asm.js - m_grfscr |= fscrNoAsmJs; - se.Clear(); - return Parse(functionRef, isByteCodeDeserialization); - } + uint nextFunctionId = funcBody->GetLocalFunctionId(); + hrParser = ps.ParseSourceWithOffset(&parseTree, pszStart, offset, length, charOffset, isCesu8, grfscr, &se, + &nextFunctionId, funcBody->GetRelativeLineNumber(), funcBody->GetSourceContextInfo(), + funcBody); + // Assert(FAILED(hrParser) || nextFunctionId == funcBody->deferredParseNextFunctionId || isDebugOrAsmJsReparse || isByteCodeDeserialization); - if (SUCCEEDED(hrParseCodeGen)) + if (FAILED(hrParser)) { - fParsed = TRUE; + hrParseCodeGen = MapDeferredReparseError(hrParser, se); // Map certain errors like OOM/SOE + AssertMsg(FAILED(hrParseCodeGen) && SUCCEEDED(hrParser), "Syntax errors should never be detected on deferred re-parse"); } else { - Assert(hrParseCodeGen == SCRIPT_E_RECORDED); - hrParseCodeGen = se.ei.scode; + TRACE_BYTECODE(_u("\nDeferred parse %s\n"), funcBody->GetDisplayName()); + Js::AutoDynamicCodeReference dynamicFunctionReference(m_scriptContext); + + bool forceNoNative = isDebugOrAsmJsReparse ? this->GetScriptContext()->IsInterpreted() : false; + + ParseableFunctionInfo* rootFunc = funcBody->GetParseableFunctionInfo(); + hrParseCodeGen = GenerateByteCode(parseTree, grfscr, m_scriptContext, + &rootFunc, funcBody->GetSourceIndex(), + forceNoNative, &ps, &se, funcBody->GetScopeInfo(), functionRef); + funcBody->SetParseableFunctionInfo(rootFunc); + + if (SUCCEEDED(hrParseCodeGen)) + { + fParsed = TRUE; + } + else + { + Assert(hrParseCodeGen == SCRIPT_E_RECORDED); + hrParseCodeGen = se.ei.scode; + } } } + END_TRANSLATE_EXCEPTION_TO_HRESULT(hr); } - END_TRANSLATE_EXCEPTION_TO_HRESULT(hr); - } - END_LEAVE_SCRIPT_INTERNAL(m_scriptContext); + END_LEAVE_SCRIPT_INTERNAL(m_scriptContext); - THROW_KNOWN_HRESULT_EXCEPTIONS(hr, m_scriptContext); + THROW_KNOWN_HRESULT_EXCEPTIONS(hr, m_scriptContext); - Assert(hr == NO_ERROR); + Assert(hr == NO_ERROR); - if (!SUCCEEDED(hrParser)) + if (!SUCCEEDED(hrParser)) + { + JavascriptError::ThrowError(m_scriptContext, VBSERR_InternalError); + } + else if (!SUCCEEDED(hrParseCodeGen)) + { + /* + * VBSERR_OutOfStack is of type kjstError but we throw a (more specific) StackOverflowError when a hard stack + * overflow occurs. To keep the behavior consistent I'm special casing it here. + */ + if (hrParseCodeGen == VBSERR_OutOfStack) + { + JavascriptError::ThrowStackOverflowError(m_scriptContext); + } + else if (hrParseCodeGen == JSERR_AsmJsCompileError) + { + asmjsParseFailed = true; + } + else + { + JavascriptError::MapAndThrowError(m_scriptContext, hrParseCodeGen); + } + } + } + else { - JavascriptError::ThrowError(m_scriptContext, VBSERR_InternalError); + fParsed = FALSE; } - else if (!SUCCEEDED(hrParseCodeGen)) + + if (!asmjsParseFailed) { - /* - * VBSERR_OutOfStack is of type kjstError but we throw a (more specific) StackOverflowError when a hard stack - * overflow occurs. To keep the behavior consistent I'm special casing it here. - */ - if (hrParseCodeGen == VBSERR_OutOfStack) - { - JavascriptError::ThrowStackOverflowError(m_scriptContext); - } - JavascriptError::MapAndThrowError(m_scriptContext, hrParseCodeGen); + autoRestoreFunctionInfo.Clear(); } } - else - { - fParsed = FALSE; - } - - autoRestoreFunctionInfo.Clear(); if (fParsed == TRUE) { @@ -2391,13 +2397,20 @@ namespace Js this->m_hasBeenParsed = true; returnFunctionBody = funcBody; } - else + else if(!asmjsParseFailed) { returnFunctionBody = this->GetFunctionBody(); } LEAVE_PINNED_SCOPE(); + if (asmjsParseFailed) + { + // disable asm.js and reparse on failure + m_grfscr |= fscrNoAsmJs; + return Parse(functionRef, isByteCodeDeserialization); + } + return returnFunctionBody; } diff --git a/lib/Runtime/Base/ScriptContext.cpp b/lib/Runtime/Base/ScriptContext.cpp index 36d60bdb8f6..2596afaac08 100644 --- a/lib/Runtime/Base/ScriptContext.cpp +++ b/lib/Runtime/Base/ScriptContext.cpp @@ -293,7 +293,7 @@ namespace Js CleanupDocumentContext = nullptr; #endif - // Do this after all operations that may cause potential exceptions + // Do this after all operations that may cause potential exceptions. Note: InitialAllocations may still throw! threadContext->RegisterScriptContext(this); numberAllocator.Initialize(this->GetRecycler()); @@ -468,6 +468,10 @@ namespace Js } } + // Normally the JavascriptLibraryBase will unregister the scriptContext from the threadContext. + // In cases where we don't finish initialization e.g. OOM, manually unregister the scriptContext. + threadContext->UnregisterScriptContext(this); + #if ENABLE_BACKGROUND_PARSING if (this->backgroundParser != nullptr) { diff --git a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp index d0d824f90cd..bba78a27365 100644 --- a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp +++ b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp @@ -3325,6 +3325,7 @@ void ByteCodeGenerator::EmitOneFunction(ParseNode *pnode) { // Emit bytecode to copy the initial values from param names to their corresponding body bindings. // We have to do this after the rest param is marked as false for need declaration. + Symbol* funcSym = funcInfo->root->sxFnc.GetFuncSymbol(); paramScope->ForEachSymbol([&](Symbol* param) { Symbol* varSym = funcInfo->GetBodyScope()->FindLocalSymbol(param->GetName()); Assert(varSym || pnode->sxFnc.pnodeName->sxVar.sym == param); @@ -3333,7 +3334,9 @@ void ByteCodeGenerator::EmitOneFunction(ParseNode *pnode) { // Do not copy the arguments to the body if it is not used } - else if (varSym && varSym->GetSymbolType() == STVariable && (varSym->IsInSlot(funcInfo) || varSym->GetLocation() != Js::Constants::NoRegister)) + else if ((funcSym == nullptr || funcSym != param) // Do not copy the symbol over to body as the function expression symbol + // is expected to stay inside the function expression scope + && (varSym && varSym->GetSymbolType() == STVariable && (varSym->IsInSlot(funcInfo) || varSym->GetLocation() != Js::Constants::NoRegister))) { // Simulating EmitPropLoad here. We can't directly call the method as we have to use the param scope specifically. // Walking the scope chain is not possible at this time. @@ -3639,9 +3642,9 @@ void ByteCodeGenerator::EmitScopeList(ParseNode *pnode, ParseNode *breakOnBodySc { exit(JSERR_AsmJsCompileError); } - else if (!(flags & fscrDeferFncParse)) + else { - // If deferral is not allowed, throw and reparse everything with asm.js disabled. + // if asm.js parse error happened, reparse with asm.js disabled. throw Js::AsmJsParseException(); } } diff --git a/lib/Runtime/ByteCode/FuncInfo.cpp b/lib/Runtime/ByteCode/FuncInfo.cpp index 7446204c688..a5d86426c38 100644 --- a/lib/Runtime/ByteCode/FuncInfo.cpp +++ b/lib/Runtime/ByteCode/FuncInfo.cpp @@ -13,7 +13,7 @@ FuncInfo::FuncInfo( Js::ParseableFunctionInfo* byteCodeFunction) : alloc(alloc), varRegsCount(0), - constRegsCount(2), + constRegsCount(InitialConstRegsCount), inArgsCount(0), innerScopeCount(0), currentInnerScopeIndex((uint)-1), diff --git a/lib/Runtime/ByteCode/FuncInfo.h b/lib/Runtime/ByteCode/FuncInfo.h index d47e14c8f99..a996858ddbd 100644 --- a/lib/Runtime/ByteCode/FuncInfo.h +++ b/lib/Runtime/ByteCode/FuncInfo.h @@ -83,6 +83,8 @@ class FuncInfo uint nextForInLoopLevel; uint maxForInLoopLevel; public: + static const Js::RegSlot InitialConstRegsCount = 2; // constRegsCount is set to 2 because R0 is the return register, and R1 is the root object + ArenaAllocator *alloc; // set in Bind/Assign pass Js::RegSlot varRegsCount; // number of registers used for non-constants @@ -192,7 +194,6 @@ class FuncInfo Symbol *innerArgumentsSymbol; JsUtil::List nonUserNonTempRegistersToInitialize; - // constRegsCount is set to 2 because R0 is the return register, and R1 is the root object. FuncInfo( const char16 *name, ArenaAllocator *alloc, diff --git a/lib/Runtime/Language/AsmJs.cpp b/lib/Runtime/Language/AsmJs.cpp index 9e87e8c9ddf..5516a491009 100644 --- a/lib/Runtime/Language/AsmJs.cpp +++ b/lib/Runtime/Language/AsmJs.cpp @@ -919,7 +919,11 @@ namespace Js { ParseNode* endStmt = m.GetCurrentParserNode(); - Assert( endStmt->nop == knopList ); + if (endStmt->nop != knopList) + { + return m.Fail(endStmt, _u("Module must have a return")); + } + ParseNode* node = ParserWrapper::GetBinaryLeft( endStmt ); ParseNode* endNode = ParserWrapper::GetBinaryRight( endStmt ); @@ -961,6 +965,10 @@ namespace Js } ParseNode* objectElement = ParserWrapper::GetUnaryNode(objNode); + if (!objectElement) + { + return m.Fail(node, _u("Return object must not be empty")); + } while( objectElement ) { ParseNode* member = nullptr; diff --git a/lib/Runtime/Language/AsmJsByteCodeGenerator.cpp b/lib/Runtime/Language/AsmJsByteCodeGenerator.cpp index ef62ed78d7a..0646117ea4e 100644 --- a/lib/Runtime/Language/AsmJsByteCodeGenerator.cpp +++ b/lib/Runtime/Language/AsmJsByteCodeGenerator.cpp @@ -3324,6 +3324,8 @@ namespace Js byteCodeGen->Writer()->EndStatement(functionNode); byteCodeGen->Writer()->End(); + functionBody->CheckAndSetConstantCount(FuncInfo::InitialConstRegsCount); + autoCleanup.Done(); } diff --git a/lib/Runtime/Language/AsmJsModule.cpp b/lib/Runtime/Language/AsmJsModule.cpp index 7de3ee92528..eca10fb536e 100644 --- a/lib/Runtime/Language/AsmJsModule.cpp +++ b/lib/Runtime/Language/AsmJsModule.cpp @@ -1091,9 +1091,14 @@ namespace Js { if( DefineIdentifier( name, func ) ) { - func->SetFunctionIndex( pnodeFnc->sxFnc.nestedIndex ); - // Add extra check to make sure all the slots between 0 - Count are filled with func; - mFunctionArray.SetItem( func->GetFunctionIndex(), func ); + uint index = (uint)mFunctionArray.Count(); + if (pnodeFnc->sxFnc.nestedIndex != index) + { + return nullptr; + } + func->SetFunctionIndex( (RegSlot)index ); + mFunctionArray.Add( func ); + Assert(index + 1 == (uint)mFunctionArray.Count()); return func; } // Error adding function diff --git a/lib/Runtime/Language/InterpreterStackFrame.cpp b/lib/Runtime/Language/InterpreterStackFrame.cpp index e462e19ccd4..73ba183867e 100644 --- a/lib/Runtime/Language/InterpreterStackFrame.cpp +++ b/lib/Runtime/Language/InterpreterStackFrame.cpp @@ -2768,16 +2768,18 @@ namespace Js AsmJsScriptFunction* scriptFuncObj = (AsmJsScriptFunction*)ScriptFunction::OP_NewScFunc(pDisplay, functionInfo); localModuleFunctions[modFunc.location] = scriptFuncObj; + + if (scriptFuncObj->GetDynamicType()->GetEntryPoint() == DefaultDeferredDeserializeThunk) + { + JavascriptFunction::DeferredDeserialize(scriptFuncObj); + } + if (i == 0 && info->GetUsesChangeHeap()) { scriptFuncObj->GetDynamicType()->SetEntryPoint(AsmJsChangeHeapBuffer); } else { - if (scriptFuncObj->GetDynamicType()->GetEntryPoint() == DefaultDeferredDeserializeThunk) - { - JavascriptFunction::DeferredDeserialize(scriptFuncObj); - } scriptFuncObj->GetDynamicType()->SetEntryPoint(AsmJsExternalEntryPoint); scriptFuncObj->GetFunctionBody()->GetAsmJsFunctionInfo()->SetModuleFunctionBody(asmJsModuleFunctionBody); } @@ -6342,6 +6344,7 @@ const byte * InterpreterStackFrame::OP_ProfiledLoopBodyStart(const byte * ip) PROBE_STACK(scriptContext, outArgs.Info.Count * sizeof(Var) + Js::Constants::MinStackDefault); // args + function call outArgsSize = outArgs.Info.Count * sizeof(Var); outArgs.Values = (Var*)_alloca(outArgsSize); + ZeroMemory(outArgs.Values, outArgsSize); } else { diff --git a/lib/Runtime/Language/JavascriptStackWalker.cpp b/lib/Runtime/Language/JavascriptStackWalker.cpp index a75d9b28c6b..1bcfe7f7808 100644 --- a/lib/Runtime/Language/JavascriptStackWalker.cpp +++ b/lib/Runtime/Language/JavascriptStackWalker.cpp @@ -154,7 +154,7 @@ namespace Js Assert(IsJavascriptFrame()); AssertMsg(this->GetCurrentFunction()->IsScriptFunction(), "GetPermanentArguments should not be called for non-script function as there is no slot allocated for it."); - const uint32 paramCount = GetCallInfo()->Count; + const uint32 paramCount = GetCallInfo().Count; if (paramCount == 0) { // glob function doesn't allocate ArgumentsObject slot on stack @@ -206,8 +206,8 @@ namespace Js else #endif { - CallInfo const *callInfo = this->GetCallInfo(); - if (callInfo->Count == 0) + const CallInfo callInfo = this->GetCallInfo(); + if (callInfo.Count == 0) { *pVarThis = JavascriptOperators::OP_GetThis(scriptContext->GetLibrary()->GetUndefined(), moduleId, scriptContext); return false; @@ -218,14 +218,14 @@ namespace Js } } - BOOL IsEval(const CallInfo* callInfo) + BOOL IsEval(CallInfo callInfo) { - return (callInfo->Flags & CallFlags_Eval) != 0; + return (callInfo.Flags & CallFlags_Eval) != 0; } BOOL JavascriptStackWalker::IsCallerGlobalFunction() const { - CallInfo const* callInfo = this->GetCallInfo(); + const CallInfo callInfo = this->GetCallInfo(); JavascriptFunction* function = this->GetCurrentFunction(); if (IsLibraryStackFrameEnabled(this->scriptContext) && !function->IsScriptFunction()) @@ -241,14 +241,14 @@ namespace Js else { AssertMsg(FALSE, "Here we should only have script functions which were already parsed/deserialized."); - return callInfo->Count == 0 || IsEval(callInfo); + return callInfo.Count == 0 || IsEval(callInfo); } } BOOL JavascriptStackWalker::IsEvalCaller() const { - CallInfo const* callInfo = this->GetCallInfo(); - return (callInfo->Flags & CallFlags_Eval) != 0; + const CallInfo callInfo = this->GetCallInfo(); + return (callInfo.Flags & CallFlags_Eval) != 0; } Var JavascriptStackWalker::GetCurrentNativeArgumentsObject() const @@ -831,7 +831,7 @@ namespace Js if (this->IsJavascriptFrame() && this->GetCurrentFunction() == funcTarget) { // Skip internal names - Assert( !(this->GetCallInfo()->Flags & CallFlags_InternalFrame) ); + Assert( !(this->GetCallInfo().Flags & CallFlags_InternalFrame) ); return true; } } @@ -1008,32 +1008,41 @@ namespace Js return GetCurrentFunction(false); } - CallInfo const * JavascriptStackWalker::GetCallInfo(bool includeInlinedFrames /* = true */) const + CallInfo JavascriptStackWalker::GetCallInfo(bool includeInlinedFrames /* = true */) const { Assert(this->IsJavascriptFrame()); + CallInfo callInfo; if (includeInlinedFrames && inlinedFramesBeingWalked) { // Since we don't support inlining constructors yet, its questionable if we should handle the // hidden frame display here? - return (CallInfo const *)&inlinedFrameCallInfo; + callInfo = inlinedFrameCallInfo; } else if (this->GetCurrentFunction()->GetFunctionInfo()->IsCoroutine()) { JavascriptGenerator* gen = JavascriptGenerator::FromVar(this->GetCurrentArgv()[JavascriptFunctionArgIndex_This]); - return &gen->GetArguments().Info; + callInfo = gen->GetArguments().Info; } else if (this->isNativeLibraryFrame) { // Return saved callInfo. Do not read from stack as compiler may stackpack/optimize args. - return &this->prevNativeLibraryEntry->callInfo; + callInfo = this->prevNativeLibraryEntry->callInfo; } else { - return (CallInfo const *)&this->GetCurrentArgv()[JavascriptFunctionArgIndex_CallInfo]; + callInfo = *(CallInfo const *)&this->GetCurrentArgv()[JavascriptFunctionArgIndex_CallInfo]; } + + if (callInfo.Flags & Js::CallFlags_ExtraArg) + { + callInfo.Flags = (CallFlags)(callInfo.Flags & ~Js::CallFlags_ExtraArg); + callInfo.Count--; + } + + return callInfo; } - CallInfo const *JavascriptStackWalker::GetCallInfoFromPhysicalFrame() const + CallInfo JavascriptStackWalker::GetCallInfoFromPhysicalFrame() const { return GetCallInfo(false); } @@ -1076,7 +1085,7 @@ namespace Js bool JavascriptStackWalker::IsCurrentPhysicalFrameForLoopBody() const { - return !!(this->GetCallInfoFromPhysicalFrame()->Flags & CallFlags_InternalFrame); + return !!(this->GetCallInfoFromPhysicalFrame().Flags & CallFlags_InternalFrame); } bool JavascriptStackWalker::IsWalkable(ScriptContext *scriptContext) diff --git a/lib/Runtime/Language/JavascriptStackWalker.h b/lib/Runtime/Language/JavascriptStackWalker.h index 9b6ca8e9522..ea7b1867e7f 100644 --- a/lib/Runtime/Language/JavascriptStackWalker.h +++ b/lib/Runtime/Language/JavascriptStackWalker.h @@ -212,8 +212,8 @@ namespace Js JavascriptFunction *GetCurrentFunction(bool includeInlinedFrames = true) const; void SetCurrentFunction(JavascriptFunction * function); - CallInfo const *GetCallInfo(bool includeInlinedFrames = true) const; - CallInfo const *GetCallInfoFromPhysicalFrame() const; + CallInfo GetCallInfo(bool includeInlinedFrames = true) const; + CallInfo GetCallInfoFromPhysicalFrame() const; bool GetThis(Var *pThis, int moduleId) const; Js::Var * GetJavascriptArgs() const; void **GetCurrentArgv() const; diff --git a/lib/Runtime/Language/ProfilingHelpers.cpp b/lib/Runtime/Language/ProfilingHelpers.cpp index 023f7a2792d..9b4c607c221 100644 --- a/lib/Runtime/Language/ProfilingHelpers.cpp +++ b/lib/Runtime/Language/ProfilingHelpers.cpp @@ -487,6 +487,7 @@ namespace Js PROBE_STACK(scriptContext, outArgs.Info.Count * sizeof(Var) + Js::Constants::MinStackDefault); // args + function call outArgsSize = outArgs.Info.Count * sizeof(Var); outArgs.Values = (Var*)_alloca(outArgsSize); + ZeroMemory(outArgs.Values, outArgsSize); } else { diff --git a/lib/Runtime/Language/StackTraceArguments.cpp b/lib/Runtime/Language/StackTraceArguments.cpp index 4cc379e4b93..a3b06192dbd 100644 --- a/lib/Runtime/Language/StackTraceArguments.cpp +++ b/lib/Runtime/Language/StackTraceArguments.cpp @@ -68,9 +68,10 @@ namespace Js { types = 0; if (!walker.IsCallerGlobalFunction()) { - int64 numberOfArguments = walker.GetCallInfo()->Count; + const CallInfo callInfo = walker.GetCallInfo(); + int64 numberOfArguments = callInfo.Count; if (numberOfArguments > 0) numberOfArguments --; // Don't consider 'this' - if (walker.GetCallInfo()->Flags & Js::CallFlags_ExtraArg) + if (callInfo.Flags & Js::CallFlags_ExtraArg) { Assert(numberOfArguments > 0 ); // skip the last FrameDisplay argument. diff --git a/lib/Runtime/Library/ArgumentsObject.cpp b/lib/Runtime/Library/ArgumentsObject.cpp index a4d2c91b13c..998cf594aff 100644 --- a/lib/Runtime/Library/ArgumentsObject.cpp +++ b/lib/Runtime/Library/ArgumentsObject.cpp @@ -67,9 +67,9 @@ namespace Js AssertMsg(JavascriptOperators::GetTypeId(funcCaller) == TypeIds_Function, "non function caller"); - CallInfo const *callInfo = walker->GetCallInfo(); - uint32 paramCount = callInfo->Count; - CallFlags flags = callInfo->Flags; + const CallInfo callInfo = walker->GetCallInfo(); + uint32 paramCount = callInfo.Count; + CallFlags flags = callInfo.Flags; if (paramCount == 0 || (flags & CallFlags_Eval)) { diff --git a/lib/Runtime/Library/ArrayBuffer.cpp b/lib/Runtime/Library/ArrayBuffer.cpp index e4072ca38a0..cfddedf8fdc 100644 --- a/lib/Runtime/Library/ArrayBuffer.cpp +++ b/lib/Runtime/Library/ArrayBuffer.cpp @@ -297,6 +297,12 @@ namespace Js if (args.Info.Count >= 3) { newBufferLength = ToIndex(args[2], JSERR_ArrayLengthConstructIncorrect, scriptContext, MaxArrayBufferLength); + + // ToIndex above can call user script (valueOf) which can detach the buffer + if (arrayBuffer->IsDetached()) + { + JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("ArrayBuffer.transfer")); + } } return arrayBuffer->TransferInternal(newBufferLength); diff --git a/lib/Runtime/Library/GlobalObject.cpp b/lib/Runtime/Library/GlobalObject.cpp index c03e041a42e..0d4771fc9f0 100644 --- a/lib/Runtime/Library/GlobalObject.cpp +++ b/lib/Runtime/Library/GlobalObject.cpp @@ -952,17 +952,16 @@ namespace Js { JavascriptError::ThrowStackOverflowError(scriptContext); } - JavascriptError::MapAndThrowError(scriptContext, hrCodeGen); - } - else - { - if (se.ei.scode == JSERR_AsmJsCompileError) + else if (hrCodeGen == JSERR_AsmJsCompileError) { // if asm.js compilation succeeded, retry with asm.js disabled grfscr |= fscrNoAsmJs; - se.Clear(); return DefaultEvalHelper(scriptContext, source, sourceLength, moduleID, grfscr, pszTitle, registerDocument, isIndirect, strictMode); } + JavascriptError::MapAndThrowError(scriptContext, hrCodeGen); + } + else + { Assert(funcBody != nullptr); funcBody->SetDisplayName(pszTitle); diff --git a/lib/Runtime/Library/JavascriptArray.cpp b/lib/Runtime/Library/JavascriptArray.cpp index 078a02dd533..de83bba1786 100644 --- a/lib/Runtime/Library/JavascriptArray.cpp +++ b/lib/Runtime/Library/JavascriptArray.cpp @@ -477,6 +477,11 @@ namespace Js bool JavascriptArray::IsMissingItem(uint32 index) { + if (this->length <= index) + { + return false; + } + bool isIntArray = false, isFloatArray = false; this->GetArrayTypeAndConvert(&isIntArray, &isFloatArray); @@ -3145,11 +3150,14 @@ namespace Js if (scriptContext->GetConfig()->IsES6IsConcatSpreadableEnabled()) { - if (JavascriptOperators::IsConcatSpreadable(aItem)) + spreadableCheckedAndTrue = JavascriptOperators::IsConcatSpreadable(aItem); + if (!JavascriptNativeIntArray::Is(pDestArray)) { - spreadableCheckedAndTrue = true; + ConcatArgs(pDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest, spreadableCheckedAndTrue); + return pDestArray; } - else + + if(!spreadableCheckedAndTrue) { pDestArray->SetItem(idxDest, aItem, PropertyOperation_ThrowIfNotExtensible); idxDest = idxDest + 1; @@ -3218,11 +3226,14 @@ namespace Js if (scriptContext->GetConfig()->IsES6IsConcatSpreadableEnabled()) { - if (JavascriptOperators::IsConcatSpreadable(aItem)) + spreadableCheckedAndTrue = JavascriptOperators::IsConcatSpreadable(aItem); + if (!JavascriptNativeFloatArray::Is(pDestArray)) { - spreadableCheckedAndTrue = true; + ConcatArgs(pDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest, spreadableCheckedAndTrue); + return pDestArray; } - else + + if(!spreadableCheckedAndTrue) { pDestArray->SetItem(idxDest, aItem, PropertyOperation_ThrowIfNotExtensible); @@ -5302,6 +5313,11 @@ namespace Js pArr->SetHasNoMissingValues(false); } + // Above FillFromPrototypes call can change the length of the array. Our segment calculation below will + // not work with the stale length. Update the length. + // Note : since we are reversing the whole segment below - the functionality is not spec compliant already. + length = pArr->length; + SparseArraySegmentBase* seg = pArr->head; SparseArraySegmentBase *prevSeg = nullptr; SparseArraySegmentBase *nextSeg = nullptr; @@ -5783,7 +5799,7 @@ namespace Js // Prototype lookup for missing elements if (!pArr->HasNoMissingValues()) { - for (uint32 i = 0; i < newLen; i++) + for (uint32 i = 0; i < newLen && (i + start) < pArr->length; i++) { // array type might be changed in the below call to DirectGetItemAtFull // need recheck array type before checking array item [i + start] @@ -10132,6 +10148,9 @@ namespace Js if (JavascriptArray::Is(newObj)) { +#if ENABLE_COPYONACCESS_ARRAY + JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray(newObj); +#endif newArr = JavascriptArray::FromVar(newObj); } } @@ -10182,6 +10201,9 @@ namespace Js if (JavascriptArray::Is(newObj)) { +#if ENABLE_COPYONACCESS_ARRAY + JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray(newObj); +#endif newArr = JavascriptArray::FromVar(newObj); } } @@ -10292,6 +10314,9 @@ namespace Js // If the new object we created is an array, remember that as it will save us time setting properties in the object below if (JavascriptArray::Is(newObj)) { +#if ENABLE_COPYONACCESS_ARRAY + JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray(newObj); +#endif newArr = JavascriptArray::FromVar(newObj); } else if (TypedArrayBase::Is(newObj)) diff --git a/lib/Runtime/Library/JavascriptFunction.cpp b/lib/Runtime/Library/JavascriptFunction.cpp index 036dcfed809..e9b6b26b9a3 100644 --- a/lib/Runtime/Library/JavascriptFunction.cpp +++ b/lib/Runtime/Library/JavascriptFunction.cpp @@ -1110,6 +1110,7 @@ namespace Js PROBE_STACK(scriptContext, outArgs.Info.Count * sizeof(Var) + Js::Constants::MinStackDefault); // args + function call outArgsSize = outArgs.Info.Count * sizeof(Var); outArgs.Values = (Var*)_alloca(outArgsSize); + ZeroMemory(outArgs.Values, outArgsSize); } else { @@ -2786,9 +2787,9 @@ void __cdecl _alloca_probe_16() Var args = nullptr; //Create a copy of the arguments and return it. - CallInfo const *callInfo = walker.GetCallInfo(); + const CallInfo callInfo = walker.GetCallInfo(); args = JavascriptOperators::LoadHeapArguments( - this, callInfo->Count - 1, + this, callInfo.Count - 1, walker.GetJavascriptArgs(), scriptContext->GetLibrary()->GetNull(), scriptContext->GetLibrary()->GetNull(), diff --git a/lib/Runtime/Library/JavascriptLibrary.cpp b/lib/Runtime/Library/JavascriptLibrary.cpp index 21009bccc5c..8e00d737a9c 100644 --- a/lib/Runtime/Library/JavascriptLibrary.cpp +++ b/lib/Runtime/Library/JavascriptLibrary.cpp @@ -1053,7 +1053,6 @@ namespace Js // during PostCollectCallBack before Dispose deleting the script context. scriptContext->ResetWeakReferenceDictionaryList(); scriptContext->SetIsFinalized(); - scriptContext->GetThreadContext()->UnregisterScriptContext(scriptContext); scriptContext->MarkForClose(); } } diff --git a/lib/Runtime/Library/JavascriptProxy.cpp b/lib/Runtime/Library/JavascriptProxy.cpp index 610fe289a78..ec2a24f9953 100644 --- a/lib/Runtime/Library/JavascriptProxy.cpp +++ b/lib/Runtime/Library/JavascriptProxy.cpp @@ -1821,8 +1821,15 @@ namespace Js return JavascriptOperators::SetItem(receiver, targetObj, indexVal, newValue, scriptContext, PropertyOperationFlags::PropertyOperation_None, skipPrototypeCheck); } case SetPropertyTrapKind::SetPropertyWPCacheKind: + { + Var name = GetName(requestContext, propertyId); + if (!JavascriptString::Is(name) || !VirtualTableInfo::HasVirtualTable(JavascriptString::FromVar(name))) + { + name = nullptr; + } return JavascriptOperators::SetPropertyWPCache(receiver, targetObj, propertyId, newValue, requestContext, - static_cast(GetName(requestContext, propertyId)), PropertyOperationFlags::PropertyOperation_None); + static_cast(name), PropertyOperationFlags::PropertyOperation_None); + } default: Assert(FALSE); } diff --git a/test/Array/Array_TypeConfusion_bugs.js b/test/Array/Array_TypeConfusion_bugs.js index e1867424929..43430fbc33a 100644 --- a/test/Array/Array_TypeConfusion_bugs.js +++ b/test/Array/Array_TypeConfusion_bugs.js @@ -593,5 +593,32 @@ var tests = [ assert.areEqual(101, arr.length); } }, + { + name: "Heap overread when splice mutates the array when executing slice", + body: function () + { + var getterCalled = false; + var a = [1, 2]; + for (var i = 0; i < 100 * 1024; i++) { + a.push(i); + } + delete a[0]; // Make a missing item + var protoObj = [11]; + Object.defineProperty(protoObj, '0', { + get : function () { + getterCalled = true; + Object.setPrototypeOf(a, Array.prototype); + a.splice(0); // head seg is now length=0 + return 42; + }, + configurable : true + }); + Object.setPrototypeOf(a, protoObj); + var b = a.slice(); + assert.isTrue(getterCalled); + assert.areEqual(0, a.length, "Getter will splice the array to zero length"); + assert.areEqual(100 * 1024 + 2, b.length, "Validating that slice will return the full array even though splice is deleting the whole array"); + } + }, ]; testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/Array/array_conv_src.js b/test/Array/array_conv_src.js new file mode 100644 index 00000000000..4b9610902b5 --- /dev/null +++ b/test/Array/array_conv_src.js @@ -0,0 +1,36 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +"use strict"; + +function func(a, b, c) { + a[0] = 1.2; + b[0] = c; + a[1] = 2.2; + a[0] = 2.3023e-320; +} + +function main() { + var a = [1.1, 2.2]; + var b = new Uint32Array(100); + + // force to optimize + for (var i = 0; i < 0x10000; i++) + func(a, b, i); + + func(a, b, { + valueOf: function () { + a[0] = {}; + + return 0; + } + }); + + a[0].toString(); +} + +main(); + +WScript.Echo('pass'); \ No newline at end of file diff --git a/test/Array/rlexe.xml b/test/Array/rlexe.xml index 113ba7db645..3e990826167 100644 --- a/test/Array/rlexe.xml +++ b/test/Array/rlexe.xml @@ -733,4 +733,9 @@ bug_9575461.js + + + array_conv_src.js + + diff --git a/test/AsmJs/nested.baseline b/test/AsmJs/nested.baseline new file mode 100644 index 00000000000..898172b0cbf --- /dev/null +++ b/test/AsmJs/nested.baseline @@ -0,0 +1,2 @@ +closure functions are not allowed +Asm.js compilation failed. diff --git a/test/AsmJs/nested.js b/test/AsmJs/nested.js new file mode 100644 index 00000000000..33c1f5378fe --- /dev/null +++ b/test/AsmJs/nested.js @@ -0,0 +1,12 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +function AsmModule() { + "use asm"; + function f() { + function g() { } + } +} +AsmModule(); diff --git a/test/AsmJs/qmarkbug.baseline b/test/AsmJs/qmarkbug.baseline index 7bb6ab56132..3fb209591f2 100644 --- a/test/AsmJs/qmarkbug.baseline +++ b/test/AsmJs/qmarkbug.baseline @@ -1,11 +1,11 @@ -qmarkbug.js(9, 3) +qmarkbug.js(6, 5) Asm.js Compilation Error function : None::f Conditional expressions must be of type int, double, or float Asm.js compilation failed. -qmarkbug.js(25, 3) +qmarkbug.js(6, 5) Asm.js Compilation Error function : None::f Conditional expressions must be of type int, double, or float diff --git a/test/AsmJs/qmarkbug.js b/test/AsmJs/qmarkbug.js index 6a35e3e1f87..58a646c2aeb 100644 --- a/test/AsmJs/qmarkbug.js +++ b/test/AsmJs/qmarkbug.js @@ -3,34 +3,36 @@ // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- -var asmModule = -(function(stdlib, foreign, heap) { 'use asm'; var Uint8ArrayView = new stdlib.Uint8Array(heap); - var Int16ArrayView = new stdlib.Int16Array(heap); - function f(d0, i1) - { - d0 = +d0; - i1 = i1|0; - var i4 = 0; - i4 = ((0) ? 0 : ((Uint8ArrayView[0]))); - return +((-7.555786372591432e+22)); - } - return f; }) +eval(` +(function(stdlib, foreign, heap) { + 'use asm'; + var Uint8ArrayView = new stdlib.Uint8Array(heap); + var Int16ArrayView = new stdlib.Int16Array(heap); + function f(d0, i1) + { + d0 = +d0; + i1 = i1|0; + var i4 = 0; + i4 = ((0) ? 0 : ((Uint8ArrayView[0]))); + return +((-7.555786372591432e+22)); + } + return f; +})(this, {}, new ArrayBuffer(1<<24)); +`); - var asmHeap = new ArrayBuffer(1<<24); - var asmFun = asmModule(this, {}, asmHeap); - asmFun(); - var asmModule = -(function(stdlib, foreign, heap) { 'use asm'; var Uint8ArrayView = new stdlib.Uint8Array(heap); - var Int16ArrayView = new stdlib.Int16Array(heap); - function f(d0, i1) - { - d0 = +d0; - i1 = i1|0; - var i4 = 0; - i4 = ((0) ? ((Uint8ArrayView[0])): 0 ); - return +((-7.555786372591432e+22)); - } - return f; }) - - var asmFun = asmModule(this, {}, asmHeap); - asmFun(); +eval(` +(function(stdlib, foreign, heap) { + 'use asm'; + var Uint8ArrayView = new stdlib.Uint8Array(heap); + var Int16ArrayView = new stdlib.Int16Array(heap); + function f(d0, i1) + { + d0 = +d0; + i1 = i1|0; + var i4 = 0; + i4 = ((0) ? ((Uint8ArrayView[0])): 0 ); + return +((-7.555786372591432e+22)); + } + return f; +})(this, {}, new ArrayBuffer(1<<24)); +`); diff --git a/test/AsmJs/rlexe.xml b/test/AsmJs/rlexe.xml index 26962b43f96..8868653f7a8 100644 --- a/test/AsmJs/rlexe.xml +++ b/test/AsmJs/rlexe.xml @@ -825,4 +825,11 @@ -asmjs -maic:0 + + + nested.js + nested.baseline + -forcedeferparse -testtrace:asmjs -simdjs + + diff --git a/test/AsmJs/shadowingBug.baseline b/test/AsmJs/shadowingBug.baseline index 5d7b043a3af..53dca1b58f2 100644 --- a/test/AsmJs/shadowingBug.baseline +++ b/test/AsmJs/shadowingBug.baseline @@ -1,18 +1,18 @@ -shadowingBug.js(7, 97) +shadowingBug.js(1, 97) Asm.js Compilation Error function : None::f1 Invalid identifier f64 Asm.js compilation failed. +0 -shadowingBug.js(8, 97) +shadowingBug.js(1, 97) Asm.js Compilation Error function : None::f1 Invalid identifier f64 Asm.js compilation failed. +NaN Var declaration with non-constant Asm.js compilation failed. 0 -NaN -0 0 diff --git a/test/AsmJs/shadowingBug.js b/test/AsmJs/shadowingBug.js index 8302c010a85..aa995384ede 100644 --- a/test/AsmJs/shadowingBug.js +++ b/test/AsmJs/shadowingBug.js @@ -4,9 +4,9 @@ //------------------------------------------------------------------------------------------------------- var buffer = new ArrayBuffer(1<<20); -print((function (stdlib,foreign,buffer) { "use asm"; var f64 = new stdlib.Float64Array(buffer); function f1(){ var f64 = 1.; f64[0] = 0.0;return +0.0;} return f1;})(this,{},buffer)()); -print((function (stdlib,foreign,buffer) { "use asm"; var f64 = new stdlib.Float64Array(buffer); function f1(){ var f64 = 1.; return +f64[0];} return f1;})(this,{},buffer)()); -print((function (stdlib,foreign,buffer) { "use asm"; const a = 10; function f1(){ var a =0; var b = a; return b|0;} return f1;})(this,{},buffer)()); +eval('print((function (stdlib,foreign,buffer) { "use asm"; var f64 = new stdlib.Float64Array(buffer); function f1(){ var f64 = 1.; f64[0] = 0.0;return +0.0;} return f1;})(this,{},buffer)())'); +eval('print((function (stdlib,foreign,buffer) { "use asm"; var f64 = new stdlib.Float64Array(buffer); function f1(){ var f64 = 1.; return +f64[0];} return f1;})(this,{},buffer)())'); +eval('print((function (stdlib,foreign,buffer) { "use asm"; const a = 10; function f1(){ var a =0; var b = a; return b|0;} return f1;})(this,{},buffer)())'); var f64Arr = new Float64Array(buffer); print(f64Arr[0]); \ No newline at end of file diff --git a/test/es6/default-splitscope.js b/test/es6/default-splitscope.js index 0a5da26a816..021ebce8df8 100644 --- a/test/es6/default-splitscope.js +++ b/test/es6/default-splitscope.js @@ -159,6 +159,22 @@ var tests = [ return a; } assert.areEqual(10, f11()(), "Recursive call to the function from the body scope returns the right value when eval is there in the body"); + + function f13() { + var a = function jnvgfg(sfgnmj = function ccunlk() { jnvgfg(undefined, 1); }, b) { + if (b) { + assert.areEqual(undefined, jnvgfg, "This refers to the instance in the body and the value of the function expression is not copied over"); + } + var jnvgfg = 10; + if (!b) { + sfgnmj(); + return 100; + } + }; + assert.areEqual(100, a(), "After the recursion the right value is returned by the split scoped function"); + }; + f13(); + } }, { diff --git a/test/es6/lambda-params-shadow.js b/test/es6/lambda-params-shadow.js index 1e45ab40bf3..69c07a584e5 100644 --- a/test/es6/lambda-params-shadow.js +++ b/test/es6/lambda-params-shadow.js @@ -18,7 +18,14 @@ class B extends A { } } let b = new B(); -if (count !== 3) { +class async extends A { + constructor() { + super(); + let Q = async A => { A }; + } +} +let a = new async(); +if (count !== 4) { WScript.Echo('fail'); } diff --git a/test/es6/proxybugs.js b/test/es6/proxybugs.js index b7db667450b..11419409484 100644 --- a/test/es6/proxybugs.js +++ b/test/es6/proxybugs.js @@ -1,107 +1,117 @@ -//------------------------------------------------------------------------------------------------------- -// Copyright (C) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. -//------------------------------------------------------------------------------------------------------- - -WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); - -var tests = [ - { - name: "Setting proxy object on Map and WeakMap", - body() { - [WeakMap, Map].forEach(function(ctor) { - var target = {}; - let p = new Proxy(target, {}); - let map = new ctor(); - map.set(p, 101); - assert.areEqual(map.get(p), 101, ctor.name + " map should be able to set and get the proxy object"); - p.x = 20; - assert.areEqual(target.x, 20, "target object should work as expected even after proxy object is added to map"); - }); - } - }, - { - name: "Setting proxy object on Map and WeakMap - multiple sets and delete", - body() { - [WeakMap, Map].forEach(function(ctor) { - var target = {}; - let p = new Proxy(target, {}); - let map = new ctor(); - map.set(p, 101); - assert.areEqual(map.get(p), 101); - map.delete(p); - assert.areEqual(map.get(p), undefined, ctor.name + " map can remove the proxy object properly"); - map.set(p, 102); - assert.areEqual(map.get(p), 102, ctor.name + " proxy object can be set again and it returns 102"); - p.x = 20; - assert.areEqual(target.x, 20, "target object should work as expected even after proxy object is added to map"); - }); - } - }, - { - name: "Assertion validation : returning descriptor during getOwnPropertyDescriptor should not pollute the descriptor", - body() { - var target = {}; - var handler = {}; - var getOwnPropertyDescriptorCalled = false; - handler['defineProperty'] = function () { - assert.fail("This function will not be called as 'getOwnPropertyDescriptor' will add accessor"); - }; - - handler['getOwnPropertyDescriptor'] = function (t, property) { - getOwnPropertyDescriptorCalled = true; - Object.defineProperty(t, 'abc', { set: function () { } }); - return Reflect.getOwnPropertyDescriptor(t, property); - }; - - var proxy = new Proxy(target, handler); - proxy.abc = undefined; - assert.isTrue(getOwnPropertyDescriptorCalled); - } - }, - { - name: "Assertion validation : returning descriptor with writable false should not defineProperty again.", - body() { - var target = {}; - var handler = {}; - var getOwnPropertyDescriptorCalled = false; - handler['defineProperty'] = function () { - assert.fail("This function will not be called as 'getOwnPropertyDescriptor' will add property with writable false"); - }; - - handler['getOwnPropertyDescriptor'] = function (t, property) { - getOwnPropertyDescriptorCalled = true; - Object.defineProperty(t, 'abc', { value : 1, writable : false }); - return Reflect.getOwnPropertyDescriptor(t, property); - }; - - var proxy = new Proxy(target, handler); - proxy.abc = undefined; - assert.isTrue(getOwnPropertyDescriptorCalled); - } - }, - { - name: "No property found at getOwnPropertyDescriptor will call defineProperty", - body() { - var target = {}; - var handler = {}; - var definePropertyCalled = false; - var getOwnPropertyDescriptorCalled = false; - handler['defineProperty'] = function () { - definePropertyCalled = true; - }; - - handler['getOwnPropertyDescriptor'] = function (t, property) { - getOwnPropertyDescriptorCalled = true; - return Reflect.getOwnPropertyDescriptor(t, property); - }; - - var proxy = new Proxy(target, handler); - proxy.abc = undefined; - assert.isTrue(definePropertyCalled); - assert.isTrue(getOwnPropertyDescriptorCalled); - } - }, +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); + +var tests = [ + { + name: "Setting proxy object on Map and WeakMap", + body() { + [WeakMap, Map].forEach(function(ctor) { + var target = {}; + let p = new Proxy(target, {}); + let map = new ctor(); + map.set(p, 101); + assert.areEqual(map.get(p), 101, ctor.name + " map should be able to set and get the proxy object"); + p.x = 20; + assert.areEqual(target.x, 20, "target object should work as expected even after proxy object is added to map"); + }); + } + }, + { + name: "Setting proxy object on Map and WeakMap - multiple sets and delete", + body() { + [WeakMap, Map].forEach(function(ctor) { + var target = {}; + let p = new Proxy(target, {}); + let map = new ctor(); + map.set(p, 101); + assert.areEqual(map.get(p), 101); + map.delete(p); + assert.areEqual(map.get(p), undefined, ctor.name + " map can remove the proxy object properly"); + map.set(p, 102); + assert.areEqual(map.get(p), 102, ctor.name + " proxy object can be set again and it returns 102"); + p.x = 20; + assert.areEqual(target.x, 20, "target object should work as expected even after proxy object is added to map"); + }); + } + }, + { + name: "Assertion validation : returning descriptor during getOwnPropertyDescriptor should not pollute the descriptor", + body() { + var target = {}; + var handler = {}; + var getOwnPropertyDescriptorCalled = false; + handler['defineProperty'] = function () { + assert.fail("This function will not be called as 'getOwnPropertyDescriptor' will add accessor"); + }; + + handler['getOwnPropertyDescriptor'] = function (t, property) { + getOwnPropertyDescriptorCalled = true; + Object.defineProperty(t, 'abc', { set: function () { } }); + return Reflect.getOwnPropertyDescriptor(t, property); + }; + + var proxy = new Proxy(target, handler); + proxy.abc = undefined; + assert.isTrue(getOwnPropertyDescriptorCalled); + } + }, + { + name: "Assertion validation : returning descriptor with writable false should not defineProperty again.", + body() { + var target = {}; + var handler = {}; + var getOwnPropertyDescriptorCalled = false; + handler['defineProperty'] = function () { + assert.fail("This function will not be called as 'getOwnPropertyDescriptor' will add property with writable false"); + }; + + handler['getOwnPropertyDescriptor'] = function (t, property) { + getOwnPropertyDescriptorCalled = true; + Object.defineProperty(t, 'abc', { value : 1, writable : false }); + return Reflect.getOwnPropertyDescriptor(t, property); + }; + + var proxy = new Proxy(target, handler); + proxy.abc = undefined; + assert.isTrue(getOwnPropertyDescriptorCalled); + } + }, + { + name: "No property found at getOwnPropertyDescriptor will call defineProperty", + body() { + var target = {}; + var handler = {}; + var definePropertyCalled = false; + var getOwnPropertyDescriptorCalled = false; + handler['defineProperty'] = function () { + definePropertyCalled = true; + }; + + handler['getOwnPropertyDescriptor'] = function (t, property) { + getOwnPropertyDescriptorCalled = true; + return Reflect.getOwnPropertyDescriptor(t, property); + }; + + var proxy = new Proxy(target, handler); + proxy.abc = undefined; + assert.isTrue(definePropertyCalled); + assert.isTrue(getOwnPropertyDescriptorCalled); + } + }, + { + name: "Type confusion in JavascriptProxy::SetPropertyTrap when using a Symbol", + body: function () { + try{ Reflect.set((new Proxy({}, {has: function(){ return true; }})), 'abc', 0x44444444, new Uint32Array); } catch(e){} + try{ Reflect.set((new Proxy({}, {has: function(){ return true; }})), 'abc', 0x44444444, new Uint32Array); } catch(e){} + + var obj1 = Object.create(new Proxy({}, {})); + obj1[Symbol.species] = 0; + } + }, { name: "Assertion validation : revoking the proxy in getPrototypeOf trap", body() { @@ -119,190 +129,190 @@ var tests = [ assert.isTrue(trapCalled); } }, - { - name: "Assertion validation : revoking the proxy in setPrototypeOf trap", - body() { - var trapCalled = false; - var handler = { - setPrototypeOf : function(a, b) { - trapCalled = true; - obj.revoke(); - return true; - } - }; - - var obj = Proxy.revocable({}, handler); - var ret = Object.setPrototypeOf(obj.proxy, {}); - assert.isTrue(trapCalled); - } - }, - { - name: "Assertion validation : revoking the proxy in isExtensible trap", - body() { - var trapCalled = false; - var handler = { - isExtensible : function(a, b) { - trapCalled = true; - obj.revoke(); - return true; - } - }; - - var obj = Proxy.revocable({}, handler); - var ret = Object.isExtensible(obj.proxy); - assert.isTrue(trapCalled); - } - }, - { - name: "Assertion validation : revoking the proxy in preventExtensions trap", - body() { - var trapCalled = false; - var handler = { - preventExtensions : function(a, b) { - trapCalled = true; - obj.revoke(); - } - }; - - var obj = Proxy.revocable({}, handler); - Object.preventExtensions(obj.proxy); - assert.isTrue(trapCalled); - } - }, - { - name: "Assertion validation : revoking the proxy in getOwnPropertyDescriptor trap", - body() { - var trapCalled = false; - var handler = { - getOwnPropertyDescriptor : function(a, b, c) { - trapCalled = true; - obj.revoke(); - } - }; - - var obj = Proxy.revocable({}, handler); - assert.throws( () => { Object.getOwnPropertyDescriptor(obj.proxy, 'a'); }, TypeError); - assert.isTrue(trapCalled); - } - }, - { - name: "Assertion validation : revoking the proxy in has trap", - body() { - var trapCalled = false; - var handler = { - has : function(a, b, c) { - trapCalled = true; - obj.revoke(); - return false; - } - }; - - var obj = Proxy.revocable({}, handler); - 'a' in obj.proxy; - assert.isTrue(trapCalled); - } - }, - { - name: "Assertion validation : revoking the proxy in get trap", - body() { - var trapCalled = false; - var handler = { - get : function (a, b, c) { - trapCalled = true; - obj.revoke(); - return {}; - } - }; - - var obj = Proxy.revocable({}, handler); - var ret = obj.proxy.a; - assert.isTrue(trapCalled); - } - }, - { - name: "Assertion validation : revoking the proxy in set trap", - body() { - var trapCalled = false; - var handler = { - set : function (a, b, c) { - trapCalled = true; - obj.revoke(); - return {}; - } - }; - - var obj = Proxy.revocable({}, handler); - obj.proxy.a = 10; - assert.isTrue(trapCalled); - } - }, - { - name: "Assertion validation : revoking the proxy in deleteProperty trap", - body() { - var trapCalled = false; - var handler = { - deleteProperty : function (a, b, c) { - trapCalled = true; - obj.revoke(); - return {}; - } - }; - - var obj = Proxy.revocable({}, handler); - delete obj.proxy.a; - assert.isTrue(trapCalled); - } - }, - { - name: "Assertion validation : revoking the proxy in ownKeys trap", - body() { - var trapCalled = false; - var handler = { - ownKeys : function (a, b, c) { - trapCalled = true; - obj.revoke(); - return {}; - } - }; - - var obj = Proxy.revocable({}, handler); - Object.keys(obj.proxy); - assert.isTrue(trapCalled); - } - }, - { - name: "Assertion validation : revoking the proxy in apply trap", - body() { - var trapCalled = false; - var handler = { - get apply () { - trapCalled = true; - obj.revoke(); - } - }; - - var obj = Proxy.revocable(() => {}, handler); - obj.proxy(); - assert.isTrue(trapCalled); - } - }, - { - name: "Assertion validation : revoking the proxy in construct trap", - body() { - var trapCalled = false; - var handler = { - get construct () { - trapCalled = true; - obj.revoke(); - return () => { return {}; }; - } - }; - - var obj = Proxy.revocable(() => {}, handler); - new obj.proxy(); - assert.isTrue(trapCalled); - } - }, -]; - -testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); + { + name: "Assertion validation : revoking the proxy in setPrototypeOf trap", + body() { + var trapCalled = false; + var handler = { + setPrototypeOf : function(a, b) { + trapCalled = true; + obj.revoke(); + return true; + } + }; + + var obj = Proxy.revocable({}, handler); + var ret = Object.setPrototypeOf(obj.proxy, {}); + assert.isTrue(trapCalled); + } + }, + { + name: "Assertion validation : revoking the proxy in isExtensible trap", + body() { + var trapCalled = false; + var handler = { + isExtensible : function(a, b) { + trapCalled = true; + obj.revoke(); + return true; + } + }; + + var obj = Proxy.revocable({}, handler); + var ret = Object.isExtensible(obj.proxy); + assert.isTrue(trapCalled); + } + }, + { + name: "Assertion validation : revoking the proxy in preventExtensions trap", + body() { + var trapCalled = false; + var handler = { + preventExtensions : function(a, b) { + trapCalled = true; + obj.revoke(); + } + }; + + var obj = Proxy.revocable({}, handler); + Object.preventExtensions(obj.proxy); + assert.isTrue(trapCalled); + } + }, + { + name: "Assertion validation : revoking the proxy in getOwnPropertyDescriptor trap", + body() { + var trapCalled = false; + var handler = { + getOwnPropertyDescriptor : function(a, b, c) { + trapCalled = true; + obj.revoke(); + } + }; + + var obj = Proxy.revocable({}, handler); + assert.throws( () => { Object.getOwnPropertyDescriptor(obj.proxy, 'a'); }, TypeError); + assert.isTrue(trapCalled); + } + }, + { + name: "Assertion validation : revoking the proxy in has trap", + body() { + var trapCalled = false; + var handler = { + has : function(a, b, c) { + trapCalled = true; + obj.revoke(); + return false; + } + }; + + var obj = Proxy.revocable({}, handler); + 'a' in obj.proxy; + assert.isTrue(trapCalled); + } + }, + { + name: "Assertion validation : revoking the proxy in get trap", + body() { + var trapCalled = false; + var handler = { + get : function (a, b, c) { + trapCalled = true; + obj.revoke(); + return {}; + } + }; + + var obj = Proxy.revocable({}, handler); + var ret = obj.proxy.a; + assert.isTrue(trapCalled); + } + }, + { + name: "Assertion validation : revoking the proxy in set trap", + body() { + var trapCalled = false; + var handler = { + set : function (a, b, c) { + trapCalled = true; + obj.revoke(); + return {}; + } + }; + + var obj = Proxy.revocable({}, handler); + obj.proxy.a = 10; + assert.isTrue(trapCalled); + } + }, + { + name: "Assertion validation : revoking the proxy in deleteProperty trap", + body() { + var trapCalled = false; + var handler = { + deleteProperty : function (a, b, c) { + trapCalled = true; + obj.revoke(); + return {}; + } + }; + + var obj = Proxy.revocable({}, handler); + delete obj.proxy.a; + assert.isTrue(trapCalled); + } + }, + { + name: "Assertion validation : revoking the proxy in ownKeys trap", + body() { + var trapCalled = false; + var handler = { + ownKeys : function (a, b, c) { + trapCalled = true; + obj.revoke(); + return {}; + } + }; + + var obj = Proxy.revocable({}, handler); + Object.keys(obj.proxy); + assert.isTrue(trapCalled); + } + }, + { + name: "Assertion validation : revoking the proxy in apply trap", + body() { + var trapCalled = false; + var handler = { + get apply () { + trapCalled = true; + obj.revoke(); + } + }; + + var obj = Proxy.revocable(() => {}, handler); + obj.proxy(); + assert.isTrue(trapCalled); + } + }, + { + name: "Assertion validation : revoking the proxy in construct trap", + body() { + var trapCalled = false; + var handler = { + get construct () { + trapCalled = true; + obj.revoke(); + return () => { return {}; }; + } + }; + + var obj = Proxy.revocable(() => {}, handler); + new obj.proxy(); + assert.isTrue(trapCalled); + } + }, +]; + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });