From 2daff0207728c0c9ec51238d5d2bfff155e2996a Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Thu, 24 Sep 2026 01:26:00 +0100 Subject: [PATCH] Nest a try's pads in the catch funclet around it (Windows) A `using` inside a catch clause did not work on Windows: try { throw 1; } catch (o) { try { using r = new Res(); ... } catch (e: number) { ... } } With no try around the catch, Win32ExceptionPass crashed the compiler (the JIT at every level). With one, ahead-of-time compilation failed LLVM's verifier ("Unwind edges out of a catch must have the same unwind dest as the parent catchswitch"). Win32ExceptionPass scans a function's instructions in order with one current region, and makes every pad `within none`. A try inside a catch got by because a throw in it ended the enclosing catch first, so the nested try ran outside the catch's funclet. A `using` defeats that: its first call is inside the catch, before any throw. The scan then took the using's cleanup pad for the end of the catch, left the catch with no end at all (the crash), and gave the cleanup pad an edge from inside the catch funclet and one from outside it (the verifier error). Pads now nest: - TryOpLowering knows which catch clause a try sits in. On Windows it tags each landing pad with its catch pad's id and the id of the catch it is nested in (the nearest try around it that holds it in its catch clause, through any try bodies between). LandingPadOpLowering emits the tag as a call to ts.internal.eh_pad right after the landingpad. - Win32ExceptionPass reads and removes the tag, and scans with a stack of open regions: a nested region's code sits in the middle of the enclosing catch's, which carries on when the nested one ends; an end marker right after a nested catch ends the enclosing one. Nested pads are created within the enclosing catchpad, and a catchswitch unwinds to the first invoke that leaves its region, never into its own descendants. - A throw inside a try nested in a catch no longer ends that catch on Windows: the nested try's pad takes it, inside the funclet. The Itanium path, which has no funclets, keeps ending it as before. 00try_mismatch_in_catch_clause_using.ts now runs on every platform (compile, jit, and the rc/none corpus), with the no-try-around case that crashed the compiler added. Co-Authored-By: Claude Opus 5.5 --- tslang/include/TypeScript/Defines.h | 4 + .../TypeScript/TypeScriptPassContext.h | 6 + tslang/lib/TypeScript/LowerToAffineLoops.cpp | 68 ++++++- tslang/lib/TypeScript/LowerToLLVM.cpp | 32 ++- .../Win32ExceptionPass.cpp | 187 ++++++++++++++++-- tslang/test/tester/CMakeLists.txt | 9 +- .../00try_mismatch_in_catch_clause_using.ts | 31 ++- 7 files changed, 304 insertions(+), 33 deletions(-) diff --git a/tslang/include/TypeScript/Defines.h b/tslang/include/TypeScript/Defines.h index baf19af8f..50c89b2c4 100644 --- a/tslang/include/TypeScript/Defines.h +++ b/tslang/include/TypeScript/Defines.h @@ -61,6 +61,10 @@ #define RETURN_VARIABLE_NAME ".return" #define CAPTURED_NAME ".captured" #define LABEL_ATTR_NAME "label" +// Windows: a landing pad's catch-pad id, and the id of the catch it is nested in - see tagPad in +// TryOpLowering and ts.internal.eh_pad in Win32ExceptionPass +#define EH_PAD_ID_ATTR_NAME "__eh_pad_id" +#define EH_PAD_PARENT_ATTR_NAME "__eh_pad_parent" #define UNDEFINED_NAME "undefined" #define INFINITY_NAME "Infinity" #define NAN_NAME "NaN" diff --git a/tslang/include/TypeScript/TypeScriptPassContext.h b/tslang/include/TypeScript/TypeScriptPassContext.h index e97d51f21..a728dd819 100644 --- a/tslang/include/TypeScript/TypeScriptPassContext.h +++ b/tslang/include/TypeScript/TypeScriptPassContext.h @@ -130,6 +130,7 @@ struct TSContext finallyBlockOf.beginRun(); landingBlockOf.beginRun(); leavesCatch.beginRun(); + catchPadIdOf.beginRun(); } // options @@ -156,6 +157,11 @@ struct TSContext // a throw cannot: `unwind` already means its invoke destination, which is a different // question with a different answer. OpSideTable leavesCatch{"leavesCatch"}; + // Windows: the id a try's catch landing pad goes by, so that the pads of a try inside that + // catch clause can name it as the funclet they are nested in - see tagPad in TryOpLowering + // and ts.internal.eh_pad in Win32ExceptionPass. Ids are unique within the module. + OpSideTable catchPadIdOf{"catchPadIdOf"}; + int nextCatchPadId = 0; mlir::Block *returnBlock; }; diff --git a/tslang/lib/TypeScript/LowerToAffineLoops.cpp b/tslang/lib/TypeScript/LowerToAffineLoops.cpp index 6430fe0e7..a2aa3b5e8 100644 --- a/tslang/lib/TypeScript/LowerToAffineLoops.cpp +++ b/tslang/lib/TypeScript/LowerToAffineLoops.cpp @@ -1329,6 +1329,48 @@ struct TryOpLowering : public TsPattern MLIRRTTIHelperVC rttih(rewriter, module, tsContext->compileOptions); auto i8PtrTy = mth.getOpaqueType(); + // Windows: which catch funclet this try's pads are nested in - the catch clause of the + // nearest try around it that holds it in its catch, through any try bodies in between. + // Win32ExceptionPass cannot tell that from the IR: a pad reached from inside a catch is + // either a try nested in it or where exceptions leave it, and the two look alike. A try + // in a finally is left at the top, as every pad was before. + auto funcletParentId = 0; + if (tsContext->compileOptions.isWindows) + { + for (Operation *child = tryOp.getOperation(), *parent = parentTryOp; parent; + child = parent, parent = tsContext->parentTryOp.lookup(parent)) + { + if (tsContext->inParentTryCatch.lookup(child)) + { + funcletParentId = tsContext->catchPadIdOf.lookup(parent); + break; + } + + if (!tsContext->inParentTryBody.lookup(child)) + { + break; + } + } + } + + auto catchPadId = 0; + auto tagPad = [&](mlir_ts::LandingPadOp landingPadOp, bool isCatchPad) { + if (!tsContext->compileOptions.isWindows) + { + return; + } + + if (isCatchPad && catchPadId) + { + landingPadOp->setAttr(EH_PAD_ID_ATTR_NAME, rewriter.getI32IntegerAttr(catchPadId)); + } + + if (funcletParentId) + { + landingPadOp->setAttr(EH_PAD_PARENT_ATTR_NAME, rewriter.getI32IntegerAttr(funcletParentId)); + } + }; + // find catch var // // This region walk must stop at a nested `try`: a `try/catch` written inside a catch @@ -1487,13 +1529,18 @@ struct TryOpLowering : public TsPattern // is what ends the catch - ending it here as well runs it twice and // breaks the unwind (51exceptions.ts is the case that proves it). // - // Except for a throw inside a try nested in this catch: that one unwinds to - // the nested try's own pad, not to the finally. Left inside this catch's - // funclet, the nested pad would be a catchswitch `within none` reached from - // inside a catchpad - invalid funclet nesting, and a crash at run time. So - // it ends the catch first, exactly as it does when there is no finally. + // A throw inside a try nested in this catch does not leave the catch: the + // nested try's pad takes it, and on Windows Win32ExceptionPass nests that + // pad in this catch's funclet. It used to end the catch first, when the pass + // could not nest pads - which, with a `using` in the nested try, ended the + // catch between the using's first call and its throw, both unwinding to the + // same cleanup pad: one edge inside the funclet and one outside it, which + // the verifier rejects. The Itanium path has no funclets and keeps ending + // the catch there, as it always did. auto inNestedTry = op->getParentOfType() != nullptr; - if (!finallyHasOps || inNestedTry) + auto leaves = tsContext->compileOptions.isWindows ? !finallyHasOps && !inNestedTry + : !finallyHasOps || inNestedTry; + if (leaves) { tsContext->leavesCatch[op] = true; } @@ -1740,6 +1787,7 @@ struct TryOpLowering : public TsPattern auto landingPadCleanupOp = rewriter.create( loc, rttih.getLandingPadType(), rewriter.getBoolAttr(true), ValueRange{undefArrayValue}); + tagPad(landingPadCleanupOp, false); auto beginCleanupCallInfo = rewriter.create(loc); rewriter.setInsertionPoint(cleanupBlockLast->getTerminator()); @@ -1767,6 +1815,12 @@ struct TryOpLowering : public TsPattern rewriter.replaceOpWithNewOp(resultOpCleanup, landingPadCleanupOp, unwindDests); } + if (catchHasOps && tsContext->compileOptions.isWindows) + { + catchPadId = ++tsContext->nextCatchPadId; + tsContext->catchPadIdOf[tryOp.getOperation()] = catchPadId; + } + mlir::Value cmpValue; SmallVector cmpWidenedValues; if (catchHasOps) @@ -1785,6 +1839,7 @@ struct TryOpLowering : public TsPattern auto landingPadOp = rewriter.create(loc, rttih.getLandingPadType(), rewriter.getBoolAttr(false), catchTypes); + tagPad(landingPadOp, true); // On Windows a typed catch that does not match is left by the runtime, through the // catchswitch's unwind edge - but Win32ExceptionPass can only point that edge at the @@ -1872,6 +1927,7 @@ struct TryOpLowering : public TsPattern auto landingPadCleanupOp = rewriter.create( loc, rttih.getLandingPadType(), rewriter.getBoolAttr(true), ValueRange{undefArrayValue}); + tagPad(landingPadCleanupOp, false); auto beginCleanupCallInfo = rewriter.create(loc); rewriter.setInsertionPoint(finallyBlockLast->getTerminator()); diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 6a84bfe16..3b8ed0767 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -4736,11 +4736,37 @@ struct LandingPadOpLowering : public TsLlvmPattern ConversionPatternRewriter &rewriter) const final { Location loc = landingPadOp.getLoc(); + + // the funclet nesting TryOpLowering worked out, for Win32ExceptionPass: a call right + // after the pad, ts.internal.eh_pad(this catch pad's id, id of the catch it is nested + // in), 0 for none. The pass reads it and removes it. + auto padId = landingPadOp->getAttrOfType(EH_PAD_ID_ATTR_NAME); + auto padParent = landingPadOp->getAttrOfType(EH_PAD_PARENT_ATTR_NAME); + auto tagPad = [&](LLVM::LandingpadOp newLandingPadOp) { + if (!padId && !padParent) + { + return; + } + + TypeHelper th(rewriter); + LLVMCodeHelper ch(landingPadOp, rewriter, getTypeConverter(), tsLlvmContext->compileOptions); + CodeLogicHelper clh(landingPadOp, rewriter); + + auto ehPadFunc = ch.getOrInsertFunction( + "ts.internal.eh_pad", th.getFunctionType(th.getVoidType(), {th.getI32Type(), th.getI32Type()})); + + mlir::OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPointAfter(newLandingPadOp); + rewriter.create(loc, ehPadFunc, + ValueRange{clh.createI32ConstantOf(padId ? padId.getInt() : 0), + clh.createI32ConstantOf(padParent ? padParent.getInt() : 0)}); + }; + if (!landingPadOp.getCleanup()) { auto catch1 = transformed.getCatches().front(); mlir::Type llvmLandingPadTy = getTypeConverter()->convertType(landingPadOp.getType()); - rewriter.replaceOpWithNewOp(landingPadOp, llvmLandingPadTy, false, ValueRange{catch1}); + tagPad(rewriter.replaceOpWithNewOp(landingPadOp, llvmLandingPadTy, false, ValueRange{catch1})); } else { @@ -4754,8 +4780,8 @@ struct LandingPadOpLowering : public TsLlvmPattern auto cleanupMarker = transformed.getCatches().front(); mlir::Type llvmLandingPadTy = getTypeConverter()->convertType(landingPadOp.getType()); - rewriter.replaceOpWithNewOp(landingPadOp, llvmLandingPadTy, true, - ValueRange{cleanupMarker}); + tagPad(rewriter.replaceOpWithNewOp(landingPadOp, llvmLandingPadTy, true, + ValueRange{cleanupMarker})); } return success(); diff --git a/tslang/lib/TypeScriptExceptionPass/Win32ExceptionPass.cpp b/tslang/lib/TypeScriptExceptionPass/Win32ExceptionPass.cpp index 533ade721..4bbc923ac 100644 --- a/tslang/lib/TypeScriptExceptionPass/Win32ExceptionPass.cpp +++ b/tslang/lib/TypeScriptExceptionPass/Win32ExceptionPass.cpp @@ -12,6 +12,9 @@ using namespace PatternMatch; #define DEBUG_TYPE "pass" +// see readPadTag +constexpr const char *EH_PAD_FUNC_NAME = "ts.internal.eh_pad"; + //#define SAVE_STACK true struct CatchRegion @@ -29,6 +32,15 @@ struct CatchRegion llvm::Instruction *saveCatch; llvm::Instruction *cxaEndCatch; llvm::Instruction *end; + // every invoke in the region, in order - unwindInfoOp is chosen from these once all the + // regions are known (see chooseUnwindInfoOps) + llvm::SmallVector invokes; + // index of the region this one is nested in, or -1; and the ids ts.internal.eh_pad gave + int parent = -1; + int padId = 0; + // the block the landing pad began, which is what invokes unwind to - making the catchpad + // splits the pad off into a block of its own + BasicBlock *padBlock = nullptr; bool isCatch() { @@ -106,14 +118,47 @@ struct Win32ExceptionPassCode auto endOfCatch = false; auto endOfCatchIfResume = false; llvm::SmallVector toRemoveWorkSet; + + // The regions still open, innermost last, as indexes into catchRegionsWorkSet (which + // reallocates). A pad can be nested in a catch - a try, or a `using`, inside a catch + // clause - and TryOpLowering says which catch in the tag after the pad (readPadTag). Its + // code sits in the middle of the enclosing catch's, whose end only comes after it: + // taking the new pad as the end of the old region, as this scan used to, left the catch + // with no end at all (the pass then crashed on it), and making every pad `within none` + // put an edge out of the catch funclet that the verifier rejects. + llvm::SmallVector openRegions; + auto closeRegion = [&]() { + if (!openRegions.empty()) + { + openRegions.pop_back(); + } + + catchRegion = openRegions.empty() ? nullptr : &catchRegionsWorkSet[openRegions.back()]; + }; + for (auto &I : instructions(F)) { if (auto *LPI = dyn_cast(&I)) { + auto [padId, parentPadId] = readPadTag(LPI, toRemoveWorkSet); + auto parent = findRegionByPadId(catchRegionsWorkSet, parentPadId); + catchRegionsWorkSet.push_back(CatchRegion()); catchRegion = &catchRegionsWorkSet.back(); catchRegion->landingPad = LPI; + catchRegion->parent = parent; + catchRegion->padId = padId; + catchRegion->padBlock = LPI->getParent(); + + // leave open what encloses the new pad, and nothing that was nested deeper; a pad + // not nested in an open region starts afresh, as every pad always did + while (!openRegions.empty() && openRegions.back() != parent) + { + openRegions.pop_back(); + } + + openRegions.push_back(catchRegionsWorkSet.size() - 1); endOfCatch = false; continue; @@ -158,23 +203,33 @@ struct Win32ExceptionPassCode { toRemoveWorkSet.push_back(&I); MadeChange = true; + + // Unless this region is nested in one still open: then the marker is the + // enclosing catch's own - a try inside a catch clause ends both, inner one + // first. The inner catch returns here, into the enclosing funclet, and the + // enclosing catch ends at what follows. + if (openRegions.size() > 1) + { + catchRegion->end = &I; + closeRegion(); + catchRegion->cxaEndCatch = &I; + } + continue; } // BR, or instraction without BR catchRegion->end = &I; endOfCatch = false; - catchRegion = nullptr; + + // the region it was nested in carries on + closeRegion(); continue; } - if (catchRegion->unwindInfoOp == nullptr) + if (auto *II = dyn_cast(&I)) { - if (auto *II = dyn_cast(&I)) - { - LLVM_DEBUG(llvm::dbgs() << "\n!! set (unwindInfoOp) : " << *II << "\n";); - catchRegion->unwindInfoOp = II; - } + catchRegion->invokes.push_back(II); } if (dyn_cast(&I)) @@ -200,6 +255,12 @@ struct Win32ExceptionPassCode continue; } + if (CI->getCalledFunction()->getName() == EH_PAD_FUNC_NAME) + { + // read at the pad by readPadTag, and removed there + continue; + } + if (CI->getCalledFunction()->getName() == "ts.internal.save_catch_var") { catchRegion->saveCatch = &I; @@ -292,10 +353,13 @@ struct Win32ExceptionPassCode } } - // create begin of catch block + chooseUnwindInfoOps(catchRegionsWorkSet); + + // create begin of catch block (a parent comes before the regions nested in it) for (auto &catchRegion : catchRegionsWorkSet) { auto *LPI = catchRegion.landingPad; + auto *parentPad = parentPadOf(catchRegionsWorkSet, catchRegion); LLVM_DEBUG(llvm::dbgs() << "\n!! Processing: " << *LPI << " isKnownSentinel: " << (LPI->isKnownSentinel() ? "true" : "false") << "\n";); @@ -313,7 +377,7 @@ struct Win32ExceptionPassCode CurrentBB->getTerminator()->eraseFromParent(); auto *II = catchRegion.unwindInfoOp; - auto *CSI = CatchSwitchInst::Create(ConstantTokenNone::get(Ctx), + auto *CSI = CatchSwitchInst::Create(parentPad, II ? II->getUnwindDest() : nullptr /*unwind to caller if null*/, 1, "catch.switch", CurrentBB); @@ -342,7 +406,7 @@ struct Win32ExceptionPassCode { assert(catchRegion.isCleanup()); - catchRegion.cleanupPad = CleanupPadInst::Create(ConstantTokenNone::get(Ctx), {}, "cleanuppad", LPI->getIterator()); + catchRegion.cleanupPad = CleanupPadInst::Create(parentPad, {}, "cleanuppad", LPI->getIterator()); } auto opBundle = getCallBundleFromCatchRegion(catchRegion); @@ -534,7 +598,8 @@ struct Win32ExceptionPassCode // add rethrow code auto *II = catchRegion.unwindInfoOp; - auto *CSI = CatchSwitchInst::Create(ConstantTokenNone::get(Ctx), + // beside the cleanup pad, in the same parent: cleanupret unwinds to it + auto *CSI = CatchSwitchInst::Create(parentPadOf(catchRegionsWorkSet, catchRegion), II ? II->getUnwindDest() : nullptr /*unwind to caller if null*/, 1, "catchswitch", CSIBlock); @@ -603,7 +668,9 @@ struct Win32ExceptionPassCode { if (auto *II = cast(callBase)) { - if (II->getUnwindDest() != CSIBlock) + // an invoke into a pad nested in this cleanup stays as it is + if (II->getUnwindDest() != CSIBlock && + !isDescendantPad(catchRegionsWorkSet, catchRegion, II->getUnwindDest())) { LLVM_DEBUG(llvm::dbgs() << "\n!! FIX INVOKE(cleanup): " << *callBase << "\n"); @@ -654,6 +721,102 @@ struct Win32ExceptionPassCode return MadeChange; } + // The tag TryOpLowering puts right after a pad on Windows (see LandingPadOpLowering): + // ts.internal.eh_pad(this catch pad's id, id of the catch pad it is nested in). The call is + // queued for removal. {0, 0} for an untagged pad - one at the top, as every pad used to be. + static std::pair readPadTag(LandingPadInst *LPI, llvm::SmallVector &toRemove) + { + auto *CI = dyn_cast_or_null(LPI->getNextNode()); + if (!CI || !CI->getCalledFunction() || CI->getCalledFunction()->getName() != EH_PAD_FUNC_NAME) + { + return {0, 0}; + } + + toRemove.push_back(CI); + auto id = cast(CI->getArgOperand(0))->getSExtValue(); + auto parent = cast(CI->getArgOperand(1))->getSExtValue(); + return {static_cast(id), static_cast(parent)}; + } + + static int findRegionByPadId(llvm::SmallVector ®ions, int padId) + { + if (padId == 0) + { + return -1; + } + + for (auto index = static_cast(regions.size()) - 1; index >= 0; index--) + { + if (regions[index].padId == padId) + { + return index; + } + } + + return -1; + } + + static bool isDescendantPad(llvm::SmallVector ®ions, CatchRegion ®ion, BasicBlock *block) + { + auto index = static_cast(®ion - regions.data()); + for (auto &other : regions) + { + if (other.padBlock != block) + { + continue; + } + + for (auto parent = other.parent; parent >= 0; parent = regions[parent].parent) + { + if (parent == index) + { + return true; + } + } + } + + return false; + } + + // A catchswitch unwinds where exceptions leaving its region go: the first invoke of the + // region that unwinds out of it. An invoke into a pad nested in the region is not one - + // a catchswitch cannot unwind into its own descendants. + static void chooseUnwindInfoOps(llvm::SmallVector ®ions) + { + for (auto ®ion : regions) + { + for (auto *II : region.invokes) + { + if (!isDescendantPad(regions, region, II->getUnwindDest())) + { + LLVM_DEBUG(llvm::dbgs() << "\n!! set (unwindInfoOp) : " << *II << "\n";); + region.unwindInfoOp = II; + break; + } + } + } + } + + // the pad a region's own pads are created within: its parent region's, or none + static Value *parentPadOf(llvm::SmallVector ®ions, CatchRegion ®ion) + { + if (region.parent >= 0) + { + auto &parent = regions[region.parent]; + if (parent.catchPad) + { + return parent.catchPad; + } + + if (parent.cleanupPad) + { + return parent.cleanupPad; + } + } + + return ConstantTokenNone::get(region.landingPad->getContext()); + } + llvm::SmallVector getCallBundleFromCatchRegion(CatchRegion &catchRegion) { llvm::SmallVector opBundle; diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 4f5367b8e..48f5d4d52 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -1455,8 +1455,10 @@ tslang_add_test(NAME test-jit-none-alloc-in-catch COMMAND test-runner -jit -mm=n # A try/catch inside a catch clause - crashed in every model at every optimisation level. tslang_add_test(NAME test-compile-00-nested-catch COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00nested_catch.ts") tslang_add_test(NAME test-compile-00-try-mismatch-in-catch-clause COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_mismatch_in_catch_clause.ts") +tslang_add_test(NAME test-compile-00-try-mismatch-in-catch-clause-using COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_mismatch_in_catch_clause_using.ts") tslang_add_test(NAME test-jit-00-nested-catch COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00nested_catch.ts") tslang_add_test(NAME test-jit-00-try-mismatch-in-catch-clause COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_mismatch_in_catch_clause.ts") +tslang_add_test(NAME test-jit-00-try-mismatch-in-catch-clause-using COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_mismatch_in_catch_clause_using.ts") tslang_add_test(NAME test-jit-rc-nested-catch COMMAND test-runner -jit -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/00nested_catch.ts") tslang_add_test(NAME test-jit-none-nested-catch COMMAND test-runner -jit -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/00nested_catch.ts") @@ -1660,6 +1662,7 @@ set(TSLANG_CORPUS 00names_conflict.ts 00nested_catch.ts 00try_mismatch_in_catch_clause.ts + 00try_mismatch_in_catch_clause_using.ts 00new_delete.ts 00ns_bug.ts 00ns.ts @@ -2467,12 +2470,6 @@ if (TSLANG_TEST_X86) "${x86_jit_skipped_count} -jit registrations without a twin") endif() -# A mismatched typed catch in a try with a `using`, inside another try's catch clause. Linux -# only: on Windows a `using` inside a catch clause is broken already (see the test's header). -if (NOT WIN32) - tslang_add_test(NAME test-compile-00-try-mismatch-in-catch-clause-using COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_mismatch_in_catch_clause_using.ts") - tslang_add_test(NAME test-jit-00-try-mismatch-in-catch-clause-using COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_mismatch_in_catch_clause_using.ts") -endif() # An untyped catch of a C++ exception tslang did not throw (Itanium only: the box thunk field of # a __pointer_type_info is a Linux mechanism). foreign_throw.cpp throws a `const char*`, a # pointer to a C++ struct and an int; the catch must box each without calling into the type_info. diff --git a/tslang/test/tester/tests/00try_mismatch_in_catch_clause_using.ts b/tslang/test/tester/tests/00try_mismatch_in_catch_clause_using.ts index a318cd5ef..7676346f8 100644 --- a/tslang/test/tester/tests/00try_mismatch_in_catch_clause_using.ts +++ b/tslang/test/tester/tests/00try_mismatch_in_catch_clause_using.ts @@ -1,9 +1,9 @@ -// 00try_mismatch_in_catch_clause.ts, with a `using` in the inner try. Linux only: on Windows a -// `using` inside a catch clause is broken on main already - its cleanup pad is not nested in the -// catch funclet, which fails LLVM's verifier ahead of time, and with a typed catch and no try -// around it the JIT crashes in Win32ExceptionPass (see 00try_using_catch.ts, section 9.11). -// On Linux the mismatch rethrow after the cleanup ran with nothing caught: "terminate called -// without an active exception". +// 00try_mismatch_in_catch_clause.ts, with a `using` in the inner try. On Linux the mismatch +// rethrow after the cleanup ran with nothing caught: "terminate called without an active +// exception". On Windows a `using` inside a catch clause did not work at all: its cleanup pad was +// not nested in the catch funclet - LLVM's verifier rejected the edge out of the catch ahead of +// time - and without a try around it Win32ExceptionPass crashed the compiler, having taken the +// cleanup pad for the end of the catch. let disposed = 0; let finals = 0; @@ -59,6 +59,21 @@ function withUsingAndFinally() { } } +// the shape that crashed the compiler on Windows: no try around the catch, and nothing thrown in +// the inner try, so its typed catch is not taken +function noTryAround() { + try { + throw 1; + } catch (o) { + try { + using r = new Res(); + finals++; + } catch (e: number) { + wrong++; + } + } +} + function main() { reset(); withUsing(); @@ -68,5 +83,9 @@ function main() { withUsingAndFinally(); assert(disposed == 1 && finals == 1 && wrong == 0 && outer == 1, "with a using and a finally"); + reset(); + noTryAround(); + assert(disposed == 1 && finals == 1 && wrong == 0, "no try around the catch"); + print("done."); }