From 77f2d835129b7d8850ae6a10e6f827608fd39f53 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 7 Jan 2026 17:11:16 -0800 Subject: [PATCH 1/2] [clr-interp] Fix EH logic edge case When a try block exactly encloses another try/finally or try/catch block and the handler ends at the same point the try body ends we had an off by one issue in our reprentation of the ilOffset on the leave chain islands. This fixes the off by one issue for that kind of basic block. The C# compiler seems to always inject a nop to avoid this issue, but I do not know why and I can find no justification for that rule. Fixes badcodeinsidefinally tests --- src/coreclr/interpreter/compiler.cpp | 46 +++++++++++++++++++++++++++- src/coreclr/interpreter/compiler.h | 4 +++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index da590da2f9856a..4cfeac982f5d2a 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -1099,11 +1099,23 @@ void InterpCompiler::EmitCode() { if (clause.HandlerOffset <= (uint32_t)bb->ilOffset && (clause.HandlerOffset + clause.HandlerLength) > (uint32_t)bb->ilOffset) { + if (bb->isLeaveChainIsland && (uint32_t)bb->ilOffset == clause.HandlerOffset) + { + // This basic block is a leave chain island, and its iloffset is JUST after the end of the handler, but its logically part of it. + continue; + } + INTERP_DUMP("BB %d with ilOffset %x overlaps EH clause %d (handler)\n", bb->index, bb->ilOffset, i); bb->overlappingEHClauseCount++; } if (clause.Flags == CORINFO_EH_CLAUSE_FILTER && clause.FilterOffset <= (uint32_t)bb->ilOffset && clause.HandlerOffset > (uint32_t)bb->ilOffset) { + if (bb->isLeaveChainIsland && (uint32_t)bb->ilOffset == clause.FilterOffset) + { + // This basic block is a leave chain island, and its iloffset is JUST after the end of the handler, but its logically part of it. + continue; + } + INTERP_DUMP("BB %d with ilOffset %x overlaps EH clause %d (filter)\n", bb->index, bb->ilOffset, i); bb->overlappingEHClauseCount++; } } @@ -1569,8 +1581,27 @@ void InterpCompiler::GetNativeRangeForClause(uint32_t startILOffset, uint32_t en assert(pStartBB != NULL); InterpBasicBlock* pEndBB = pStartBB; - for (InterpBasicBlock* pBB = pStartBB->pNextBB; (pBB != NULL) && ((uint32_t)pBB->ilOffset < endILOffset); pBB = pBB->pNextBB) + + // Iterate to the last BB whose ilOffset is less than endILOffset (the normal il basic blocks which are fully contained in the clause) + // BUT then continue on to any BB which are leave chain islands which start at the same IL offset as endILOffset, as long as they have + // the same overlappingEHClauseCount as pStartBB (these are also considered part of the clause) This allows the compiler to properly + // handle the case of an try block which contains another try block which has a handler that ends exactly at the end of the outer try block. + for (InterpBasicBlock* pBB = pStartBB->pNextBB;; pBB = pBB->pNextBB) { + if (pBB == NULL) + { + break; + } + + // Check to see if we've reached or passed the end IL offset + if (((uint32_t)pBB->ilOffset >= endILOffset)) + { + // We're probably done except for leave chain islands, which allow for a match to be allowed. + if (!pBB->isLeaveChainIsland || (uint32_t)pBB->ilOffset != endILOffset) + { + break; + } + } if (pBB->overlappingEHClauseCount == pStartBB->overlappingEHClauseCount) { pEndBB = pBB; @@ -1682,6 +1713,18 @@ void InterpCompiler::BuildEHInfo() m_compHnd->setEHcount(nativeEHCount); unsigned int nativeEHIndex = 0; + +#ifdef DEBUG + INTERP_DUMP(" BB overlapping EH clause counts:\n"); + if (t_interpDump) + { + for (InterpBasicBlock* pBB = GetBB(0); (pBB != NULL); pBB = pBB->pNextBB) + { + INTERP_DUMP("BB:%d has overlappingEHClauseCount=%d and ilOffset=%x\n", pBB->index, pBB->overlappingEHClauseCount, pBB->ilOffset); + } + } +#endif + for (unsigned int i = 0; i < getEHcount(m_methodInfo); i++) { CORINFO_EH_CLAUSE clause; @@ -2274,6 +2317,7 @@ void InterpCompiler::CreateLeaveChainIslandBasicBlocks(CORINFO_METHOD_INFO* meth { pLeaveChainIslandBB = AllocBB(clause.HandlerOffset + clause.HandlerLength); pLeaveChainIslandBB->pLeaveTargetBB = pLeaveTargetBB; + pLeaveChainIslandBB->isLeaveChainIsland = true; *ppLastBBNext = pLeaveChainIslandBB; } diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index af4f20e74bd075..6a380a103d5bc6 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -335,6 +335,9 @@ struct InterpBasicBlock // Valid only for BBs of call islands. It is set to true if it is a finally call island, false if is is a catch leave island. bool isFinallyCallIsland; + // Is a leave chain island basic block + bool isLeaveChainIsland; + // If this basic block is a catch or filter funclet entry, this is the index of the variable // that holds the exception object. int clauseVarIndex; @@ -366,6 +369,7 @@ struct InterpBasicBlock clauseType = BBClauseNone; isFilterOrCatchFuncletEntry = false; isFinallyCallIsland = false; + isLeaveChainIsland = false; clauseVarIndex = -1; overlappingEHClauseCount = 0; enclosingTryBlockCount = -1; From 544776d89849c2f5161463d66f7692b0226885fc Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 8 Jan 2026 09:47:18 -0800 Subject: [PATCH 2/2] Adjust the iloffset of the leave chain island to avoid needing to modify GetNativeRangeForClause --- src/coreclr/interpreter/compiler.cpp | 38 ++++++---------------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 4cfeac982f5d2a..cae549411e4540 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -1097,24 +1097,20 @@ void InterpCompiler::EmitCode() getEHinfo(m_methodInfo, i, &clause); for (InterpBasicBlock *bb = m_pEntryBB; bb != NULL; bb = bb->pNextBB) { + if (bb->isLeaveChainIsland && clause.HandlerOffset == (uint32_t)bb->ilOffset) + { + // Leave chain islands are not part of any EH clause, but their IL offset may be + continue; + } + if (clause.HandlerOffset <= (uint32_t)bb->ilOffset && (clause.HandlerOffset + clause.HandlerLength) > (uint32_t)bb->ilOffset) { - if (bb->isLeaveChainIsland && (uint32_t)bb->ilOffset == clause.HandlerOffset) - { - // This basic block is a leave chain island, and its iloffset is JUST after the end of the handler, but its logically part of it. - continue; - } INTERP_DUMP("BB %d with ilOffset %x overlaps EH clause %d (handler)\n", bb->index, bb->ilOffset, i); bb->overlappingEHClauseCount++; } if (clause.Flags == CORINFO_EH_CLAUSE_FILTER && clause.FilterOffset <= (uint32_t)bb->ilOffset && clause.HandlerOffset > (uint32_t)bb->ilOffset) { - if (bb->isLeaveChainIsland && (uint32_t)bb->ilOffset == clause.FilterOffset) - { - // This basic block is a leave chain island, and its iloffset is JUST after the end of the handler, but its logically part of it. - continue; - } INTERP_DUMP("BB %d with ilOffset %x overlaps EH clause %d (filter)\n", bb->index, bb->ilOffset, i); bb->overlappingEHClauseCount++; } @@ -1582,26 +1578,8 @@ void InterpCompiler::GetNativeRangeForClause(uint32_t startILOffset, uint32_t en InterpBasicBlock* pEndBB = pStartBB; - // Iterate to the last BB whose ilOffset is less than endILOffset (the normal il basic blocks which are fully contained in the clause) - // BUT then continue on to any BB which are leave chain islands which start at the same IL offset as endILOffset, as long as they have - // the same overlappingEHClauseCount as pStartBB (these are also considered part of the clause) This allows the compiler to properly - // handle the case of an try block which contains another try block which has a handler that ends exactly at the end of the outer try block. - for (InterpBasicBlock* pBB = pStartBB->pNextBB;; pBB = pBB->pNextBB) + for (InterpBasicBlock* pBB = pStartBB->pNextBB; (pBB != NULL) && ((uint32_t)pBB->ilOffset < endILOffset); pBB = pBB->pNextBB) { - if (pBB == NULL) - { - break; - } - - // Check to see if we've reached or passed the end IL offset - if (((uint32_t)pBB->ilOffset >= endILOffset)) - { - // We're probably done except for leave chain islands, which allow for a match to be allowed. - if (!pBB->isLeaveChainIsland || (uint32_t)pBB->ilOffset != endILOffset) - { - break; - } - } if (pBB->overlappingEHClauseCount == pStartBB->overlappingEHClauseCount) { pEndBB = pBB; @@ -2315,7 +2293,7 @@ void InterpCompiler::CreateLeaveChainIslandBasicBlocks(CORINFO_METHOD_INFO* meth if (pLeaveChainIslandBB == NULL) { - pLeaveChainIslandBB = AllocBB(clause.HandlerOffset + clause.HandlerLength); + pLeaveChainIslandBB = AllocBB(clause.HandlerOffset); pLeaveChainIslandBB->pLeaveTargetBB = pLeaveTargetBB; pLeaveChainIslandBB->isLeaveChainIsland = true; *ppLastBBNext = pLeaveChainIslandBB;