diff --git a/Build/NuGet/.pack-version b/Build/NuGet/.pack-version index 10c088013f8..6a126f402d5 100644 --- a/Build/NuGet/.pack-version +++ b/Build/NuGet/.pack-version @@ -1 +1 @@ -1.7.4 +1.7.5 diff --git a/lib/Backend/BackwardPass.cpp b/lib/Backend/BackwardPass.cpp index 7b83fcbda92..4193f4a19d3 100644 --- a/lib/Backend/BackwardPass.cpp +++ b/lib/Backend/BackwardPass.cpp @@ -7853,6 +7853,14 @@ BackwardPass::RemoveEmptyLoopAfterMemOp(Loop *loop) outerBlock->RemovePred(head, this->func->m_fg); landingPad->RemoveSucc(head, this->func->m_fg); + Assert(landingPad->GetSuccList()->Count() == 0); + + IR::Instr* firstOuterInstr = outerBlock->GetFirstInstr(); + AssertOrFailFast(firstOuterInstr->IsLabelInstr() && !landingPad->GetLastInstr()->EndsBasicBlock()); + IR::LabelInstr* label = firstOuterInstr->AsLabelInstr(); + // Add br to Outer block to keep coherence between branches and flow graph + IR::BranchInstr *outerBr = IR::BranchInstr::New(Js::OpCode::Br, label, this->func); + landingPad->InsertAfter(outerBr); this->func->m_fg->AddEdge(landingPad, outerBlock); this->func->m_fg->RemoveBlock(head, nullptr); diff --git a/lib/Backend/GlobOpt.cpp b/lib/Backend/GlobOpt.cpp index 5d3384eaae9..f634c6b53b5 100644 --- a/lib/Backend/GlobOpt.cpp +++ b/lib/Backend/GlobOpt.cpp @@ -12946,7 +12946,9 @@ GlobOpt::ProcessValueKills(IR::Instr *const instr) Assert(kills.KillsTypedArrayHeadSegmentLengths()); // - Calls need to kill the value types of values in the following list. For instance, calls can transform a JS array - // into an ES5 array, so any definitely-array value types need to be killed. Update the value types. + // into an ES5 array, so any definitely-array value types need to be killed. Also, VirtualTypeArrays do not have + // bounds checks; this can be problematic if the array is detached, so check to ensure that it is a virtual array. + // Update the value types to likley to ensure a bailout that asserts Array type is generated. // - Calls also need to kill typed array head segment lengths. A typed array's array buffer may be transferred to a web // worker, in which case the typed array's length is set to zero. for(auto it = valuesToKillOnCalls->GetIterator(); it.IsValid(); it.MoveNext()) @@ -12956,7 +12958,7 @@ GlobOpt::ProcessValueKills(IR::Instr *const instr) Assert( valueInfo->IsArrayOrObjectWithArray() || valueInfo->IsOptimizedTypedArray() && valueInfo->AsArrayValueInfo()->HeadSegmentLengthSym()); - if(valueInfo->IsArrayOrObjectWithArray()) + if (valueInfo->IsArrayOrObjectWithArray() || valueInfo->IsOptimizedVirtualTypedArray()) { ChangeValueType(nullptr, value, valueInfo->Type().ToLikely(), false); continue; @@ -18165,8 +18167,8 @@ GlobOpt::TrackTempObjectSyms(IR::Instr * instr, IR::RegOpnd * opnd) (instr->GetSrc1()->IsRegOpnd() && globOptData.canStoreTempObjectSyms->Test(instr->GetSrc1()->AsRegOpnd()->m_sym->m_id)) && (!instr->GetSrc2() || (instr->GetSrc2()->IsRegOpnd() && globOptData.canStoreTempObjectSyms->Test(instr->GetSrc2()->AsRegOpnd()->m_sym->m_id)))); - Assert(!canStoreTemp || instr->dstIsTempObject); - Assert(!maybeTemp || instr->dstIsTempObject); + AssertOrFailFast(!canStoreTemp || instr->dstIsTempObject); + AssertOrFailFast(!maybeTemp || instr->dstIsTempObject); } // Need to get the var equiv sym as assignment of type specialized sym kill the var sym value anyway. diff --git a/lib/Backend/IRBuilderAsmJs.cpp b/lib/Backend/IRBuilderAsmJs.cpp index 5ac08230eff..fe1973adef0 100644 --- a/lib/Backend/IRBuilderAsmJs.cpp +++ b/lib/Backend/IRBuilderAsmJs.cpp @@ -995,7 +995,7 @@ IRBuilderAsmJs::CreateLabel(IR::BranchInstr * branchInstr, uint & offset) } IR::LabelInstr * labelInstr; - if (instrPrev && instrPrev->IsLabelInstr() && instrPrev->GetByteCodeOffset() == offset) + if (instrPrev && instrPrev->IsLabelInstr()) { // Found an existing label at the right offset. Just reuse it. labelInstr = instrPrev->AsLabelInstr(); diff --git a/lib/Backend/Inline.cpp b/lib/Backend/Inline.cpp index c52f3eff0ba..03489fc760d 100644 --- a/lib/Backend/Inline.cpp +++ b/lib/Backend/Inline.cpp @@ -1983,19 +1983,7 @@ Inline::InlineBuiltInFunction(IR::Instr *callInstr, const FunctionJITTimeInfo * StackSym* originalCallTargetStackSym = callInstr->GetSrc1()->GetStackSym(); bool originalCallTargetOpndIsJITOpt = callInstr->GetSrc1()->GetIsJITOptimizedReg(); - // We are committed to inlining, optimize the call instruction for fixed fields now and don't attempt it later. - bool safeThis = false; - if (TryOptimizeCallInstrWithFixedMethod(callInstr, inlineeData, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/, safeThis /*unused here*/)) - { - Assert(callInstr->m_opcode == Js::OpCode::CallIFixed); - Assert(callInstr->GetFixedFunction()->GetFuncInfoAddr() == inlineeData->GetFunctionInfoAddr()); - } - else - { - // FunctionObject check for built-ins - IR::BailOutInstr * bailOutInstr = IR::BailOutInstr::New(Js::OpCode::BailOnNotBuiltIn, IR::BailOutOnInlineFunction, callInstr, callInstr->m_func); - InsertFunctionObjectCheck(callInstr, callInstr, bailOutInstr, inlineeData); - } + IR::ByteCodeUsesInstr* useCallTargetInstr = EmitFixedMethodOrFunctionObjectChecksForBuiltIns(callInstr, callInstr, inlineeData, false, true, false, true); // To push function object for cases when we have to make calls to helper method to assist in inlining if(inlineCallOpCode == Js::OpCode::CallDirect) @@ -2031,11 +2019,9 @@ Inline::InlineBuiltInFunction(IR::Instr *callInstr, const FunctionJITTimeInfo * } } - // Insert a byteCodeUsesInstr to make sure the function object's lifetime is extended beyond the last bailout point - // at which we may need to call the inlinee again in the interpreter. + if (useCallTargetInstr) { - IR::ByteCodeUsesInstr * useCallTargetInstr = IR::ByteCodeUsesInstr::New(callInstr); - useCallTargetInstr->SetRemovedOpndSymbol(originalCallTargetOpndIsJITOpt, originalCallTargetStackSym->m_id); + useCallTargetInstr->Unlink(); callInstr->InsertBefore(useCallTargetInstr); } @@ -2071,7 +2057,7 @@ Inline::InlineBuiltInFunction(IR::Instr *callInstr, const FunctionJITTimeInfo * // Insert a byteCodeUsesInstr to make sure the function object's lifetime is extended beyond the last bailout point // at which we may need to call the inlinee again in the interpreter. - IR::ByteCodeUsesInstr * useCallTargetInstr = IR::ByteCodeUsesInstr::New(callInstr->GetPrevRealInstrOrLabel()); + useCallTargetInstr = IR::ByteCodeUsesInstr::New(callInstr->GetPrevRealInstrOrLabel()); useCallTargetInstr->SetRemovedOpndSymbol(originalCallTargetOpndIsJITOpt, originalCallTargetStackSym->m_id); if(inlineCallOpCode == Js::OpCode::InlineArrayPop) @@ -2364,7 +2350,7 @@ IR::Instr* Inline::InlineApply(IR::Instr *callInstr, const FunctionJITTimeInfo * // TODO: OOP JIT enable assert (readprocessmemory?) //Assert((inlineeData->GetFunctionInfo()->GetAttributes() & Js::FunctionInfo::Attributes::BuiltInInlinableAsLdFldInlinee) != 0); - return InlineApplyWithArray(callInstr, applyData, Js::JavascriptLibrary::GetBuiltInForFuncInfo(inlineeData->GetFunctionInfoAddr(), this->topFunc->GetThreadContextInfo())); + return InlineApplyBuiltInTargetWithArray(callInstr, applyData, inlineeData); } else { @@ -2477,7 +2463,7 @@ IR::Instr * Inline::InlineApplyWithArgumentsObject(IR::Instr * callInstr, IR::In /* This method will only do CallDirect style inlining of built-in targets. No script function inlining. */ -IR::Instr * Inline::InlineApplyWithArray(IR::Instr * callInstr, const FunctionJITTimeInfo * funcInfo, Js::BuiltinFunction builtInId) +IR::Instr * Inline::InlineApplyBuiltInTargetWithArray(IR::Instr * callInstr, const FunctionJITTimeInfo * applyInfo, const FunctionJITTimeInfo * builtInInfo) { IR::Instr * implicitThisArgOut = nullptr; IR::Instr * explicitThisArgOut = nullptr; @@ -2485,7 +2471,25 @@ IR::Instr * Inline::InlineApplyWithArray(IR::Instr * callInstr, const FunctionJI uint argOutCount = 0; this->GetArgInstrsForCallAndApply(callInstr, &implicitThisArgOut, &explicitThisArgOut, &arrayArgOut, argOutCount); - TryFixedMethodAndPrepareInsertionPoint(callInstr, funcInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); + Js::OpCode originalCallOpcode = callInstr->m_opcode; + IR::Opnd * originalCallSrc1 = callInstr->GetSrc1()->Copy(this->topFunc); + IR::AutoReuseOpnd autoReuseOriginalCallSrc1(originalCallSrc1, this->topFunc); + + IR::Instr* applyLdInstr = nullptr; + IR::Instr* applyTargetLdInstr = nullptr; + if (!TryGetApplyAndTargetLdInstrs(callInstr, &applyLdInstr, &applyTargetLdInstr)) + { + return callInstr; + } + AnalysisAssert(applyTargetLdInstr != nullptr); + // Fixed function/function object checks for target built-in + callInstr->ReplaceSrc1(applyTargetLdInstr->GetDst()); + EmitFixedMethodOrFunctionObjectChecksForBuiltIns(callInstr, callInstr, builtInInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); + + // Fixed function/function object checks for .apply + callInstr->m_opcode = originalCallOpcode; + callInstr->ReplaceSrc1(originalCallSrc1); + EmitFixedMethodOrFunctionObjectChecksForBuiltIns(callInstr, callInstr, applyInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); IR::Instr* builtInEndInstr = InsertInlineeBuiltInStartEndTags(callInstr, 3); // 3 args (implicit this + explicit this + array = 3) builtInEndInstr->m_opcode = Js::OpCode::InlineNonTrackingBuiltInEnd; // We will call EndTrackCall when we see CallDirect for reasons explained in GlobOpt::TrackCalls @@ -2513,6 +2517,7 @@ IR::Instr * Inline::InlineApplyWithArray(IR::Instr * callInstr, const FunctionJI argOut = IR::Instr::New(Js::OpCode::ArgOut_A_InlineSpecialized, linkOpnd, implicitThisArgOut->GetSrc1(), argOut->GetDst(), callInstr->m_func); callInstr->InsertBefore(argOut); + Js::BuiltinFunction builtInId = Js::JavascriptLibrary::GetBuiltInForFuncInfo(builtInInfo->GetFunctionInfoAddr(), this->topFunc->GetThreadContextInfo()); IR::HelperCallOpnd * helperCallOpnd = nullptr; switch (builtInId) { @@ -2543,7 +2548,7 @@ IR::Instr * Inline::InlineApplyWithoutArrayArgument(IR::Instr *callInstr, const uint argOutCount = 0; this->GetArgInstrsForCallAndApply(callInstr, &implicitThisArgOut, &explicitThisArgOut, &dummyInstr, argOutCount); - TryFixedMethodAndPrepareInsertionPoint(callInstr, applyInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); + EmitFixedMethodOrFunctionObjectChecksForBuiltIns(callInstr, callInstr, applyInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); InsertInlineeBuiltInStartEndTags(callInstr, 2); // 2 args (implicit this + explicit this) @@ -2616,6 +2621,22 @@ void Inline::GetArgInstrsForCallAndApply(IR::Instr* callInstr, IR::Instr** impli linkOpnd->AsRegOpnd()->m_sym->m_isInlinedArgSlot = true; } +bool Inline::TryGetApplyAndTargetLdInstrs(IR::Instr * callInstr, _Outptr_result_maybenull_ IR::Instr ** applyLdInstr, _Outptr_result_maybenull_ IR::Instr ** applyTargetLdInstr) +{ + IR::Opnd* applyOpnd = callInstr->GetSrc1(); + Assert(applyOpnd->IsRegOpnd()); + StackSym* applySym = applyOpnd->AsRegOpnd()->m_sym->AsStackSym(); + if (!applySym->IsSingleDef()) + { + *applyLdInstr = nullptr; + *applyTargetLdInstr = nullptr; + return false; + } + *applyLdInstr = applySym->GetInstrDef();; + *applyTargetLdInstr = (*applyLdInstr)->m_prev; + return true; +} + /* This method only inlines targets which are script functions, under the condition that the second argument (if any) passed to apply is arguments object. @@ -2637,16 +2658,14 @@ bool Inline::InlineApplyScriptTarget(IR::Instr *callInstr, const FunctionJITTime // Begin inlining apply target - IR::Opnd* applyOpnd = callInstr->GetSrc1(); - Assert(applyOpnd->IsRegOpnd()); - StackSym* applySym = applyOpnd->AsRegOpnd()->m_sym->AsStackSym(); - if (!applySym->IsSingleDef()) + IR::Instr* applyLdInstr = nullptr; + IR::Instr* applyTargetLdInstr = nullptr; + if (!TryGetApplyAndTargetLdInstrs(callInstr, &applyLdInstr, &applyTargetLdInstr)) { return false; } - IR::Instr* applyLdInstr = applySym->GetInstrDef(); - IR::Instr* applyTargetLdInstr = applyLdInstr->m_prev; - + AnalysisAssert(applyTargetLdInstr != nullptr); + if(applyTargetLdInstr->m_opcode != Js::OpCode::LdFldForCallApplyTarget || ((applyTargetLdInstr->AsProfiledInstr()->u.FldInfo().flags & Js::FldInfo_FromAccessor) != 0)) { @@ -2908,7 +2927,7 @@ Inline::InlineCall(IR::Instr *callInstr, const FunctionJITTimeInfo *funcInfo, co IR::SymOpnd* orgLinkOpnd = callInstr->GetSrc2()->AsSymOpnd(); - TryFixedMethodAndPrepareInsertionPoint(callInstr, funcInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); + EmitFixedMethodOrFunctionObjectChecksForBuiltIns(callInstr, callInstr, funcInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); InsertInlineeBuiltInStartEndTags(callInstr, actualCount); @@ -4225,26 +4244,29 @@ Inline::PrepareInsertionPoint(IR::Instr *callInstr, const FunctionJITTimeInfo *f return primaryBailOutInstr; } -void -Inline::TryFixedMethodAndPrepareInsertionPoint(IR::Instr *callInstr, const FunctionJITTimeInfo * inlineeInfo, bool isPolymorphic, bool isBuiltIn, bool isCtor, bool isInlined) +IR::ByteCodeUsesInstr* +Inline::EmitFixedMethodOrFunctionObjectChecksForBuiltIns(IR::Instr *callInstr, IR::Instr * funcObjCheckInsertInstr, const FunctionJITTimeInfo * inlineeInfo, bool isPolymorphic, bool isBuiltIn, bool isCtor, bool isInlined) { StackSym* originalCallTargetStackSym = callInstr->GetSrc1()->GetStackSym(); bool originalCallTargetIsJITOpt = callInstr->GetSrc1()->GetIsJITOptimizedReg(); + IR::ByteCodeUsesInstr * useCallTargetInstr = nullptr; bool safeThis = false; if (TryOptimizeCallInstrWithFixedMethod(callInstr, inlineeInfo, isPolymorphic, isBuiltIn, isCtor, isInlined, safeThis)) { Assert(callInstr->m_opcode == Js::OpCode::CallIFixed); - + Assert(callInstr->GetFixedFunction()->GetFuncInfoAddr() == inlineeInfo->GetFunctionInfoAddr()); // If we optimized the call instruction for a fixed function, we must extend the function object's lifetime until after the last bailout before the call. - IR::ByteCodeUsesInstr * useCallTargetInstr = IR::ByteCodeUsesInstr::New(callInstr); + useCallTargetInstr = IR::ByteCodeUsesInstr::New(callInstr); useCallTargetInstr->SetRemovedOpndSymbol(originalCallTargetIsJITOpt, originalCallTargetStackSym->m_id); callInstr->InsertBefore(useCallTargetInstr); } else { - PrepareInsertionPoint(callInstr, inlineeInfo, callInstr); + IR::BailOutInstr * bailOutInstr = IR::BailOutInstr::New(Js::OpCode::BailOnNotBuiltIn, IR::BailOutOnInlineFunction, callInstr, callInstr->m_func); + InsertFunctionObjectCheck(callInstr, funcObjCheckInsertInstr, bailOutInstr, inlineeInfo); } + return useCallTargetInstr; } uint Inline::CountActuals(IR::Instr *callInstr) diff --git a/lib/Backend/Inline.h b/lib/Backend/Inline.h index f4f00ceb348..e7e1ed58f91 100644 --- a/lib/Backend/Inline.h +++ b/lib/Backend/Inline.h @@ -47,13 +47,13 @@ class Inline IR::Instr * SimulateCallForGetterSetter(IR::Instr *accessorInstr, IR::Instr* insertInstr, IR::PropertySymOpnd* methodOpnd, bool isGetter); IR::Instr * InlineApply(IR::Instr *callInstr, const FunctionJITTimeInfo * applyData, const FunctionJITTimeInfo * inlinerData, const StackSym *symThis, bool* pIsInlined, uint callSiteId, uint recursiveInlineDepth, uint argsCount); - IR::Instr * InlineApplyWithArray(IR::Instr *callInstr, const FunctionJITTimeInfo * inlineeInfo, Js::BuiltinFunction builtInId); + IR::Instr * InlineApplyBuiltInTargetWithArray(IR::Instr *callInstr, const FunctionJITTimeInfo * applyInfo, const FunctionJITTimeInfo * builtInInfo); IR::Instr * InlineApplyWithArgumentsObject(IR::Instr * callInstr, IR::Instr * argsObjectArgInstr, const FunctionJITTimeInfo * inlineeInfo); IR::Instr * InlineApplyWithoutArrayArgument(IR::Instr *callInstr, const FunctionJITTimeInfo * applyInfo, const FunctionJITTimeInfo * applyTargetInfo); bool InlineApplyScriptTarget(IR::Instr *callInstr, const FunctionJITTimeInfo* inlinerData, const FunctionJITTimeInfo** pInlineeData, const FunctionJITTimeInfo * applyFuncInfo, const StackSym *symThis, IR::Instr ** returnInstr, uint recursiveInlineDepth, bool isArrayOpndArgumentsObject, uint argsCount); void GetArgInstrsForCallAndApply(IR::Instr* callInstr, IR::Instr** implicitThisArgOut, IR::Instr** explicitThisArgOut, IR::Instr** argumentsOrArrayArgOut, uint &argOutCount); - + bool TryGetApplyAndTargetLdInstrs(IR::Instr * callInstr, _Outptr_result_maybenull_ IR::Instr ** applyLdInstr, _Outptr_result_maybenull_ IR::Instr ** applyTargetLdInstr); IR::Instr * InlineCall(IR::Instr *callInstr, const FunctionJITTimeInfo * inlineeData, const FunctionJITTimeInfo * inlinerData, const StackSym *symThis, bool* pIsInlined, uint callSiteId, uint recursiveInlineDepth); bool InlineCallTarget(IR::Instr *callInstr, const FunctionJITTimeInfo* inlinerData, const FunctionJITTimeInfo** pInlineeData, const FunctionJITTimeInfo *callFuncInfo, const StackSym *symThis, IR::Instr ** returnInstr, uint recursiveInlineDepth); @@ -83,7 +83,7 @@ class Inline void FixupExtraActualParams(IR::Instr * instr, IR::Instr *argOuts[], IR::Instr *argOutsExtra[], uint index, uint actualCount, Js::ProfileId callSiteId); void RemoveExtraFixupArgouts(IR::Instr* instr, uint argoutRemoveCount, Js::ProfileId callSiteId); IR::Instr* PrepareInsertionPoint(IR::Instr *callInstr, const FunctionJITTimeInfo *funcInfo, IR::Instr *insertBeforeInstr, IR::BailOutKind bailOutKind = IR::BailOutOnInlineFunction); - void TryFixedMethodAndPrepareInsertionPoint(IR::Instr *callInstr, const FunctionJITTimeInfo * inlineeInfo, bool isPolymorphic, bool isBuiltIn, bool isCtor, bool isInlined); + IR::ByteCodeUsesInstr* EmitFixedMethodOrFunctionObjectChecksForBuiltIns(IR::Instr *callInstr, IR::Instr * funcObjCheckInsertInstr, const FunctionJITTimeInfo * inlineeInfo, bool isPolymorphic, bool isBuiltIn, bool isCtor, bool isInlined); Js::ArgSlot MapActuals(IR::Instr *callInstr, __out_ecount(maxParamCount) IR::Instr *argOuts[], Js::ArgSlot formalCount, Func *inlinee, Js::ProfileId callSiteId, bool *stackArgsArgOutExpanded, IR::Instr *argOutsExtra[] = nullptr, Js::ArgSlot maxParamCount = Js::InlineeCallInfo::MaxInlineeArgoutCount); uint32 CountActuals(IR::Instr *callIntr); void MapFormals(Func *inlinee, __in_ecount(formalCount) IR::Instr *argOuts[], uint formalCount, uint actualCount, IR::RegOpnd *retOpnd, IR::Opnd * funcObjOpnd, const StackSym *symCallerThis, bool stackArgsArgOutExpanded, bool fixedFunctionSafeThis = false, IR::Instr *argOutsExtra[] = nullptr); diff --git a/lib/Backend/Lower.cpp b/lib/Backend/Lower.cpp index 309ae5e2d8b..1f8f734cbf1 100644 --- a/lib/Backend/Lower.cpp +++ b/lib/Backend/Lower.cpp @@ -18425,22 +18425,28 @@ Lowerer::GenerateFastReplace(IR::Opnd* strOpnd, IR::Opnd* src1, IR::Opnd* src2, this->GenerateStringTest(src2->AsRegOpnd(), insertInstr, labelHelper); } - //scriptContext, pRegEx, pThis, pReplace (to be pushed in reverse order) - - // pReplace, pThis, pRegEx - this->m_lowererMD.LoadHelperArgument(insertInstr, src2); - this->m_lowererMD.LoadHelperArgument(insertInstr, strOpnd); - this->m_lowererMD.LoadHelperArgument(insertInstr, src1); - - // script context - LoadScriptContext(insertInstr); - IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, insertInstr->m_func); - if(callDst) + if (callDst) { helperCallInstr->SetDst(callDst); } insertInstr->InsertBefore(helperCallInstr); + + if (insertInstr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(insertInstr->GetBailOutKind())) + { + helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, insertInstr->GetBailOutInfo(), insertInstr->GetBailOutKind(), insertInstr); + } + + //scriptContext, pRegEx, pThis, pReplace (to be pushed in reverse order) + + // pReplace, pThis, pRegEx + this->m_lowererMD.LoadHelperArgument(helperCallInstr, src2); + this->m_lowererMD.LoadHelperArgument(helperCallInstr, strOpnd); + this->m_lowererMD.LoadHelperArgument(helperCallInstr, src1); + + // script context + LoadScriptContext(helperCallInstr); + if(callDst) { m_lowererMD.ChangeToHelperCall(helperCallInstr, IR::JnHelperMethod::HelperRegExp_ReplaceStringResultUsed); @@ -18520,6 +18526,17 @@ Lowerer::GenerateFastInlineStringSplitMatch(IR::Instr * instr) labelHelper, instr); + IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func); + if (callDst) + { + helperCallInstr->SetDst(callDst); + } + instr->InsertBefore(helperCallInstr); + if (instr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(instr->GetBailOutKind())) + { + helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, instr->GetBailOutInfo(), instr->GetBailOutKind(), instr); + } + // [stackAllocationPointer, ]scriptcontext, regexp, input[, limit] (to be pushed in reverse order) if(src1->AsHelperCallOpnd()->m_fnHelper == IR::JnHelperMethod::HelperString_Split) @@ -18527,15 +18544,15 @@ Lowerer::GenerateFastInlineStringSplitMatch(IR::Instr * instr) //limit //As we are optimizing only for two operands, make limit UINT_MAX IR::Opnd* limit = IR::IntConstOpnd::New(UINT_MAX, TyUint32, instr->m_func); - this->m_lowererMD.LoadHelperArgument(instr, limit); + this->m_lowererMD.LoadHelperArgument(helperCallInstr, limit); } //input, regexp - this->m_lowererMD.LoadHelperArgument(instr, argsOpnd[0]); - this->m_lowererMD.LoadHelperArgument(instr, argsOpnd[1]); + this->m_lowererMD.LoadHelperArgument(helperCallInstr, argsOpnd[0]); + this->m_lowererMD.LoadHelperArgument(helperCallInstr, argsOpnd[1]); // script context - LoadScriptContext(instr); + LoadScriptContext(helperCallInstr); IR::JnHelperMethod helperMethod = IR::JnHelperMethod::HelperInvalid; IR::AutoReuseOpnd autoReuseStackAllocationOpnd; @@ -18560,8 +18577,8 @@ Lowerer::GenerateFastInlineStringSplitMatch(IR::Instr * instr) IR::RegOpnd *const stackAllocationOpnd = IR::RegOpnd::New(TyVar, m_func); autoReuseStackAllocationOpnd.Initialize(stackAllocationOpnd, m_func); stackAllocationOpnd->SetValueType(callDst->GetValueType()); - GenerateMarkTempAlloc(stackAllocationOpnd, Js::JavascriptArray::StackAllocationSize, instr); - m_lowererMD.LoadHelperArgument(instr, stackAllocationOpnd); + GenerateMarkTempAlloc(stackAllocationOpnd, Js::JavascriptArray::StackAllocationSize, helperCallInstr); + m_lowererMD.LoadHelperArgument(helperCallInstr, stackAllocationOpnd); } else { @@ -18587,13 +18604,6 @@ Lowerer::GenerateFastInlineStringSplitMatch(IR::Instr * instr) } } - IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func); - if(callDst) - { - helperCallInstr->SetDst(callDst); - } - instr->InsertBefore(helperCallInstr); - m_lowererMD.ChangeToHelperCall(helperCallInstr, helperMethod); IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); @@ -18768,20 +18778,30 @@ Lowerer::GenerateFastInlineRegExpExec(IR::Instr * instr) instr->InsertBefore(labelFastHelper); } + IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func); + if (callDst) + { + helperCallInstr->SetDst(callDst); + } + instr->InsertBefore(helperCallInstr); + if (instr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(instr->GetBailOutKind())) + { + helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, instr->GetBailOutInfo(), instr->GetBailOutKind(), instr); + } // [stackAllocationPointer, ]scriptcontext, regexp, string (to be pushed in reverse order) //string, regexp - this->m_lowererMD.LoadHelperArgument(instr, opndString); - this->m_lowererMD.LoadHelperArgument(instr, opndRegex); + this->m_lowererMD.LoadHelperArgument(helperCallInstr, opndString); + this->m_lowererMD.LoadHelperArgument(helperCallInstr, opndRegex); // script context - LoadScriptContext(instr); + LoadScriptContext(helperCallInstr); IR::JnHelperMethod helperMethod; IR::AutoReuseOpnd autoReuseStackAllocationOpnd; - if(callDst) + if (callDst) { - if(instr->dstIsTempObject) + if (instr->dstIsTempObject) { helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultUsedAndMayBeTemp; @@ -18789,8 +18809,8 @@ Lowerer::GenerateFastInlineRegExpExec(IR::Instr * instr) IR::RegOpnd *const stackAllocationOpnd = IR::RegOpnd::New(TyVar, m_func); autoReuseStackAllocationOpnd.Initialize(stackAllocationOpnd, m_func); stackAllocationOpnd->SetValueType(callDst->GetValueType()); - GenerateMarkTempAlloc(stackAllocationOpnd, Js::JavascriptArray::StackAllocationSize, instr); - m_lowererMD.LoadHelperArgument(instr, stackAllocationOpnd); + GenerateMarkTempAlloc(stackAllocationOpnd, Js::JavascriptArray::StackAllocationSize, helperCallInstr); + m_lowererMD.LoadHelperArgument(helperCallInstr, stackAllocationOpnd); } else { @@ -18802,12 +18822,6 @@ Lowerer::GenerateFastInlineRegExpExec(IR::Instr * instr) helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultNotUsed; } - IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func); - if(callDst) - { - helperCallInstr->SetDst(callDst); - } - instr->InsertBefore(helperCallInstr); m_lowererMD.ChangeToHelperCall(helperCallInstr, helperMethod); instr->InsertAfter(doneLabel); @@ -25536,6 +25550,18 @@ Lowerer::InsertLoopTopLabel(IR::Instr * insertBeforeInstr) return loopTopLabel; } +IR::Instr * +Lowerer::AddBailoutToHelperCallInstr(IR::Instr * helperCallInstr, BailOutInfo * bailoutInfo, IR::BailOutKind bailoutKind, IR::Instr * primaryBailoutInstr) +{ + helperCallInstr = helperCallInstr->ConvertToBailOutInstr(bailoutInfo, bailoutKind); + if (bailoutInfo->bailOutInstr == primaryBailoutInstr) + { + IR::Instr * instrShare = primaryBailoutInstr->ShareBailOut(); + LowerBailTarget(instrShare); + } + return helperCallInstr; +} + #if DBG void Lowerer::LegalizeVerifyRange(IR::Instr * instrStart, IR::Instr * instrLast) diff --git a/lib/Backend/Lower.h b/lib/Backend/Lower.h index 09dfdf33224..ec09ac6fe0a 100644 --- a/lib/Backend/Lower.h +++ b/lib/Backend/Lower.h @@ -666,6 +666,7 @@ class Lowerer void GenerateHasObjectArrayCheck(IR::RegOpnd * objectOpnd, IR::RegOpnd * typeOpnd, IR::LabelInstr * hasObjectArray, IR::Instr * insertBeforeInstr); IR::LabelInstr* InsertLoopTopLabel(IR::Instr * insertBeforeInstr); + IR::Instr * AddBailoutToHelperCallInstr(IR::Instr * helperCallInstr, BailOutInfo * bailoutInfo, IR::BailOutKind bailoutKind, IR::Instr * primaryBailoutInstr); public: static IRType GetImplicitCallFlagsType() { diff --git a/lib/Backend/ObjTypeSpecFldInfo.cpp b/lib/Backend/ObjTypeSpecFldInfo.cpp index e234320ea50..f57ff5b3891 100644 --- a/lib/Backend/ObjTypeSpecFldInfo.cpp +++ b/lib/Backend/ObjTypeSpecFldInfo.cpp @@ -674,16 +674,26 @@ ObjTypeSpecFldInfo* ObjTypeSpecFldInfo::CreateFrom(uint id, Js::PolymorphicInlin // when we add a property. We also don't invalidate proto inline caches (and guards) unless the property being added exists on the proto chain. // Missing properties by definition do not exist on the proto chain, so in the end we could have an EquivalentObjTypeSpec cache hit on a // property that once was missing, but has since been added. (See OS Bugs 280582). - else if (inlineCache.IsProto() && !inlineCache.u.proto.isMissing) + else if (inlineCache.IsProto()) { - isProto = true; - typeId = TypeWithoutAuxSlotTag(inlineCache.u.proto.type)->GetTypeId(); - usesAuxSlot = TypeHasAuxSlotTag(inlineCache.u.proto.type); - slotIndex = inlineCache.u.proto.slotIndex; - prototypeObject = inlineCache.u.proto.prototypeObject; + if(!inlineCache.u.proto.isMissing) + { + isProto = true; + typeId = TypeWithoutAuxSlotTag(inlineCache.u.proto.type)->GetTypeId(); + usesAuxSlot = TypeHasAuxSlotTag(inlineCache.u.proto.type); + slotIndex = inlineCache.u.proto.slotIndex; + prototypeObject = inlineCache.u.proto.prototypeObject; + } + else + { + areEquivalent = false; + areStressEquivalent = false; + gatherDataForInlining = false; + } } else { + AssertOrFailFast(inlineCache.IsAccessor()); if (!PHASE_OFF(Js::FixAccessorPropsPhase, functionBody)) { isAccessor = true; diff --git a/lib/Backend/TempTracker.cpp b/lib/Backend/TempTracker.cpp index 246df51d5c5..09371ac18b9 100644 --- a/lib/Backend/TempTracker.cpp +++ b/lib/Backend/TempTracker.cpp @@ -1026,7 +1026,7 @@ ObjectTemp::IsTempUseOpCodeSym(IR::Instr * instr, Js::OpCode opcode, Sym * sym) return instr->GetSrc1()->AsIndirOpnd()->GetBaseOpnd()->m_sym == sym; case Js::OpCode::StElemI_A: case Js::OpCode::StElemI_A_Strict: - return instr->GetDst()->AsIndirOpnd()->GetBaseOpnd()->m_sym == sym; + return instr->GetDst()->AsIndirOpnd()->GetBaseOpnd()->m_sym == sym && instr->GetSrc1()->GetStackSym() != sym; case Js::OpCode::Memset: return instr->GetDst()->AsIndirOpnd()->GetBaseOpnd()->m_sym == sym || (instr->GetSrc1()->IsRegOpnd() && instr->GetSrc1()->AsRegOpnd()->m_sym == sym); case Js::OpCode::Memcopy: diff --git a/lib/Backend/ValueInfo.h b/lib/Backend/ValueInfo.h index 8c2c6158381..e8cff7ff8b2 100644 --- a/lib/Backend/ValueInfo.h +++ b/lib/Backend/ValueInfo.h @@ -153,6 +153,7 @@ class ValueInfo : protected ValueType using ValueType::IsLikelyTypedArray; using ValueType::IsOptimizedTypedArray; + using ValueType::IsOptimizedVirtualTypedArray; using ValueType::IsLikelyOptimizedTypedArray; using ValueType::IsLikelyOptimizedVirtualTypedArray; diff --git a/lib/Common/ChakraCoreVersion.h b/lib/Common/ChakraCoreVersion.h index c53910414ce..48a81b2cec4 100644 --- a/lib/Common/ChakraCoreVersion.h +++ b/lib/Common/ChakraCoreVersion.h @@ -17,7 +17,7 @@ // ChakraCore version number definitions (used in ChakraCore binary metadata) #define CHAKRA_CORE_MAJOR_VERSION 1 #define CHAKRA_CORE_MINOR_VERSION 7 -#define CHAKRA_CORE_PATCH_VERSION 4 +#define CHAKRA_CORE_PATCH_VERSION 5 #define CHAKRA_CORE_VERSION_RELEASE_QFE 0 // Redundant with PATCH_VERSION. Keep this value set to 0. // ------------- diff --git a/lib/Parser/Parse.h b/lib/Parser/Parse.h index ec01e9a0315..e21d3ad28b4 100644 --- a/lib/Parser/Parse.h +++ b/lib/Parser/Parse.h @@ -1125,6 +1125,7 @@ class Parser : m_parser(parser) { m_prevState = m_parser->GetIsInParsingArgList(); + m_prevDestructuringState = m_parser->GetHasDestructuringPattern(); m_parser->SetHasDestructuringPattern(false); m_parser->SetIsInParsingArgList(true); } @@ -1135,11 +1136,20 @@ class Parser { m_parser->SetHasDestructuringPattern(false); } + else + { + // Reset back to previous state only when the current call node does not have usage of destructuring expression. + if (!m_parser->GetHasDestructuringPattern()) + { + m_parser->SetHasDestructuringPattern(m_prevDestructuringState); + } + } } private: Parser *m_parser; bool m_prevState; + bool m_prevDestructuringState; }; public: diff --git a/lib/Parser/RegexCompileTime.cpp b/lib/Parser/RegexCompileTime.cpp index 7d6ad5d34cc..2b27c0242b9 100644 --- a/lib/Parser/RegexCompileTime.cpp +++ b/lib/Parser/RegexCompileTime.cpp @@ -1173,7 +1173,17 @@ namespace UnifiedRegex { if ((compiler.program->flags & IgnoreCaseRegexFlag) != 0) { - Char equivs[CaseInsensitive::EquivClassSize]; + // To ensure initialization, we first default-initialize the + // whole array with a constant, and then individually set it + // to be just the first character (known to exist). This can + // hopefully be optimized to just initialize to cs[0] by the + // compiler. + Char equivs[CaseInsensitive::EquivClassSize] = { (Char)-1 }; + for (int i = 0; i < CaseInsensitive::EquivClassSize; i++) + { + equivs[i] = cs[0]; + + } bool isNonTrivial = compiler.standardChars->ToEquivs(compiler.program->GetCaseMappingSource(), cs[0], equivs); if (isNonTrivial) { @@ -1279,10 +1289,23 @@ namespace UnifiedRegex { if (isEquivClass) { - Char uniqueEquivs[CaseInsensitive::EquivClassSize]; + // To ensure initialization, we first default-initialize the + // whole array with a constant, and then individually set it + // to be just the first character (known to exist). This can + // hopefully be optimized to just initialize to cs[0] by the + // compiler. + Char uniqueEquivs[CaseInsensitive::EquivClassSize] = { (Char)-1 }; + for (int i = 0; i < CaseInsensitive::EquivClassSize; i++) + { + uniqueEquivs[i] = cs[0]; + } CharCount uniqueEquivCount = FindUniqueEquivs(cs, uniqueEquivs); switch (uniqueEquivCount) { + case 1: + EMIT(compiler, MatchCharInst, uniqueEquivs[0]); + break; + case 2: EMIT(compiler, MatchChar2Inst, uniqueEquivs[0], uniqueEquivs[1]); break; diff --git a/lib/Parser/RegexCompileTime.h b/lib/Parser/RegexCompileTime.h index bbd0d12fc99..2f293bd60c4 100644 --- a/lib/Parser/RegexCompileTime.h +++ b/lib/Parser/RegexCompileTime.h @@ -374,10 +374,8 @@ namespace UnifiedRegex , isEquivClass(false) { cs[0] = c; -#if DBG for (int i = 1; i < CaseInsensitive::EquivClassSize; i++) - cs[i] = (Char)-1; -#endif + cs[i] = c; } NODE_DECL diff --git a/lib/Parser/RegexParser.cpp b/lib/Parser/RegexParser.cpp index 132bd7ccd2c..e59dcd58c66 100644 --- a/lib/Parser/RegexParser.cpp +++ b/lib/Parser/RegexParser.cpp @@ -2961,6 +2961,8 @@ namespace UnifiedRegex this->scriptContext->ProfileEnd(Js::RegexCompilePhase); #endif + AssertOrFailFast(0 < pattern->NumGroups() && pattern->NumGroups() <= MAX_NUM_GROUPS); + return pattern; } @@ -2993,6 +2995,8 @@ namespace UnifiedRegex program->numGroups = nextGroupId; + AssertOrFailFast(0 < program->numGroups && program->numGroups <= MAX_NUM_GROUPS); + // Remaining to set during compilation: litbuf, litbufLen, numLoops, insts, instsLen, entryPointLabel } diff --git a/lib/Parser/RegexParser.h b/lib/Parser/RegexParser.h index 7426f1a48fc..322ddf8e66c 100644 --- a/lib/Parser/RegexParser.h +++ b/lib/Parser/RegexParser.h @@ -77,6 +77,16 @@ namespace UnifiedRegex const EncodedChar* next; bool inBody; + // Maximum number of capturing groups allowed, including the entire regexp, which is always + // considered a capturing group. Using INT16_MAX allows us to pass one value for each + // group, plus a few additional values, to a JavaScript function without overflowing the + // number of arguments. This is important, for example, in the implementation of + // String.prototype.replace, where the second argument is a function. + // + // This should really be an unsigned, but we compare it against numGroups, so make it + // an int for now to avoid a bunch of compiler warnings until we can go back and clean this up. + static const int MAX_NUM_GROUPS = INT16_MAX; + int numGroups; // determined in first parse int nextGroupId; // Buffer accumulating all literals. diff --git a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp index 353771b1395..c20647f4780 100644 --- a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp +++ b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp @@ -538,8 +538,7 @@ void ByteCodeGenerator::LoadUncachedHeapArguments(FuncInfo *funcInfo) { // Pass the frame object and ID array to the runtime, and put the resulting Arguments object // at the expected location. - - Js::PropertyIdArray *propIds = funcInfo->GetParsedFunctionBody()->AllocatePropertyIdArrayForFormals(count * sizeof(Js::PropertyId), count, 0); + Js::PropertyIdArray *propIds = funcInfo->GetParsedFunctionBody()->AllocatePropertyIdArrayForFormals(UInt32Math::Mul(count, sizeof(Js::PropertyId)), count, 0); GetFormalArgsArray(this, funcInfo, propIds); } @@ -1557,7 +1556,9 @@ void ByteCodeGenerator::EmitScopeObjectInit(FuncInfo *funcInfo) uint cachedFuncCount = 0; Js::PropertyId firstFuncSlot = Js::Constants::NoProperty; Js::PropertyId firstVarSlot = Js::Constants::NoProperty; - uint extraAlloc = (slotCount + Js::ActivationObjectEx::ExtraSlotCount()) * sizeof(Js::PropertyId); + + uint extraAlloc = UInt32Math::Add(slotCount, Js::ActivationObjectEx::ExtraSlotCount()); + extraAlloc = UInt32Math::Mul(extraAlloc, sizeof(Js::PropertyId)); // Create and fill the array of local property ID's. // They all have slots assigned to them already (if they need them): see StartEmitFunction. @@ -1997,7 +1998,7 @@ void ByteCodeGenerator::LoadAllConstants(FuncInfo *funcInfo) uint count = funcInfo->inArgsCount + (funcInfo->root->sxFnc.pnodeRest != nullptr ? 1 : 0) - 1; if (count != 0) { - Js::PropertyIdArray *propIds = RecyclerNewPlus(scriptContext->GetRecycler(), count * sizeof(Js::PropertyId), Js::PropertyIdArray, count, 0); + Js::PropertyIdArray *propIds = RecyclerNewPlus(scriptContext->GetRecycler(), UInt32Math::Mul(count, sizeof(Js::PropertyId)), Js::PropertyIdArray, count, 0); GetFormalArgsArray(this, funcInfo, propIds); byteCodeFunction->SetPropertyIdsOfFormals(propIds); @@ -7198,7 +7199,7 @@ Js::ArgSlot EmitArgList( if (spreadArgCount > 0) { - const size_t extraAlloc = spreadArgCount * sizeof(uint32); + const size_t extraAlloc = UInt32Math::Mul(spreadArgCount, sizeof(uint32)); Assert(spreadIndices != nullptr); *spreadIndices = AnewPlus(byteCodeGenerator->GetAllocator(), extraAlloc, Js::AuxArray, spreadArgCount); } @@ -7347,7 +7348,7 @@ Js::ArgSlot EmitNewObjectOfConstants( EmitArgListStart(Js::Constants::NoRegister, byteCodeGenerator, funcInfo, Js::Constants::NoProfileId); // Create the vars array - Js::VarArrayVarCount *vars = AnewPlus(byteCodeGenerator->GetAllocator(), (argCount - 1) * sizeof(Js::Var), Js::VarArrayVarCount, Js::TaggedInt::ToVarUnchecked(argCount - 1)); + Js::VarArrayVarCount *vars = AnewPlus(byteCodeGenerator->GetAllocator(), UInt32Math::Mul((argCount - 1), sizeof(Js::Var)), Js::VarArrayVarCount, Js::TaggedInt::ToVarUnchecked(argCount - 1)); // Emit all constants to the vars array EmitConstantArgsToVarArray(byteCodeGenerator, vars->elements, pnode->sxCall.pnodeArgs, argCount - 1); @@ -7372,10 +7373,11 @@ Js::ArgSlot EmitNewObjectOfConstants( Js::OpCode::NewScObject_A, funcInfo->AcquireLoc(pnode), vars, - sizeof(Js::VarArray) + (argCount - 1) * sizeof(Js::Var), + UInt32Math::MulAdd((argCount-1)), pnode->sxCall.pnodeTarget->location); - AdeletePlus(byteCodeGenerator->GetAllocator(), (argCount - 1) * sizeof(Js::VarArrayVarCount), vars); + + AdeletePlus(byteCodeGenerator->GetAllocator(), UInt32Math::Mul((argCount-1), sizeof(Js::VarArrayVarCount)), vars); return actualArgCount; } @@ -7804,8 +7806,8 @@ void EmitCallI( if (pnode->sxCall.spreadArgCount > 0) { Assert(spreadIndices != nullptr); - spreadExtraAlloc = spreadIndices->count * sizeof(uint32); - spreadIndicesSize = sizeof(*spreadIndices) + spreadExtraAlloc; + spreadExtraAlloc = UInt32Math::Mul(spreadIndices->count, sizeof(uint32)); + spreadIndicesSize = UInt32Math::Add(sizeof(*spreadIndices), spreadExtraAlloc); options = Js::CallIExtended_SpreadArgs; } @@ -7994,8 +7996,8 @@ void EmitNew(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* f if (pnode->sxCall.spreadArgCount > 0) { Assert(spreadIndices != nullptr); - uint spreadExtraAlloc = spreadIndices->count * sizeof(uint32); - uint spreadIndicesSize = sizeof(*spreadIndices) + spreadExtraAlloc; + uint spreadExtraAlloc = UInt32Math::Mul(spreadIndices->count, sizeof(uint32)); + uint spreadIndicesSize = UInt32Math::Add(sizeof(*spreadIndices), spreadExtraAlloc); byteCodeGenerator->Writer()->CallIExtended(op, funcInfo->AcquireLoc(pnode), pnode->sxCall.pnodeTarget->location, (uint16)actualArgCount, Js::CallIExtended_SpreadArgs, spreadIndices, spreadIndicesSize, callSiteId); @@ -8413,7 +8415,8 @@ void EmitObjectInitializers(ParseNode *memberList, Js::RegSlot objectLocation, B } else { - Js::PropertyIdArray *propIds = AnewPlus(byteCodeGenerator->GetAllocator(), argCount * sizeof(Js::PropertyId), Js::PropertyIdArray, argCount, 0); + uint32 allocSize = UInt32Math::Mul(argCount, sizeof(Js::PropertyId)); + Js::PropertyIdArray *propIds = AnewPlus(byteCodeGenerator->GetAllocator(), allocSize, Js::PropertyIdArray, argCount, 0); if (propertyIds->ContainsKey(Js::PropertyIds::__proto__)) { @@ -8451,11 +8454,11 @@ void EmitObjectInitializers(ParseNode *memberList, Js::RegSlot objectLocation, B uint32 literalObjectId = funcInfo->GetParsedFunctionBody()->NewObjectLiteral(); // Generate the opcode with propIds and cacheId - byteCodeGenerator->Writer()->Auxiliary(Js::OpCode::NewScObjectLiteral, objectLocation, propIds, sizeof(Js::PropertyIdArray) + argCount * sizeof(Js::PropertyId), literalObjectId); + byteCodeGenerator->Writer()->Auxiliary(Js::OpCode::NewScObjectLiteral, objectLocation, propIds, UInt32Math::Add(sizeof(Js::PropertyIdArray), allocSize), literalObjectId); Adelete(byteCodeGenerator->GetAllocator(), propertyIds); - AdeletePlus(byteCodeGenerator->GetAllocator(), argCount * sizeof(Js::PropertyId), propIds); + AdeletePlus(byteCodeGenerator->GetAllocator(), allocSize, propIds); } memberList = pmemberList; @@ -8594,7 +8597,12 @@ void SetNewArrayElements(ParseNode *pnode, Js::RegSlot arrayLocation, ByteCodeGe bool arrayIntOpt = nativeArrays && pnode->sxArrLit.arrayOfInts; if (arrayIntOpt) { - int extraAlloc = argCount * sizeof(int32); + int extraAlloc = 0, auxSize = 0; + if (Int32Math::Mul(argCount, sizeof(int32), &extraAlloc) + || Int32Math::Add(sizeof(Js::AuxArray), extraAlloc, &auxSize)) + { + ::Math::DefaultOverflowPolicy(); + } Js::AuxArray *ints = AnewPlus(byteCodeGenerator->GetAllocator(), extraAlloc, Js::AuxArray, argCount); EmitConstantArgsToIntArray(byteCodeGenerator, ints->elements, args, argCount); Assert(!pnode->sxArrLit.hasMissingValues); @@ -8602,7 +8610,7 @@ void SetNewArrayElements(ParseNode *pnode, Js::RegSlot arrayLocation, ByteCodeGe Js::OpCode::NewScIntArray, pnode->location, ints, - sizeof(Js::AuxArray) + extraAlloc, + auxSize, argCount); AdeletePlus(byteCodeGenerator->GetAllocator(), extraAlloc, ints); return; @@ -8611,7 +8619,12 @@ void SetNewArrayElements(ParseNode *pnode, Js::RegSlot arrayLocation, ByteCodeGe bool arrayNumOpt = nativeArrays && pnode->sxArrLit.arrayOfNumbers; if (arrayNumOpt) { - int extraAlloc = argCount * sizeof(double); + int extraAlloc = 0, auxSize = 0; + if (Int32Math::Mul(argCount, sizeof(double), &extraAlloc) + || Int32Math::Add(sizeof(Js::AuxArray), extraAlloc, &auxSize)) + { + ::Math::DefaultOverflowPolicy(); + } Js::AuxArray *doubles = AnewPlus(byteCodeGenerator->GetAllocator(), extraAlloc, Js::AuxArray, argCount); EmitConstantArgsToFltArray(byteCodeGenerator, doubles->elements, args, argCount); Assert(!pnode->sxArrLit.hasMissingValues); @@ -8619,7 +8632,7 @@ void SetNewArrayElements(ParseNode *pnode, Js::RegSlot arrayLocation, ByteCodeGe Js::OpCode::NewScFltArray, pnode->location, doubles, - sizeof(Js::AuxArray) + extraAlloc, + auxSize, argCount); AdeletePlus(byteCodeGenerator->GetAllocator(), extraAlloc, doubles); return; @@ -8630,7 +8643,7 @@ void SetNewArrayElements(ParseNode *pnode, Js::RegSlot arrayLocation, ByteCodeGe Js::RegSlot spreadArrLoc = arrayLocation; Js::AuxArray *spreadIndices = nullptr; - const uint extraAlloc = spreadCount * sizeof(uint32); + const uint extraAlloc = UInt32Math::Mul(spreadCount, sizeof(uint32)); if (pnode->sxArrLit.spreadCount > 0) { arrayLocation = funcInfo->AcquireTmpRegister(); @@ -8678,14 +8691,15 @@ void SetNewArrayElements(ParseNode *pnode, Js::RegSlot arrayLocation, ByteCodeGe if (arrayLitOpt) { - Js::VarArray *vars = AnewPlus(byteCodeGenerator->GetAllocator(), argCount * sizeof(Js::Var), Js::VarArray, argCount); + uint32 allocSize = UInt32Math::Mul(argCount, sizeof(Js::Var)); + Js::VarArray *vars = AnewPlus(byteCodeGenerator->GetAllocator(), allocSize, Js::VarArray, argCount); EmitConstantArgsToVarArray(byteCodeGenerator, vars->elements, args, argCount); // Generate the opcode with vars - byteCodeGenerator->Writer()->Auxiliary(Js::OpCode::StArrSegItem_A, arrLoc, vars, sizeof(Js::VarArray) + argCount * sizeof(Js::Var), argCount); + byteCodeGenerator->Writer()->Auxiliary(Js::OpCode::StArrSegItem_A, arrLoc, vars, UInt32Math::Add(sizeof(Js::VarArray), allocSize), argCount); - AdeletePlus(byteCodeGenerator->GetAllocator(), argCount * sizeof(Js::Var), vars); + AdeletePlus(byteCodeGenerator->GetAllocator(), allocSize, vars); } else { @@ -8755,7 +8769,7 @@ void SetNewArrayElements(ParseNode *pnode, Js::RegSlot arrayLocation, ByteCodeGe if (pnode->sxArrLit.spreadCount > 0) { - byteCodeGenerator->Writer()->Reg2Aux(Js::OpCode::SpreadArrayLiteral, spreadArrLoc, arrayLocation, spreadIndices, sizeof(Js::AuxArray) + extraAlloc, extraAlloc); + byteCodeGenerator->Writer()->Reg2Aux(Js::OpCode::SpreadArrayLiteral, spreadArrLoc, arrayLocation, spreadIndices, UInt32Math::Add(sizeof(Js::AuxArray), extraAlloc), extraAlloc); AdeletePlus(byteCodeGenerator->GetAllocator(), extraAlloc, spreadIndices); funcInfo->ReleaseTmpRegister(arrayLocation); } diff --git a/lib/Runtime/Language/AsmJsModule.cpp b/lib/Runtime/Language/AsmJsModule.cpp index fc5fb150de5..6a64fcf1e36 100644 --- a/lib/Runtime/Language/AsmJsModule.cpp +++ b/lib/Runtime/Language/AsmJsModule.cpp @@ -839,6 +839,7 @@ namespace Js AsmJsSIMDValue simdValue; simdValue.Zero(); // define all variables + BVSparse initializerBV(&mAllocator); while (pnode->nop == knopList) { ParseNode * varNode = ParserWrapper::GetBinaryLeft(pnode); @@ -932,6 +933,12 @@ namespace Js { return Fail(decl, _u("Failed to define var")); } + // If we are declaring a var that we previously used in an initializer, that value will be undefined + // so we need to throw an error. + if (initializerBV.Test(var->GetName()->GetPropertyId())) + { + return Fail(decl, _u("Cannot declare a var after using it in an initializer")); + } RegSlot loc = Constants::NoRegister; if (pnodeInit->nop == knopInt) { @@ -970,6 +977,7 @@ namespace Js if (declSym->GetSymbolType() == AsmJsSymbol::Variable) { AsmJsVar * definition = declSym->Cast(); + initializerBV.Set(definition->GetName()->GetPropertyId()); switch (definition->GetVarType().which()) { case AsmJsVarType::Double: diff --git a/lib/Runtime/Language/ValueType.cpp b/lib/Runtime/Language/ValueType.cpp index 22ef773b63d..aa90004056f 100644 --- a/lib/Runtime/Language/ValueType.cpp +++ b/lib/Runtime/Language/ValueType.cpp @@ -650,6 +650,11 @@ bool ValueType::IsOptimizedTypedArray() const return IsObject() && ((GetObjectType() >= ObjectType::Int8Array && GetObjectType() <= ObjectType::Float64MixedArray)); } +bool ValueType::IsOptimizedVirtualTypedArray() const +{ + return IsObject() && (GetObjectType() >= ObjectType::Int8VirtualArray && GetObjectType() <= ObjectType::Float64VirtualArray); +} + bool ValueType::IsLikelyOptimizedTypedArray() const { return IsLikelyObject() && ((GetObjectType() >= ObjectType::Int8Array && GetObjectType() <= ObjectType::Float64MixedArray)); diff --git a/lib/Runtime/Language/ValueType.h b/lib/Runtime/Language/ValueType.h index f789653575c..431867a9b12 100644 --- a/lib/Runtime/Language/ValueType.h +++ b/lib/Runtime/Language/ValueType.h @@ -228,6 +228,7 @@ class ValueType bool IsTypedIntOrFloatArray() const; bool IsOptimizedTypedArray() const; + bool IsOptimizedVirtualTypedArray() const; bool IsLikelyOptimizedTypedArray() const; bool IsLikelyOptimizedVirtualTypedArray() const; diff --git a/lib/Runtime/Library/JavascriptGeneratorFunction.cpp b/lib/Runtime/Library/JavascriptGeneratorFunction.cpp index 9f9168a1a5f..385906a5d8a 100644 --- a/lib/Runtime/Library/JavascriptGeneratorFunction.cpp +++ b/lib/Runtime/Library/JavascriptGeneratorFunction.cpp @@ -307,7 +307,7 @@ namespace Js // to get the length from our private ScriptFunction instead of ourself. int len = 0; Var varLength; - if (scriptFunction->GetProperty(scriptFunction, PropertyIds::length, &varLength, NULL, requestContext)) + if (scriptFunction->GetProperty(this, PropertyIds::length, &varLength, NULL, requestContext)) { len = JavascriptConversion::ToInt32(varLength, requestContext); } diff --git a/lib/Runtime/Library/JavascriptObject.cpp b/lib/Runtime/Library/JavascriptObject.cpp index d75a99715dd..7682e9aa1d1 100644 --- a/lib/Runtime/Library/JavascriptObject.cpp +++ b/lib/Runtime/Library/JavascriptObject.cpp @@ -1857,7 +1857,7 @@ namespace Js size_t totalChars; if (SizeTAdd(propertyLength, ConstructNameGetSetLength, &totalChars) == S_OK) { - finalName = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, totalChars); + finalName = RecyclerNewArrayLeafZ(scriptContext->GetRecycler(), char16, totalChars); Assert(finalName != nullptr); const char16* propertyName = propertyRecord->GetBuffer(); Assert(propertyName != nullptr); diff --git a/lib/Runtime/Library/RegexHelper.cpp b/lib/Runtime/Library/RegexHelper.cpp index d8fcc12e817..669d474086a 100644 --- a/lib/Runtime/Library/RegexHelper.cpp +++ b/lib/Runtime/Library/RegexHelper.cpp @@ -724,7 +724,14 @@ namespace Js char16 currentChar = replaceStr[substitutionOffset + 1]; if (currentChar >= _u('0') && currentChar <= _u('9')) { + // We've found a substitution ref, like $32. In accordance with the standard (sec-getsubstitution), + // we recognize at most two decimal digits after the dollar sign. + + // This should be unsigned, but this would cause lots of compiler warnings unless we also make + // numGroups unsigned, because of a comparison below. int captureIndex = (int)(currentChar - _u('0')); + Assert(0 <= captureIndex && captureIndex <= 9); // numeric value of single decimal digit + offset = substitutionOffset + 2; if (offset < replaceLength) @@ -732,7 +739,9 @@ namespace Js currentChar = replaceStr[substitutionOffset + 2]; if (currentChar >= _u('0') && currentChar <= _u('9')) { + // Should also be unsigned; see captureIndex above. int tempCaptureIndex = (10 * captureIndex) + (int)(currentChar - _u('0')); + Assert(0 <= tempCaptureIndex && tempCaptureIndex < 100); // numeric value of 2-digit positive decimal number if (tempCaptureIndex < numGroups) { captureIndex = tempCaptureIndex; @@ -741,6 +750,7 @@ namespace Js } } + Assert(0 <= captureIndex && captureIndex < 100); // as above, value of 2-digit positive decimal number if (captureIndex < numGroups && (captureIndex != 0)) { Var group = getGroup(captureIndex, nonMatchValue); @@ -1204,10 +1214,13 @@ namespace Js JavascriptString* newString = nullptr; const char16* inputStr = input->GetString(); CharCount inputLength = input->GetLength(); - const int numGroups = pattern->NumGroups(); + const int rawNumGroups = pattern->NumGroups(); Var nonMatchValue = NonMatchValue(scriptContext, false); UnifiedRegex::GroupInfo lastMatch; // initially undefined + AssertOrFailFast(0 < rawNumGroups && rawNumGroups <= INT16_MAX); + const uint16 numGroups = uint16(rawNumGroups); + #if ENABLE_REGEX_CONFIG_OPTIONS RegexHelperTrace(scriptContext, UnifiedRegex::RegexStats::Replace, regularExpression, input, scriptContext->GetLibrary()->CreateStringFromCppLiteral(_u(""))); #endif @@ -1265,7 +1278,6 @@ namespace Js lastSuccessfulMatch = lastActualMatch; for (int groupId = 0; groupId < numGroups; groupId++) replaceArgs[groupId + 1] = GetGroup(scriptContext, pattern, input, nonMatchValue, groupId); -#pragma prefast(suppress:6386, "The write index numGroups + 1 is in the bound") replaceArgs[numGroups + 1] = JavascriptNumber::ToVar(lastActualMatch.offset, scriptContext); // The called function must see the global state updated by the current match @@ -1278,7 +1290,7 @@ namespace Js ThreadContext* threadContext = scriptContext->GetThreadContext(); Var replaceVar = threadContext->ExecuteImplicitCall(replacefn, ImplicitCall_Accessor, [=]()->Js::Var { - return replacefn->CallFunction(Arguments(CallInfo((ushort)(numGroups + 3)), replaceArgs)); + return replacefn->CallFunction(Arguments(CallInfo(UInt16Math::Add(numGroups, 3)), replaceArgs)); }); JavascriptString* replace = JavascriptConversion::ToString(replaceVar, scriptContext); concatenated.Append(input, offset, lastActualMatch.offset - offset); diff --git a/test/Regex/replace.baseline b/test/Regex/replace.baseline index ac5062164f2..446637400e8 100644 --- a/test/Regex/replace.baseline +++ b/test/Regex/replace.baseline @@ -38,6 +38,10 @@ replace(/b/ /*lastIndex=0*/ , "abc", "$12345678"); "a$12345678c" r.lastIndex=0 RegExp.${_,1,...,9}=["abc","","","","","","","","",""] +replace(/([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])([ab])/ /*lastIndex=0*/ , "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "$100"); +"a0" +r.lastIndex=0 +RegExp.${_,1,...,9}=["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","a","a","a","a","a","a","a","a","a"] replace(/b/ /*lastIndex=0*/ , "abc", []); "ac" diff --git a/test/Regex/replace.js b/test/Regex/replace.js index 19292dfad29..77539ec5163 100644 --- a/test/Regex/replace.js +++ b/test/Regex/replace.js @@ -229,6 +229,13 @@ replace(/b/, "abc", "$`$&$'"); replace(/b/, "abc", "1234567$"); replace(/b/, "abc", "$12345678"); +// Ensure that we only accept 2 digits after a $ in the replacement +// string, as per standards (sec-getsubstitution); this avoids an +// integer overflow in implementation of 'replace'. Correct behavior +// is 10th char of input ('a') followed by '0'; incorrect behavior is +// 100th char of input ('b'). +replace(new RegExp('([ab])'.repeat(101)), ("a".repeat(50) + "b".repeat(51)), "$100"); + echo(""); replace(/b/, "abc", function () { return ""; });