Chakra Scripting Engine Memory Corruption Vulnerability
Description
A remote code execution vulnerability exists in the way that the Chakra scripting engine handles objects in memory in Microsoft Edge (HTML-based). The vulnerability could corrupt memory in such a way that an attacker could execute arbitrary code in the context of the current user. An attacker who successfully exploited the vulnerability could gain the same user rights as the current user. If the current user is logged on with administrative user rights, an attacker who successfully exploited the vulnerability could take control of an affected system. An attacker could then install programs; view, change, or delete data; or create new accounts with full user rights. In a web-based attack scenario, an attacker could host a specially crafted website that is designed to exploit the vulnerability through Microsoft Edge (HTML-based) and then convince a user to view the website. The attacker could also take advantage of compromised websites and websites that accept or host user-provided content or advertisements. These websites could contain specially crafted content that could exploit the vulnerability. The security update addresses the vulnerability by modifying how the Chakra scripting engine handles objects in memory.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A remote code execution vulnerability in ChakraCore/Microsoft Edge can corrupt memory, allowing arbitrary code execution with user rights, patched in June 2019.
Vulnerability
Overview
CVE-2019-0991 is a remote code execution (RCE) vulnerability in the Chakra scripting engine (ChakraCore) used by Microsoft Edge (HTML-based). The root cause lies in how the engine handles objects in memory, specifically during array and type specialization operations in the global optimizer (GlobOpt). The patch introduces a new parameter valueOpnd to KillLiveElems and adds logic to kill object arrays with no missing values when certain conditions are not met, preventing memory corruption [1][2][4].
Attack
Vector and Prerequisites
The vulnerability is exploitable in a web-based scenario: an attacker hosts a specially crafted website (or compromises a legitimate one) that, when viewed using Microsoft Edge, triggers the memory corruption. No additional user interaction beyond viewing the page is required, though social engineering could be used to lure users. The attacker does not need prior authentication or special network access other than the ability to serve the malicious content [1].
Impact
Successful exploitation grants the attacker arbitrary code execution within the context of the current user. If the user has administrative privileges, the attacker can fully compromise the system: install programs, view/change/delete data, or create new accounts with full rights. This aligns with typical browser RCE impacts, making it critical for user-level attacks [1].
Mitigation and
Status
Microsoft released a security update on June 11, 2019 (Patch Tuesday) that modifies how the Chakra engine handles objects in memory, fixing the vulnerability. The fix was also incorporated into the ChakraCore open-source repository as commit 3d6226cc2d1077537220361c82e34a362c6c76ee [2]. Users should ensure Edge and Windows are updated. ChakraCore 1.11 received security updates until March 2021; later versions are community-maintained [3].
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
Microsoft.ChakraCoreNuGet | < 1.11.10 | 1.11.10 |
Affected products
3- Range: 0
- Range: 1.0..0
Patches
23d6226cc2d10[MERGE #6155 @pleath] ChakraCore servicing update for June, 2019
23 files changed · +430 −74
Build/Common.Build.Default.props+1 −0 modified@@ -17,6 +17,7 @@ <PlatformToolset Condition="'$(BuildToolVersion)'=='12.0'">v120</PlatformToolset> <PlatformToolset Condition="'$(BuildToolVersion)'=='14.0'">v140</PlatformToolset> <PlatformToolset Condition="'$(BuildToolVersion)'=='15.0'">v141</PlatformToolset> + <PlatformToolset Condition="'$(BuildToolVersion)'=='16.0'">v142</PlatformToolset> </PropertyGroup> <!-- Default ChakraDevConfigDir -->
Build/NuGet/.pack-version+1 −1 modified@@ -1 +1 @@ -1.11.9 +1.11.10
lib/Backend/BackwardPass.cpp+75 −4 modified@@ -1645,6 +1645,8 @@ BackwardPass::ProcessLoop(BasicBlock * lastBlock) { Assert(loop->symsAssignedToInLoop == nullptr); loop->symsAssignedToInLoop = JitAnew(this->globOpt->alloc, BVSparse<JitArenaAllocator>, this->globOpt->alloc); + Assert(loop->preservesNumberValue == nullptr); + loop->preservesNumberValue = JitAnew(this->globOpt->alloc, BVSparse<JitArenaAllocator>, this->globOpt->alloc); } FOREACH_BLOCK_BACKWARD_IN_RANGE_DEAD_OR_ALIVE(block, lastBlock, nullptr) @@ -4316,7 +4318,10 @@ BackwardPass::ProcessNoImplicitCallDef(IR::Instr *const instr) const bool transferArrayLengthSymUse = !!currentBlock->noImplicitCallArrayLengthSymUses->TestAndClear(dstSym->m_id); IR::Opnd *const src = instr->GetSrc1(); - if(!src || instr->GetSrc2()) + + // Stop attempting to transfer noImplicitCallUses symbol if the instr is not a transfer instr (based on the opcode's + // flags) or does not have the attributes to be a transfer instr (based on the existance of src and src2). + if(!src || (instr->GetSrc2() && !OpCodeAttr::NonIntTransfer(instr->m_opcode))) { return; } @@ -5004,16 +5009,24 @@ BackwardPass::UpdateArrayBailOutKind(IR::Instr *const instr) return; } + instr->GetDst()->AsIndirOpnd()->AllowConversion(true); IR::BailOutKind includeBailOutKinds = IR::BailOutInvalid; if (!baseValueType.IsNotNativeArray() && - (!baseValueType.IsLikelyNativeArray() || instr->GetSrc1()->IsVar()) && !currentBlock->noImplicitCallNativeArrayUses->IsEmpty() && !(instr->GetBailOutKind() & IR::BailOutOnArrayAccessHelperCall)) { // There is an upwards-exposed use of a native array. Since the array referenced by this instruction can be aliased, // this instruction needs to bail out if it converts the native array even if this array specifically is not // upwards-exposed. - includeBailOutKinds |= IR::BailOutConvertedNativeArray; + if (!baseValueType.IsLikelyNativeArray() || instr->GetSrc1()->IsVar()) + { + includeBailOutKinds |= IR::BailOutConvertedNativeArray; + } + else + { + // We are assuming that array conversion is impossible here, so make sure we execute code that fails if conversion does happen. + instr->GetDst()->AsIndirOpnd()->AllowConversion(false); + } } if(baseOpnd->IsArrayRegOpnd() && baseOpnd->AsArrayRegOpnd()->EliminatedUpperBoundCheck()) @@ -7410,6 +7423,52 @@ BackwardPass::TrackFloatSymEquivalence(IR::Instr *const instr) } } +bool +BackwardPass::SymIsIntconstOrSelf(Sym *sym, IR::Opnd *opnd) +{ + Assert(sym->IsStackSym()); + if (!opnd->IsRegOpnd()) + { + return false; + } + StackSym *opndSym = opnd->AsRegOpnd()->m_sym; + + if (sym == opndSym) + { + return true; + } + + if (!opndSym->IsSingleDef()) + { + return false; + } + + if (opndSym->GetInstrDef()->m_opcode == Js::OpCode::LdC_A_I4) + { + return true; + } + + return false; +} + +bool +BackwardPass::InstrPreservesNumberValues(IR::Instr *instr, Sym *defSym) +{ + if (instr->m_opcode == Js::OpCode::Ld_A) + { + if (instr->GetSrc1()->IsRegOpnd()) + { + IR::RegOpnd *src1 = instr->GetSrc1()->AsRegOpnd(); + if (src1->m_sym->IsSingleDef()) + { + instr = src1->m_sym->GetInstrDef(); + } + } + } + return (OpCodeAttr::ProducesNumber(instr->m_opcode) || + (instr->m_opcode == Js::OpCode::Add_A && this->SymIsIntconstOrSelf(defSym, instr->GetSrc1()) && this->SymIsIntconstOrSelf(defSym, instr->GetSrc2()))); +} + bool BackwardPass::ProcessDef(IR::Opnd * opnd) { @@ -7424,7 +7483,19 @@ BackwardPass::ProcessDef(IR::Opnd * opnd) this->InvalidateCloneStrCandidate(opnd); if ((tag == Js::BackwardPhase) && IsPrePass()) { - this->currentPrePassLoop->symsAssignedToInLoop->Set(sym->m_id); + bool firstDef = !this->currentPrePassLoop->symsAssignedToInLoop->TestAndSet(sym->m_id); + + if (firstDef) + { + if (this->InstrPreservesNumberValues(this->currentInstr, sym)) + { + this->currentPrePassLoop->preservesNumberValue->Set(sym->m_id); + } + } + else if (!this->InstrPreservesNumberValues(this->currentInstr, sym)) + { + this->currentPrePassLoop->preservesNumberValue->Clear(sym->m_id); + } } } }
lib/Backend/BackwardPass.h+3 −0 modified@@ -36,6 +36,9 @@ class BackwardPass bool ProcessDef(IR::Opnd * opnd); void ProcessTransfers(IR::Instr * instr); void ProcessFieldKills(IR::Instr * instr); + bool SymIsIntconstOrSelf(Sym *sym, IR::Opnd *opnd); + bool InstrPreservesNumberValues(IR::Instr *instr, Sym *defSym); + template<typename T> void ClearBucketsOnFieldKill(IR::Instr *instr, HashTable<T> *table); StackSym* ProcessByteCodeUsesDst(IR::ByteCodeUsesInstr * byteCodeUsesInstr); const BVSparse<JitArenaAllocator>* ProcessByteCodeUsesSrcs(IR::ByteCodeUsesInstr * byteCodeUsesInstr);
lib/Backend/FlowGraph.h+2 −0 modified@@ -588,6 +588,7 @@ class Loop // cleanup in PreOptPeep in the pre-pass of a loop. For aggressively transferring // values in prepass, we need to know if a source sym was ever assigned to in a loop. BVSparse<JitArenaAllocator> *symsAssignedToInLoop; + BVSparse<JitArenaAllocator> *preservesNumberValue; BailOutInfo * bailOutInfo; IR::BailOutInstr * toPrimitiveSideEffectCheck; @@ -733,6 +734,7 @@ class Loop symsAssignedToInLoop(nullptr), needImplicitCallBailoutChecksForJsArrayCheckHoist(false), inductionVariables(nullptr), + preservesNumberValue(nullptr), dominatingLoopCountableBlock(nullptr), loopCount(nullptr), loopCountBasedBoundBaseSyms(nullptr),
lib/Backend/GlobOpt.cpp+68 −1 modified@@ -1244,7 +1244,7 @@ void GlobOpt::InsertValueCompensation( { IR::Instr *const newInstr = IR::Instr::New( - Js::OpCode::Ld_I4, + Js::OpCode::Ld_A, IR::RegOpnd::New(mergedHeadSegmentLengthSym, mergedHeadSegmentLengthSym->GetType(), func), IR::RegOpnd::New(predecessorHeadSegmentLengthSym, predecessorHeadSegmentLengthSym->GetType(), func), func); @@ -2694,6 +2694,48 @@ GlobOpt::OptInstr(IR::Instr *&instr, bool* isInstrRemoved) return instrNext; } +bool +GlobOpt::IsNonNumericRegOpnd(IR::RegOpnd *opnd, bool inGlobOpt) const +{ + if (opnd == nullptr) + { + return false; + } + + if (opnd->m_sym->m_isNotNumber) + { + return true; + } + + if (!inGlobOpt) + { + return false; + } + + if (opnd->GetValueType().IsNumber() || currentBlock->globOptData.IsTypeSpecialized(opnd->m_sym)) + { + if (!this->IsLoopPrePass()) + { + return false; + } + + Value * opndValue = this->currentBlock->globOptData.FindValue(opnd->m_sym); + ValueInfo * opndValueInfo = opndValue ? opndValue->GetValueInfo() : nullptr; + if (!opndValueInfo) + { + return true; + } + if (this->prePassLoop->preservesNumberValue->Test(opnd->m_sym->m_id)) + { + return false; + } + + return !this->IsSafeToTransferInPrepass(opnd->m_sym, opndValueInfo); + } + + return true; +} + bool GlobOpt::OptTagChecks(IR::Instr *instr) { @@ -12827,6 +12869,26 @@ GlobOpt::ProcessValueKills(IR::Instr *const instr) it.RemoveCurrent(); } } + else if(kills.KillsObjectArraysWithNoMissingValues()) + { + // Some operations may kill objects with arrays-with-no-missing-values in unlikely circumstances. Convert their value types to likely + // versions so that the checks have to be redone. + for(auto it = valuesToKillOnCalls->GetIteratorWithRemovalSupport(); it.IsValid(); it.MoveNext()) + { + Value *const value = it.CurrentValue(); + ValueInfo *const valueInfo = value->GetValueInfo(); + Assert( + valueInfo->IsArrayOrObjectWithArray() || + valueInfo->IsOptimizedVirtualTypedArray() || + valueInfo->IsOptimizedTypedArray() && valueInfo->AsArrayValueInfo()->HeadSegmentLengthSym()); + if(!valueInfo->IsArrayOrObjectWithArray() || valueInfo->IsArray() || !valueInfo->HasNoMissingValues()) + { + continue; + } + ChangeValueType(nullptr, value, valueInfo->Type().ToLikely(), false); + it.RemoveCurrent(); + } + } if(kills.KillsNativeArrays()) { @@ -13358,6 +13420,11 @@ GlobOpt::CheckJsArrayKills(IR::Instr *const instr) { kills.SetKillsArrayLengths(); } + + if(doArrayMissingValueCheckHoist && !(useValueTypes && arrayValueType.IsArray())) + { + kills.SetKillsObjectArraysWithNoMissingValues(); + } break; }
lib/Backend/GlobOptFields.cpp+22 −12 modified@@ -208,7 +208,7 @@ void GlobOpt::KillLiveFields(BVSparse<JitArenaAllocator> *const fieldsToKill, BV } void -GlobOpt::KillLiveElems(IR::IndirOpnd * indirOpnd, BVSparse<JitArenaAllocator> * bv, bool inGlobOpt, Func *func) +GlobOpt::KillLiveElems(IR::IndirOpnd * indirOpnd, IR::Opnd * valueOpnd, BVSparse<JitArenaAllocator> * bv, bool inGlobOpt, Func *func) { IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd(); @@ -225,14 +225,7 @@ GlobOpt::KillLiveElems(IR::IndirOpnd * indirOpnd, BVSparse<JitArenaAllocator> * // - We check the type specialization status for the sym as well. For the purpose of doing kills, we can assume that // if type specialization happened, that fields don't need to be killed. Note that they may be killed in the next // pass based on the value. - if (func->GetThisOrParentInlinerHasArguments() || - ( - indexOpnd && - ( - indexOpnd->m_sym->m_isNotNumber || - (inGlobOpt && !indexOpnd->GetValueType().IsNumber() && !currentBlock->globOptData.IsTypeSpecialized(indexOpnd->m_sym)) - ) - )) + if (func->GetThisOrParentInlinerHasArguments() || this->IsNonNumericRegOpnd(indexOpnd, inGlobOpt)) { this->KillAllFields(bv); // This also kills all property type values, as the same bit-vector tracks those stack syms SetAnyPropertyMayBeWrittenTo(); @@ -248,6 +241,23 @@ GlobOpt::KillLiveElems(IR::IndirOpnd * indirOpnd, BVSparse<JitArenaAllocator> * // Write/delete to a non-integer numeric index can't alias a name on the RHS of a dot, but it change object layout this->KillAllObjectTypes(bv); } + else if ((!valueOpnd || valueOpnd->IsVar()) && this->objectTypeSyms != nullptr) + { + // If we wind up converting a native array, block final-type opt at this point, because we could evolve + // to a type with the wrong type ID. Do this by noting that we may have evolved any type and so must + // check it before evolving it further. + IR::RegOpnd *baseOpnd = indirOpnd->GetBaseOpnd(); + Value * baseValue = baseOpnd ? this->currentBlock->globOptData.FindValue(baseOpnd->m_sym) : nullptr; + ValueInfo * baseValueInfo = baseValue ? baseValue->GetValueInfo() : nullptr; + if (!baseValueInfo || !baseValueInfo->IsNotNativeArray()) + { + if (this->currentBlock->globOptData.maybeWrittenTypeSyms == nullptr) + { + this->currentBlock->globOptData.maybeWrittenTypeSyms = JitAnew(this->alloc, BVSparse<JitArenaAllocator>, this->alloc); + } + this->currentBlock->globOptData.maybeWrittenTypeSyms->Or(this->objectTypeSyms); + } + } } } @@ -340,7 +350,7 @@ GlobOpt::ProcessFieldKills(IR::Instr *instr, BVSparse<JitArenaAllocator> *bv, bo case Js::OpCode::StElemI_A_Strict: Assert(dstOpnd != nullptr); KillLiveFields(this->lengthEquivBv, bv); - KillLiveElems(dstOpnd->AsIndirOpnd(), bv, inGlobOpt, instr->m_func); + KillLiveElems(dstOpnd->AsIndirOpnd(), instr->GetSrc1(), bv, inGlobOpt, instr->m_func); if (inGlobOpt) { KillObjectHeaderInlinedTypeSyms(this->currentBlock, false); @@ -350,7 +360,7 @@ GlobOpt::ProcessFieldKills(IR::Instr *instr, BVSparse<JitArenaAllocator> *bv, bo case Js::OpCode::InitComputedProperty: case Js::OpCode::InitGetElemI: case Js::OpCode::InitSetElemI: - KillLiveElems(dstOpnd->AsIndirOpnd(), bv, inGlobOpt, instr->m_func); + KillLiveElems(dstOpnd->AsIndirOpnd(), instr->GetSrc1(), bv, inGlobOpt, instr->m_func); if (inGlobOpt) { KillObjectHeaderInlinedTypeSyms(this->currentBlock, false); @@ -360,7 +370,7 @@ GlobOpt::ProcessFieldKills(IR::Instr *instr, BVSparse<JitArenaAllocator> *bv, bo case Js::OpCode::DeleteElemI_A: case Js::OpCode::DeleteElemIStrict_A: Assert(dstOpnd != nullptr); - KillLiveElems(instr->GetSrc1()->AsIndirOpnd(), bv, inGlobOpt, instr->m_func); + KillLiveElems(instr->GetSrc1()->AsIndirOpnd(), nullptr, bv, inGlobOpt, instr->m_func); break; case Js::OpCode::DeleteFld:
lib/Backend/GlobOpt.h+7 −1 modified@@ -317,6 +317,7 @@ class JsArrayKills { bool killsAllArrays : 1; bool killsArraysWithNoMissingValues : 1; + bool killsObjectArraysWithNoMissingValues : 1; bool killsNativeArrays : 1; bool killsArrayHeadSegments : 1; bool killsArrayHeadSegmentLengths : 1; @@ -342,6 +343,9 @@ class JsArrayKills bool KillsArraysWithNoMissingValues() const { return killsArraysWithNoMissingValues; } void SetKillsArraysWithNoMissingValues() { killsArraysWithNoMissingValues = true; } + bool KillsObjectArraysWithNoMissingValues() const { return killsObjectArraysWithNoMissingValues; } + void SetKillsObjectArraysWithNoMissingValues() { killsObjectArraysWithNoMissingValues = true; } + bool KillsNativeArrays() const { return killsNativeArrays; } void SetKillsNativeArrays() { killsNativeArrays = true; } @@ -769,6 +773,8 @@ class GlobOpt const bool lossy = false, const bool forceInvariantHoisting = false, IR::BailOutKind bailoutKind = IR::BailOutInvalid); void HoistInvariantValueInfo(ValueInfo *const invariantValueInfoToHoist, Value *const valueToUpdate, BasicBlock *const targetBlock); void OptHoistUpdateValueType(Loop* loop, IR::Instr* instr, IR::Opnd** srcOpndPtr, Value *const srcVal); + bool IsNonNumericRegOpnd(IR::RegOpnd *opnd, bool inGlobOpt) const; + public: static bool IsTypeSpecPhaseOff(Func const * func); static bool DoAggressiveIntTypeSpec(Func const * func); @@ -891,7 +897,7 @@ class GlobOpt void KillLiveFields(StackSym * stackSym, BVSparse<JitArenaAllocator> * bv); void KillLiveFields(PropertySym * propertySym, BVSparse<JitArenaAllocator> * bv); void KillLiveFields(BVSparse<JitArenaAllocator> *const fieldsToKill, BVSparse<JitArenaAllocator> *const bv) const; - void KillLiveElems(IR::IndirOpnd * indirOpnd, BVSparse<JitArenaAllocator> * bv, bool inGlobOpt, Func *func); + void KillLiveElems(IR::IndirOpnd * indirOpnd, IR::Opnd * valueOpnd, BVSparse<JitArenaAllocator> * bv, bool inGlobOpt, Func *func); void KillAllFields(BVSparse<JitArenaAllocator> * bv); void SetAnyPropertyMayBeWrittenTo(); void AddToPropertiesWrittenTo(Js::PropertyId propertyId);
lib/Backend/InductionVariable.cpp+34 −0 modified@@ -81,6 +81,21 @@ bool InductionVariable::Add(const int n) if(n == 0) return true; + int lowerBound = changeBounds.LowerBound(); + int upperBound = changeBounds.UpperBound(); + + if (n < 0 && (lowerBound < upperBound || (lowerBound == upperBound && lowerBound > 0))) + { + isChangeDeterminate = false; + return false; + } + + if (n > 0 && (lowerBound > upperBound || (lowerBound == upperBound && lowerBound < 0))) + { + isChangeDeterminate = false; + return false; + } + int newLowerBound; if(changeBounds.LowerBound() == IntConstMin) { @@ -148,6 +163,25 @@ void InductionVariable::Merge(const InductionVariable &other) // The value number may be different, the caller will give the merged info the appropriate value number isChangeDeterminate &= other.isChangeDeterminate; + if(!isChangeDeterminate) + return; + + int lowerBound = this->ChangeBounds().LowerBound(); + int upperBound = this->ChangeBounds().UpperBound(); + + int otherLowerBound = other.ChangeBounds().LowerBound(); + int otherUpperBound = other.ChangeBounds().UpperBound(); + + if ((lowerBound < upperBound || (lowerBound == upperBound && lowerBound > 0)) && !(otherLowerBound < otherUpperBound || (otherLowerBound == otherUpperBound && otherLowerBound > 0))) + { + isChangeDeterminate = false; + } + + if ((lowerBound > upperBound || (lowerBound == upperBound && lowerBound < 0)) && !(otherLowerBound > otherUpperBound || (otherLowerBound == otherUpperBound && otherLowerBound < 0))) + { + isChangeDeterminate = false; + } + if(!isChangeDeterminate) return;
lib/Backend/Inline.cpp+21 −18 modified@@ -69,7 +69,7 @@ Inline::Optimize(Func *func, __in_ecount_opt(callerArgOutCount) IR::Instr *calle if (instr->AsLabelInstr()->m_isForInExit) { - Assert(this->currentForInDepth != 0); + AssertOrFailFast(this->currentForInDepth != 0); this->currentForInDepth--; } } @@ -2158,14 +2158,12 @@ Inline::InlineBuiltInFunction(IR::Instr *callInstr, const FunctionJITTimeInfo * IR::Instr *inlineBuiltInEndInstr = nullptr; if (inlineCallOpCode == Js::OpCode::InlineFunctionApply) { - inlineBuiltInEndInstr = InlineApply(callInstr, inlineeData, inlinerData, symCallerThis, pIsInlined, profileId, recursiveInlineDepth, inlineCallArgCount - (usesThisArgument ? 1 : 0)); - return inlineBuiltInEndInstr->m_next; + return InlineApply(callInstr, inlineeData, inlinerData, symCallerThis, pIsInlined, profileId, recursiveInlineDepth, inlineCallArgCount - (usesThisArgument ? 1 : 0)); } if (inlineCallOpCode == Js::OpCode::InlineFunctionCall) { - inlineBuiltInEndInstr = InlineCall(callInstr, inlineeData, inlinerData, symCallerThis, pIsInlined, profileId, recursiveInlineDepth); - return inlineBuiltInEndInstr->m_next; + return InlineCall(callInstr, inlineeData, inlinerData, symCallerThis, pIsInlined, profileId, recursiveInlineDepth); } @@ -2415,9 +2413,10 @@ IR::Instr* Inline::InlineApply(IR::Instr *callInstr, const FunctionJITTimeInfo * // We may still decide not to inline. *pIsInlined = false; + IR::Instr* instrNext = callInstr->m_next; if (argsCount == 0) { - return callInstr; + return instrNext; } Js::BuiltinFunction builtInId = Js::JavascriptLibrary::GetBuiltInForFuncInfo(applyData->GetLocalFunctionId()); @@ -2459,7 +2458,7 @@ IR::Instr* Inline::InlineApply(IR::Instr *callInstr, const FunctionJITTimeInfo * if (PHASE_OFF1(Js::InlineApplyWithoutArrayArgPhase)) { *pIsInlined = false; - return callInstr; + return instrNext; } *pIsInlined = true; @@ -2490,7 +2489,7 @@ IR::Instr* Inline::InlineApply(IR::Instr *callInstr, const FunctionJITTimeInfo * { INLINE_TESTTRACE(_u("INLINING: Skip Inline: Supporting inlining func.apply(this, array) or func.apply(this, arguments) with formals in the parent function only when func is a built-in inlinable as apply target \tCaller: %s (%s)\n"), inlinerData->GetBody()->GetDisplayName(), inlinerData->GetDebugNumberSet(debugStringBuffer)); - return callInstr; + return instrNext; } } @@ -2506,6 +2505,7 @@ IR::Instr* Inline::InlineApply(IR::Instr *callInstr, const FunctionJITTimeInfo * IR::Instr * Inline::InlineApplyWithArgumentsObject(IR::Instr * callInstr, IR::Instr * argsObjectArgInstr, const FunctionJITTimeInfo * funcInfo) { + IR::Instr* instrNext = callInstr->m_next; IR::Instr* ldHeapArguments = argsObjectArgInstr->GetSrc1()->GetStackSym()->GetInstrDef(); argsObjectArgInstr->ReplaceSrc1(ldHeapArguments->GetDst()); @@ -2595,14 +2595,15 @@ IR::Instr * Inline::InlineApplyWithArgumentsObject(IR::Instr * callInstr, IR::In argout = IR::Instr::New(Js::OpCode::ArgOut_A_Dynamic, linkOpnd2, explicitThisArgOut->GetSrc1(), linkOpnd1, callInstr->m_func); // push explicit this as this pointer callInstr->InsertBefore(argout); - return callInstr; + return instrNext; } /* This method will only do CallDirect style inlining of built-in targets. No script function inlining. */ IR::Instr * Inline::InlineApplyBuiltInTargetWithArray(IR::Instr * callInstr, const FunctionJITTimeInfo * applyInfo, const FunctionJITTimeInfo * builtInInfo) { + IR::Instr* instrNext = callInstr->m_next; IR::Instr * implicitThisArgOut = nullptr; IR::Instr * explicitThisArgOut = nullptr; IR::Instr * arrayArgOut = nullptr; @@ -2620,7 +2621,7 @@ IR::Instr * Inline::InlineApplyBuiltInTargetWithArray(IR::Instr * callInstr, con IR::Instr* applyTargetLdInstr = nullptr; if (!TryGetApplyAndTargetLdInstrs(callInstr, &applyLdInstr, &applyTargetLdInstr)) { - return callInstr; + return instrNext; } // Fixed function/function object checks for target built-in callInstr->ReplaceSrc1(applyTargetLdInstr->GetDst()); @@ -2685,11 +2686,12 @@ IR::Instr * Inline::InlineApplyBuiltInTargetWithArray(IR::Instr * callInstr, con callInstr->ReplaceSrc1(helperCallOpnd); callInstr->ReplaceSrc2(argOut->GetDst()); - return callInstr; + return instrNext; } IR::Instr * Inline::InlineApplyWithoutArrayArgument(IR::Instr *callInstr, const FunctionJITTimeInfo * applyInfo, const FunctionJITTimeInfo * applyTargetInfo) { + IR::Instr* instrNext = callInstr->m_next; IR::Instr * implicitThisArgOut = nullptr; IR::Instr * explicitThisArgOut = nullptr; IR::Instr * dummyInstr = nullptr; @@ -2728,12 +2730,12 @@ IR::Instr * Inline::InlineApplyWithoutArrayArgument(IR::Instr *callInstr, const if (!callTargetStackSym->IsSingleDef()) { - return callInstr; + return instrNext; } if (!applyTargetInfo) { - return callInstr; + return instrNext; } bool safeThis = false; @@ -2745,7 +2747,7 @@ IR::Instr * Inline::InlineApplyWithoutArrayArgument(IR::Instr *callInstr, const callInstr->InsertBefore(bytecodeUses); } - return callInstr; + return instrNext; } void Inline::GetArgInstrsForCallAndApply(IR::Instr* callInstr, IR::Instr** implicitThisArgOut, IR::Instr** explicitThisArgOut, IR::Instr** argumentsOrArrayArgOut, uint &argOutCount) @@ -3015,7 +3017,7 @@ Inline::InlineCallApplyTarget_Shared(IR::Instr *callInstr, bool originalCallTarg // instrNext IR::Instr* instrNext = callInstr->m_next; - return InlineFunctionCommon(callInstr, originalCallTargetOpndIsJITOpt, originalCallTargetStackSym, inlineeData, inlinee, instrNext, returnValueOpnd, callInstr, nullptr, recursiveInlineDepth, safeThis, isApplyTarget)->m_prev; + return InlineFunctionCommon(callInstr, originalCallTargetOpndIsJITOpt, originalCallTargetStackSym, inlineeData, inlinee, instrNext, returnValueOpnd, callInstr, nullptr, recursiveInlineDepth, safeThis, isApplyTarget); } IR::Opnd * @@ -3029,14 +3031,15 @@ Inline::ConvertToInlineBuiltInArgOut(IR::Instr * argInstr) IR::Instr* Inline::InlineCall(IR::Instr *callInstr, const FunctionJITTimeInfo *funcInfo, const FunctionJITTimeInfo * inlinerData, const StackSym *symCallerThis, bool* pIsInlined, uint callSiteId, uint recursiveInlineDepth) { + IR::Instr* instrNext = callInstr->m_next; Func *func = callInstr->m_func; Js::BuiltinFunction builtInId = Js::JavascriptLibrary::GetBuiltInForFuncInfo(funcInfo->GetLocalFunctionId()); *pIsInlined = false; if (PHASE_OFF(Js::InlineCallPhase, this->topFunc) || PHASE_OFF(Js::InlineCallPhase, func) || !this->topFunc->GetJITFunctionBody()->GetInParamsCount()) { - return callInstr; + return instrNext; } // Convert all the current ARG_OUT to ArgOut_A_InlineBuiltIn @@ -3045,7 +3048,7 @@ Inline::InlineCall(IR::Instr *callInstr, const FunctionJITTimeInfo *funcInfo, co if (!GetDefInstr(linkOpnd)->GetSrc2()->IsSymOpnd()) { // There is no benefit of inlining.call() with no arguments. - return callInstr; + return instrNext; } *pIsInlined = true; @@ -3125,7 +3128,7 @@ Inline::InlineCall(IR::Instr *callInstr, const FunctionJITTimeInfo *funcInfo, co } clonedArgout->SetSrc2(startCall->GetDst()); Assert(GetDefInstr(orgLinkOpnd) == argImplicitInstr); - return callInstr; + return instrNext; } bool
lib/Backend/IR.cpp+8 −1 modified@@ -3307,7 +3307,14 @@ bool Instr::TransfersSrcValue() // Consider: Add opcode attribute to indicate whether the opcode would use the value or not - return this->GetDst() != nullptr && this->GetSrc2() == nullptr && !OpCodeAttr::DoNotTransfer(this->m_opcode) && !this->CallsAccessor(); + return + this->GetDst() != nullptr && + + // The lack of a Src2 does not always indicate that the instr is not a transfer instr (ex: StSlotChkUndecl). + (this->GetSrc2() == nullptr || OpCodeAttr::NonIntTransfer(this->m_opcode)) && + + !OpCodeAttr::DoNotTransfer(this->m_opcode) && + !this->CallsAccessor(); }
lib/Backend/JnHelperMethodList.h+6 −0 modified@@ -205,6 +205,12 @@ HELPERCALLCHK(Op_SetNativeIntElementI_Int32, Js::JavascriptOperators::OP_SetNati HELPERCALLCHK(Op_SetNativeFloatElementI_Int32, Js::JavascriptOperators::OP_SetNativeFloatElementI_Int32, AttrCanThrow) HELPERCALLCHK(Op_SetNativeIntElementI_UInt32, Js::JavascriptOperators::OP_SetNativeIntElementI_UInt32, AttrCanThrow) HELPERCALLCHK(Op_SetNativeFloatElementI_UInt32, Js::JavascriptOperators::OP_SetNativeFloatElementI_UInt32, AttrCanThrow) +HELPERCALLCHK(Op_SetNativeIntElementI_NoConvert, Js::JavascriptOperators::OP_SetNativeIntElementI_NoConvert, AttrCanThrow) +HELPERCALLCHK(Op_SetNativeFloatElementI_NoConvert, Js::JavascriptOperators::OP_SetNativeFloatElementI_NoConvert, AttrCanThrow) +HELPERCALLCHK(Op_SetNativeIntElementI_Int32_NoConvert, Js::JavascriptOperators::OP_SetNativeIntElementI_Int32_NoConvert, AttrCanThrow) +HELPERCALLCHK(Op_SetNativeFloatElementI_Int32_NoConvert, Js::JavascriptOperators::OP_SetNativeFloatElementI_Int32_NoConvert, AttrCanThrow) +HELPERCALLCHK(Op_SetNativeIntElementI_UInt32_NoConvert, Js::JavascriptOperators::OP_SetNativeIntElementI_UInt32_NoConvert, AttrCanThrow) +HELPERCALLCHK(Op_SetNativeFloatElementI_UInt32_NoConvert, Js::JavascriptOperators::OP_SetNativeFloatElementI_UInt32_NoConvert, AttrCanThrow) HELPERCALLCHK(ScrArr_SetNativeIntElementC, Js::JavascriptArray::OP_SetNativeIntElementC, AttrCanNotBeReentrant) HELPERCALLCHK(ScrArr_SetNativeFloatElementC, Js::JavascriptArray::OP_SetNativeFloatElementC, AttrCanNotBeReentrant) HELPERCALLCHK(Op_DeleteElementI, Js::JavascriptOperators::OP_DeleteElementI, AttrCanThrow)
lib/Backend/Lower.cpp+11 −6 modified@@ -9001,6 +9001,8 @@ Lowerer::LowerStElemI(IR::Instr * instr, Js::PropertyOperationFlags flags, bool AssertMsg(dst->IsIndirOpnd(), "Expected indirOpnd on StElementI"); + bool allowConvert = dst->AsIndirOpnd()->ConversionAllowed(); + #if !FLOATVAR if (dst->AsIndirOpnd()->GetBaseOpnd()->GetValueType().IsLikelyOptimizedTypedArray() && src1->IsRegOpnd()) { @@ -9085,15 +9087,17 @@ Lowerer::LowerStElemI(IR::Instr * instr, Js::PropertyOperationFlags flags, bool { helperMethod = srcType == TyVar ? IR::HelperOp_SetElementI_Int32 : - srcType == TyInt32 ? IR::HelperOp_SetNativeIntElementI_Int32 : - IR::HelperOp_SetNativeFloatElementI_Int32; + srcType == TyInt32 ? + (allowConvert ? IR::HelperOp_SetNativeIntElementI_Int32 : IR::HelperOp_SetNativeIntElementI_Int32_NoConvert) : + (allowConvert ? IR::HelperOp_SetNativeFloatElementI_Int32 : IR::HelperOp_SetNativeFloatElementI_Int32_NoConvert) ; } else if (indexOpnd->GetType() == TyUint32) { helperMethod = srcType == TyVar ? IR::HelperOp_SetElementI_UInt32 : - srcType == TyInt32 ? IR::HelperOp_SetNativeIntElementI_UInt32 : - IR::HelperOp_SetNativeFloatElementI_UInt32; + srcType == TyInt32 ? + (allowConvert ? IR::HelperOp_SetNativeIntElementI_UInt32 : IR::HelperOp_SetNativeIntElementI_UInt32_NoConvert) : + (allowConvert ? IR::HelperOp_SetNativeFloatElementI_UInt32 : IR::HelperOp_SetNativeFloatElementI_UInt32_NoConvert) ; } else { @@ -9111,8 +9115,9 @@ Lowerer::LowerStElemI(IR::Instr * instr, Js::PropertyOperationFlags flags, bool if (srcType != TyVar) { - helperMethod = - srcType == TyInt32 ? IR::HelperOp_SetNativeIntElementI : IR::HelperOp_SetNativeFloatElementI; + helperMethod = srcType == TyInt32 ? + (allowConvert ? IR::HelperOp_SetNativeIntElementI : IR::HelperOp_SetNativeIntElementI_NoConvert) : + (allowConvert ? IR::HelperOp_SetNativeFloatElementI : IR::HelperOp_SetNativeFloatElementI_NoConvert); } }
lib/Backend/Opnd.cpp+2 −0 modified@@ -2538,6 +2538,7 @@ IndirOpnd::New(RegOpnd *baseOpnd, int32 offset, IRType type, Func *func, bool do indirOpnd->m_type = type; indirOpnd->SetIsJITOptimizedReg(false); + indirOpnd->m_conversionAllowed = false; indirOpnd->m_kind = OpndKindIndir; @@ -2596,6 +2597,7 @@ IndirOpnd::CopyInternal(Func *func) newOpnd->canStoreTemp = this->canStoreTemp; newOpnd->SetOffset(m_offset, m_dontEncode); newOpnd->SetIsJITOptimizedReg(this->GetIsJITOptimizedReg()); + newOpnd->m_conversionAllowed = this->m_conversionAllowed; #if DBG_DUMP newOpnd->m_addrKind = m_addrKind;
lib/Backend/Opnd.h+3 −0 modified@@ -1662,6 +1662,8 @@ class IndirOpnd: public Opnd byte GetScale() const; void SetScale(byte scale); bool TryGetIntConstIndexValue(bool trySym, IntConstType *pValue, bool *pIsNotInt); + void AllowConversion(bool value) { m_conversionAllowed = value; } + bool ConversionAllowed() const { return m_conversionAllowed; } #if DBG_DUMP || defined(ENABLE_IR_VIEWER) const char16 * GetDescription(); IR::AddrOpndKind GetAddrKind() const; @@ -1678,6 +1680,7 @@ class IndirOpnd: public Opnd RegOpnd * m_indexOpnd; int32 m_offset; byte m_scale; + bool m_conversionAllowed; Func * m_func; // We need the allocator to copy the base and index... #if DBG_DUMP || defined(ENABLE_IR_VIEWER)
lib/Common/ChakraCoreVersion.h+1 −1 modified@@ -17,7 +17,7 @@ // ChakraCore version number definitions (used in ChakraCore binary metadata) #define CHAKRA_CORE_MAJOR_VERSION 1 #define CHAKRA_CORE_MINOR_VERSION 11 -#define CHAKRA_CORE_PATCH_VERSION 9 +#define CHAKRA_CORE_PATCH_VERSION 10 #define CHAKRA_CORE_VERSION_RELEASE_QFE 0 // Redundant with PATCH_VERSION. Keep this value set to 0. // -------------
lib/Parser/Parse.cpp+0 −14 modified@@ -5444,20 +5444,6 @@ void Parser::ParseFncDeclHelper(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint, us } return false; }); - - if (pnodeFnc->IsBodyAndParamScopeMerged() && !fDeclaration && pnodeFnc->pnodeName != nullptr) - { - Assert(pnodeFnc->pnodeName->nop == knopVarDecl); - Symbol* funcSym = pnodeFnc->pnodeName->sym; - if (funcSym->GetPid()->GetTopRef()->GetFuncScopeId() > pnodeFnc->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. - pnodeFnc->ResetBodyAndParamScopeMerged(); - } - } } }
lib/Runtime/ByteCode/ByteCodeGenerator.cpp+11 −0 modified@@ -3091,6 +3091,17 @@ void ByteCodeGenerator::ProcessCapturedSym(Symbol *sym) Assert(sym->NeedsSlotAlloc(this, funcHome) || sym->GetIsGlobal() || sym->GetIsModuleImport() || sym->GetIsModuleExportStorage()); + if (sym->GetScope()->GetScopeType() == ScopeType_FuncExpr) + { + if ((funcHome->GetParamScope() && Scope::HasSymbolName(funcHome->GetParamScope(), sym->GetName())) || + (funcHome->IsBodyAndParamScopeMerged() && funcHome->GetBodyScope() && Scope::HasSymbolName(funcHome->GetBodyScope(), sym->GetName()))) + { + // Make sure the function expression scope gets instantiated, since we can't merge the name symbol into another scope. + // Make it an object, since that's the only case the code gen can currently handle. + sym->GetScope()->SetIsObject(); + } + } + // If this is not a local property, or not all its references can be tracked, or // it's not scoped to the function, or we're in debug mode, disable the delayed capture optimization. if (funcHome->IsGlobalFunction() ||
lib/Runtime/ByteCode/OpCodes.h+10 −10 modified@@ -464,21 +464,21 @@ MACRO_WMS( StEnvSlot, ElementSlotI2, None) MACRO_WMS( StInnerSlot, ElementSlotI2, None) MACRO_WMS( StLocalSlot, ElementSlotI1, None) MACRO_EXTEND_WMS( StParamSlot, ElementSlotI1, None) -MACRO_BACKEND_ONLY( StSlotChkUndecl, ElementSlot, OpSideEffect) -MACRO_EXTEND_WMS( StEnvSlotChkUndecl, ElementSlotI2, OpSideEffect) -MACRO_EXTEND_WMS( StInnerSlotChkUndecl, ElementSlotI2, OpSideEffect) -MACRO_EXTEND_WMS( StLocalSlotChkUndecl, ElementSlotI1, OpSideEffect) -MACRO_EXTEND_WMS( StParamSlotChkUndecl, ElementSlotI1, OpSideEffect) +MACRO_BACKEND_ONLY( StSlotChkUndecl, ElementSlot, OpSideEffect|OpNonIntTransfer) // Src1 is transferred to Dst, Src2 holds the same value as Dst to communicate Dst's liveness. +MACRO_EXTEND_WMS( StEnvSlotChkUndecl, ElementSlotI2, OpSideEffect|OpNonIntTransfer) +MACRO_EXTEND_WMS( StInnerSlotChkUndecl, ElementSlotI2, OpSideEffect|OpNonIntTransfer) +MACRO_EXTEND_WMS( StLocalSlotChkUndecl, ElementSlotI1, OpSideEffect|OpNonIntTransfer) +MACRO_EXTEND_WMS( StParamSlotChkUndecl, ElementSlotI1, OpSideEffect|OpNonIntTransfer) MACRO_EXTEND_WMS( StObjSlot, ElementSlot, OpSideEffect) MACRO_EXTEND_WMS( StInnerObjSlot, ElementSlotI2, OpSideEffect) MACRO_EXTEND_WMS( StLocalObjSlot, ElementSlotI1, OpSideEffect) MACRO_EXTEND_WMS( StParamObjSlot, ElementSlotI1, OpSideEffect) -MACRO_EXTEND_WMS( StLocalObjSlotChkUndecl, ElementSlotI1, OpSideEffect) -MACRO_EXTEND_WMS( StParamObjSlotChkUndecl, ElementSlotI1, OpSideEffect) +MACRO_EXTEND_WMS( StLocalObjSlotChkUndecl, ElementSlotI1, OpSideEffect|OpNonIntTransfer) +MACRO_EXTEND_WMS( StParamObjSlotChkUndecl, ElementSlotI1, OpSideEffect|OpNonIntTransfer) MACRO_EXTEND_WMS( StEnvObjSlot, ElementSlotI2, OpSideEffect) -MACRO_EXTEND_WMS( StObjSlotChkUndecl, ElementSlot, OpSideEffect) -MACRO_EXTEND_WMS( StInnerObjSlotChkUndecl, ElementSlotI2, OpSideEffect) -MACRO_EXTEND_WMS( StEnvObjSlotChkUndecl, ElementSlotI2, OpSideEffect) +MACRO_EXTEND_WMS( StObjSlotChkUndecl, ElementSlot, OpSideEffect|OpNonIntTransfer) +MACRO_EXTEND_WMS( StInnerObjSlotChkUndecl, ElementSlotI2, OpSideEffect|OpNonIntTransfer) +MACRO_EXTEND_WMS( StEnvObjSlotChkUndecl, ElementSlotI2, OpSideEffect|OpNonIntTransfer) MACRO_EXTEND_WMS( StModuleSlot, ElementSlotI2, OpSideEffect) MACRO_BACKEND_ONLY( LdAsmJsFunc, ElementSlot, OpTempNumberSources|OpCanCSE) MACRO_BACKEND_ONLY( LdWasmFunc, ElementSlot, OpSideEffect)
lib/Runtime/ByteCode/Scope.h+15 −5 modified@@ -110,19 +110,28 @@ class Scope } } + static bool HasSymbolName(Scope * scope, const JsUtil::CharacterBuffer<WCHAR>& name) + { + for (Symbol *sym = scope->m_symList; sym; sym = sym->GetNext()) + { + if (sym->GetName() == name) + { + return true; + } + } + return false; + } + void AddSymbol(Symbol *sym) { if (enclosingScope == nullptr) { sym->SetIsGlobal(true); } sym->SetScope(this); - for (Symbol *symInList = m_symList; symInList; symInList = symInList->GetNext()) + if (HasSymbolName(this, sym->GetName())) { - if (symInList->GetName() == sym->GetName()) - { - return; - } + return; } sym->SetNext(m_symList); m_symList = sym; @@ -136,6 +145,7 @@ class Scope sym->SetIsGlobal(true); } sym->SetScope(this); + Assert(!HasSymbolName(this, sym->GetName())); sym->SetNext(m_symList); m_symList = sym; m_count++;
lib/Runtime/Language/JavascriptOperators.cpp+118 −0 modified@@ -4644,6 +4644,120 @@ using namespace Js; return JavascriptOperators::SetProperty(receiver, object, propertyRecord->GetPropertyId(), value, scriptContext, flags); } + BOOL JavascriptOperators::OP_SetNativeIntElementI_NoConvert( + Var instance, + Var aElementIndex, + int32 iValue, + ScriptContext* scriptContext, + PropertyOperationFlags flags) + { + JIT_HELPER_REENTRANT_HEADER(Op_SetNativeIntElementI_NoConvert); + JIT_HELPER_SAME_ATTRIBUTES(Op_SetNativeIntElementI_NoConvert, Op_SetNativeIntElementI); + BOOL converted = OP_SetNativeIntElementI(instance, aElementIndex, iValue, scriptContext, flags); + if (converted) + { + AssertMsg(false, "Unexpected native array conversion"); + Js::Throw::FatalInternalError(); + } + return FALSE; + JIT_HELPER_END(Op_SetNativeIntElementI_NoConvert); + } + + BOOL JavascriptOperators::OP_SetNativeIntElementI_UInt32_NoConvert( + Var instance, + uint32 aElementIndex, + int32 iValue, + ScriptContext* scriptContext, + PropertyOperationFlags flags) + { + JIT_HELPER_REENTRANT_HEADER(Op_SetNativeIntElementI_UInt32_NoConvert); + JIT_HELPER_SAME_ATTRIBUTES(Op_SetNativeIntElementI_UInt32_NoConvert, Op_SetNativeIntElementI_UInt32); + BOOL converted = OP_SetNativeIntElementI_UInt32(instance, aElementIndex, iValue, scriptContext, flags); + if (converted) + { + AssertMsg(false, "Unexpected native array conversion"); + Js::Throw::FatalInternalError(); + } + return FALSE; + JIT_HELPER_END(Op_SetNativeIntElementI_UInt32_NoConvert); + } + + BOOL JavascriptOperators::OP_SetNativeIntElementI_Int32_NoConvert( + Var instance, + int32 aElementIndex, + int32 iValue, + ScriptContext* scriptContext, + PropertyOperationFlags flags) + { + JIT_HELPER_REENTRANT_HEADER(Op_SetNativeIntElementI_Int32_NoConvert); + JIT_HELPER_SAME_ATTRIBUTES(Op_SetNativeIntElementI_Int32_NoConvert, Op_SetNativeIntElementI_Int32); + BOOL converted = OP_SetNativeIntElementI_Int32(instance, aElementIndex, iValue, scriptContext, flags); + if (converted) + { + AssertMsg(false, "Unexpected native array conversion"); + Js::Throw::FatalInternalError(); + } + return FALSE; + JIT_HELPER_END(Op_SetNativeIntElementI_Int32_NoConvert); + } + + BOOL JavascriptOperators::OP_SetNativeFloatElementI_NoConvert( + Var instance, + Var aElementIndex, + ScriptContext* scriptContext, + PropertyOperationFlags flags, + double dValue) + { + JIT_HELPER_REENTRANT_HEADER(Op_SetNativeFloatElementI_NoConvert); + JIT_HELPER_SAME_ATTRIBUTES(Op_SetNativeFloatElementI_NoConvert, Op_SetNativeFloatElementI); + BOOL converted = OP_SetNativeFloatElementI(instance, aElementIndex, scriptContext, flags, dValue); + if (converted) + { + AssertMsg(false, "Unexpected native array conversion"); + Js::Throw::FatalInternalError(); + } + return FALSE; + JIT_HELPER_END(Op_SetNativeFloatElementI_NoConvert); + } + + BOOL JavascriptOperators::OP_SetNativeFloatElementI_UInt32_NoConvert( + Var instance, + uint32 aElementIndex, + ScriptContext* scriptContext, + PropertyOperationFlags flags, + double dValue) + { + JIT_HELPER_REENTRANT_HEADER(Op_SetNativeFloatElementI_UInt32_NoConvert); + JIT_HELPER_SAME_ATTRIBUTES(Op_SetNativeFloatElementI_NoConvert, Op_SetNativeFloatElementI_UInt32); + BOOL converted = OP_SetNativeFloatElementI_UInt32(instance, aElementIndex, scriptContext, flags, dValue); + if (converted) + { + AssertMsg(false, "Unexpected native array conversion"); + Js::Throw::FatalInternalError(); + } + return FALSE; + JIT_HELPER_END(Op_SetNativeFloatElementI_UInt32_NoConvert); + } + + BOOL JavascriptOperators::OP_SetNativeFloatElementI_Int32_NoConvert( + Var instance, + int32 aElementIndex, + ScriptContext* scriptContext, + PropertyOperationFlags flags, + double dValue) + { + JIT_HELPER_REENTRANT_HEADER(Op_SetNativeFloatElementI_Int32_NoConvert); + JIT_HELPER_SAME_ATTRIBUTES(Op_SetNativeFloatElementI_NoConvert, Op_SetNativeFloatElementI_Int32); + BOOL converted = OP_SetNativeFloatElementI_Int32(instance, aElementIndex, scriptContext, flags, dValue); + if (converted) + { + AssertMsg(false, "Unexpected native array conversion"); + Js::Throw::FatalInternalError(); + } + return FALSE; + JIT_HELPER_END(Op_SetNativeFloatElementI_Int32_NoConvert); + } + BOOL JavascriptOperators::OP_SetNativeIntElementI( Var instance, Var aElementIndex, @@ -9541,6 +9655,10 @@ using namespace Js; Var result = CALL_ENTRYPOINT(threadContext, marshalledFunction->GetEntryPoint(), function, CallInfo(flags, 1), thisVar); result = CrossSite::MarshalVar(requestContext, result); + // Set implicit call flags so we bail out if we're trying to propagate the value forward, e.g., from a compare. Subsequent calls + // to the getter may produce different results. + threadContext->AddImplicitCallFlags(ImplicitCall_Accessor); + return result; }); }
lib/Runtime/Language/JavascriptOperators.h+6 −0 modified@@ -392,6 +392,12 @@ namespace Js static BOOL OP_SetElementI_UInt32(Var instance, uint32 aElementIndex, Var aValue, ScriptContext* scriptContext, PropertyOperationFlags flags = PropertyOperation_None); static BOOL OP_SetElementI_Int32(Var instance, int32 aElementIndex, Var aValue, ScriptContext* scriptContext, PropertyOperationFlags flags = PropertyOperation_None); static BOOL SetElementIHelper(Var receiver, RecyclableObject* object, Var index, Var value, ScriptContext* scriptContext, PropertyOperationFlags flags); + static BOOL OP_SetNativeIntElementI_NoConvert(Var instance, Var aElementIndex, int32 aValue, ScriptContext* scriptContext, PropertyOperationFlags flags = PropertyOperation_None); + static BOOL OP_SetNativeIntElementI_UInt32_NoConvert(Var instance, uint32 aElementIndex, int32 aValue, ScriptContext* scriptContext, PropertyOperationFlags flags = PropertyOperation_None); + static BOOL OP_SetNativeIntElementI_Int32_NoConvert(Var instance, int aElementIndex, int32 aValue, ScriptContext* scriptContext, PropertyOperationFlags flags = PropertyOperation_None); + static BOOL OP_SetNativeFloatElementI_NoConvert(Var instance, Var aElementIndex, ScriptContext* scriptContext, PropertyOperationFlags flags, double value); + static BOOL OP_SetNativeFloatElementI_UInt32_NoConvert(Var instance, uint32 aElementIndex, ScriptContext* scriptContext, PropertyOperationFlags flags, double value); + static BOOL OP_SetNativeFloatElementI_Int32_NoConvert(Var instance, int aElementIndex, ScriptContext* scriptContext, PropertyOperationFlags flags, double value); static BOOL OP_SetNativeIntElementI(Var instance, Var aElementIndex, int32 aValue, ScriptContext* scriptContext, PropertyOperationFlags flags = PropertyOperation_None); static BOOL OP_SetNativeIntElementI_UInt32(Var instance, uint32 aElementIndex, int32 aValue, ScriptContext* scriptContext, PropertyOperationFlags flags = PropertyOperation_None); static BOOL OP_SetNativeIntElementI_Int32(Var instance, int aElementIndex, int32 aValue, ScriptContext* scriptContext, PropertyOperationFlags flags = PropertyOperation_None);
lib/Runtime/Library/JavascriptProxy.cpp+5 −0 modified@@ -392,6 +392,8 @@ namespace Js } propertyDescriptor->SetValue(getGetResult); + threadContext->AddImplicitCallFlags(Js::ImplicitCall_External); + return TRUE; } @@ -1907,6 +1909,9 @@ namespace Js } } } + + threadContext->AddImplicitCallFlags(Js::ImplicitCall_External); + return TRUE; }
2 files changed · +29 −0
lib/Backend/GlobOpt.cpp+25 −0 modified@@ -12827,6 +12827,26 @@ GlobOpt::ProcessValueKills(IR::Instr *const instr) it.RemoveCurrent(); } } + else if(kills.KillsObjectArraysWithNoMissingValues()) + { + // Some operations may kill objects with arrays-with-no-missing-values in unlikely circumstances. Convert their value types to likely + // versions so that the checks have to be redone. + for(auto it = valuesToKillOnCalls->GetIteratorWithRemovalSupport(); it.IsValid(); it.MoveNext()) + { + Value *const value = it.CurrentValue(); + ValueInfo *const valueInfo = value->GetValueInfo(); + Assert( + valueInfo->IsArrayOrObjectWithArray() || + valueInfo->IsOptimizedVirtualTypedArray() || + valueInfo->IsOptimizedTypedArray() && valueInfo->AsArrayValueInfo()->HeadSegmentLengthSym()); + if(!valueInfo->IsArrayOrObjectWithArray() || valueInfo->IsArray() || !valueInfo->HasNoMissingValues()) + { + continue; + } + ChangeValueType(nullptr, value, valueInfo->Type().ToLikely(), false); + it.RemoveCurrent(); + } + } if(kills.KillsNativeArrays()) { @@ -13358,6 +13378,11 @@ GlobOpt::CheckJsArrayKills(IR::Instr *const instr) { kills.SetKillsArrayLengths(); } + + if(doArrayMissingValueCheckHoist && !(useValueTypes && arrayValueType.IsArray())) + { + kills.SetKillsObjectArraysWithNoMissingValues(); + } break; }
lib/Backend/GlobOpt.h+4 −0 modified@@ -317,6 +317,7 @@ class JsArrayKills { bool killsAllArrays : 1; bool killsArraysWithNoMissingValues : 1; + bool killsObjectArraysWithNoMissingValues : 1; bool killsNativeArrays : 1; bool killsArrayHeadSegments : 1; bool killsArrayHeadSegmentLengths : 1; @@ -342,6 +343,9 @@ class JsArrayKills bool KillsArraysWithNoMissingValues() const { return killsArraysWithNoMissingValues; } void SetKillsArraysWithNoMissingValues() { killsArraysWithNoMissingValues = true; } + bool KillsObjectArraysWithNoMissingValues() const { return killsObjectArraysWithNoMissingValues; } + void SetKillsObjectArraysWithNoMissingValues() { killsObjectArraysWithNoMissingValues = true; } + bool KillsNativeArrays() const { return killsNativeArrays; } void SetKillsNativeArrays() { killsNativeArrays = true; }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-6973-94v8-5mgwghsaADVISORY
- msrc.microsoft.com/update-guide/vulnerability/CVE-2019-0991ghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2019-0991ghsaADVISORY
- github.com/chakra-core/ChakraCore/commit/1caa4118796d33513bc40ce894c053a92de98abbghsaWEB
- github.com/chakra-core/ChakraCore/commit/3d6226cc2d1077537220361c82e34a362c6c76eeghsaWEB
- portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0991ghsaWEB
News mentions
0No linked articles in our index yet.