Skip to content

Commit 04636be

Browse files
committed
[MS][clang] Add support for vector deleting destructors
Whereas it is UB in terms of the standard to delete an array of objects via pointer whose static type doesn't match its dynamic type, MSVC supports an extension allowing to do it. Aside from array deletion not working correctly in the mentioned case, currently not having this extension implemented causes clang to generate code that is not compatible with the code generated by MSVC, because clang always puts scalar deleting destructor to the vftable. This PR aims to resolve these problems. Fixes #19772
1 parent 83ba374 commit 04636be

33 files changed

+528
-157
lines changed

clang/include/clang/AST/VTableBuilder.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class VTableComponent {
150150

151151
bool isRTTIKind() const { return isRTTIKind(getKind()); }
152152

153-
GlobalDecl getGlobalDecl() const {
153+
GlobalDecl getGlobalDecl(bool HasVectorDeletingDtors) const {
154154
assert(isUsedFunctionPointerKind() &&
155155
"GlobalDecl can be created only from virtual function");
156156

@@ -161,7 +161,9 @@ class VTableComponent {
161161
case CK_CompleteDtorPointer:
162162
return GlobalDecl(DtorDecl, CXXDtorType::Dtor_Complete);
163163
case CK_DeletingDtorPointer:
164-
return GlobalDecl(DtorDecl, CXXDtorType::Dtor_Deleting);
164+
return GlobalDecl(DtorDecl, (HasVectorDeletingDtors)
165+
? CXXDtorType::Dtor_VectorDeleting
166+
: CXXDtorType::Dtor_Deleting);
165167
case CK_VCallOffset:
166168
case CK_VBaseOffset:
167169
case CK_OffsetToTop:

clang/include/clang/Basic/ABI.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ enum CXXCtorType {
3131

3232
/// C++ destructor types.
3333
enum CXXDtorType {
34-
Dtor_Deleting, ///< Deleting dtor
35-
Dtor_Complete, ///< Complete object dtor
36-
Dtor_Base, ///< Base object dtor
37-
Dtor_Comdat ///< The COMDAT used for dtors
34+
Dtor_Deleting, ///< Deleting dtor
35+
Dtor_Complete, ///< Complete object dtor
36+
Dtor_Base, ///< Base object dtor
37+
Dtor_Comdat, ///< The COMDAT used for dtors
38+
Dtor_VectorDeleting ///< Vector deleting dtor
3839
};
3940

4041
} // end namespace clang

clang/lib/AST/ItaniumMangle.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6001,6 +6001,8 @@ void CXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
60016001
case Dtor_Comdat:
60026002
Out << "D5";
60036003
break;
6004+
case Dtor_VectorDeleting:
6005+
llvm_unreachable("Itanium ABI does not use vector deleting dtors");
60046006
}
60056007
}
60066008

clang/lib/AST/MicrosoftMangle.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,8 +1484,7 @@ void MicrosoftCXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
14841484
// <operator-name> ::= ?_G # scalar deleting destructor
14851485
case Dtor_Deleting: Out << "?_G"; return;
14861486
// <operator-name> ::= ?_E # vector deleting destructor
1487-
// FIXME: Add a vector deleting dtor type. It goes in the vtable, so we need
1488-
// it.
1487+
case Dtor_VectorDeleting: Out << "?_E"; return;
14891488
case Dtor_Comdat:
14901489
llvm_unreachable("not expecting a COMDAT");
14911490
}
@@ -2886,9 +2885,12 @@ void MicrosoftCXXNameMangler::mangleFunctionType(const FunctionType *T,
28862885
// ::= @ # structors (they have no declared return type)
28872886
if (IsStructor) {
28882887
if (isa<CXXDestructorDecl>(D) && isStructorDecl(D)) {
2889-
// The scalar deleting destructor takes an extra int argument which is not
2888+
// The deleting destructors take an extra argument of type int that indicates
2889+
// whether the storage for the object should be deleted and whether a single
2890+
// object or an array of objects is being destroyed. This extra argument is not
28902891
// reflected in the AST.
2891-
if (StructorType == Dtor_Deleting) {
2892+
if (StructorType == Dtor_Deleting ||
2893+
StructorType == Dtor_VectorDeleting) {
28922894
Out << (PointersAre64Bit ? "PEAXI@Z" : "PAXI@Z");
28932895
return;
28942896
}
@@ -3861,10 +3863,10 @@ void MicrosoftMangleContextImpl::mangleCXXDtorThunk(const CXXDestructorDecl *DD,
38613863
const ThunkInfo &Thunk,
38623864
bool /*ElideOverrideInfo*/,
38633865
raw_ostream &Out) {
3864-
// FIXME: Actually, the dtor thunk should be emitted for vector deleting
3865-
// dtors rather than scalar deleting dtors. Just use the vector deleting dtor
3866-
// mangling manually until we support both deleting dtor types.
3867-
assert(Type == Dtor_Deleting);
3866+
// The dtor thunk should use vector deleting dtor mangling, however as an
3867+
// optimization we may end up emitting only scalar deleting dtor body, so just
3868+
// use the vector deleting dtor mangling manually.
3869+
assert(Type == Dtor_Deleting || Type == Dtor_VectorDeleting);
38683870
msvc_hashing_ostream MHO(Out);
38693871
MicrosoftCXXNameMangler Mangler(*this, MHO, DD, Type);
38703872
Mangler.getStream() << "??_E";

clang/lib/AST/VTableBuilder.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,8 +1733,8 @@ void ItaniumVTableBuilder::LayoutPrimaryAndSecondaryVTables(
17331733
const CXXMethodDecl *MD = I.first;
17341734
const MethodInfo &MI = I.second;
17351735
if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
1736-
MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)]
1737-
= MI.VTableIndex - AddressPoint;
1736+
MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)] =
1737+
MI.VTableIndex - AddressPoint;
17381738
MethodVTableIndices[GlobalDecl(DD, Dtor_Deleting)]
17391739
= MI.VTableIndex + 1 - AddressPoint;
17401740
} else {
@@ -2655,7 +2655,10 @@ class VFTableBuilder {
26552655
MethodVFTableLocation Loc(MI.VBTableIndex, WhichVFPtr.getVBaseWithVPtr(),
26562656
WhichVFPtr.NonVirtualOffset, MI.VFTableIndex);
26572657
if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
2658-
MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
2658+
if (!Context.getTargetInfo().getCXXABI().isMicrosoft())
2659+
MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
2660+
else
2661+
MethodVFTableLocations[GlobalDecl(DD, Dtor_VectorDeleting)] = Loc;
26592662
} else {
26602663
MethodVFTableLocations[MD] = Loc;
26612664
}
@@ -3285,7 +3288,10 @@ void VFTableBuilder::dumpLayout(raw_ostream &Out) {
32853288
const CXXDestructorDecl *DD = Component.getDestructorDecl();
32863289

32873290
DD->printQualifiedName(Out);
3288-
Out << "() [scalar deleting]";
3291+
if (Context.getTargetInfo().getCXXABI().isMicrosoft())
3292+
Out << "() [vector deleting]";
3293+
else
3294+
Out << "() [scalar deleting]";
32893295

32903296
if (DD->isPureVirtual())
32913297
Out << " [pure]";
@@ -3756,7 +3762,7 @@ void MicrosoftVTableContext::dumpMethodLocations(
37563762
PredefinedIdentKind::PrettyFunctionNoVirtual, MD);
37573763

37583764
if (isa<CXXDestructorDecl>(MD)) {
3759-
IndicesMap[I.second] = MethodName + " [scalar deleting]";
3765+
IndicesMap[I.second] = MethodName + " [vector deleting]";
37603766
} else {
37613767
IndicesMap[I.second] = MethodName;
37623768
}
@@ -3873,7 +3879,7 @@ MicrosoftVTableContext::getMethodVFTableLocation(GlobalDecl GD) {
38733879
assert(hasVtableSlot(cast<CXXMethodDecl>(GD.getDecl())) &&
38743880
"Only use this method for virtual methods or dtors");
38753881
if (isa<CXXDestructorDecl>(GD.getDecl()))
3876-
assert(GD.getDtorType() == Dtor_Deleting);
3882+
assert(GD.getDtorType() == Dtor_VectorDeleting);
38773883

38783884
GD = GD.getCanonicalDecl();
38793885

clang/lib/CodeGen/CGCXX.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,6 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
175175
// requires explicit comdat support in the IL.
176176
if (llvm::GlobalValue::isWeakForLinker(TargetLinkage))
177177
return true;
178-
179178
// Create the alias with no name.
180179
auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
181180
Aliasee, &getModule());
@@ -201,6 +200,42 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
201200
return false;
202201
}
203202

203+
/// Emit a definition as a global alias for another definition, unconditionally.
204+
void CodeGenModule::EmitDefinitionAsAlias(GlobalDecl AliasDecl,
205+
GlobalDecl TargetDecl) {
206+
207+
llvm::Type *AliasValueType = getTypes().GetFunctionType(AliasDecl);
208+
209+
StringRef MangledName = getMangledName(AliasDecl);
210+
llvm::GlobalValue *Entry = GetGlobalValue(MangledName);
211+
if (Entry && !Entry->isDeclaration())
212+
return;
213+
auto *Aliasee = cast<llvm::GlobalValue>(GetAddrOfGlobal(TargetDecl));
214+
215+
// Determine the linkage type for the alias.
216+
llvm::GlobalValue::LinkageTypes Linkage = getFunctionLinkage(AliasDecl);
217+
218+
// Create the alias with no name.
219+
auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
220+
Aliasee, &getModule());
221+
// Destructors are always unnamed_addr.
222+
Alias->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
223+
224+
if (Entry) {
225+
assert(Entry->getValueType() == AliasValueType &&
226+
Entry->getAddressSpace() == Alias->getAddressSpace() &&
227+
"declaration exists with different type");
228+
Alias->takeName(Entry);
229+
Entry->replaceAllUsesWith(Alias);
230+
Entry->eraseFromParent();
231+
} else {
232+
Alias->setName(MangledName);
233+
}
234+
235+
// Set any additional necessary attributes for the alias.
236+
SetCommonAttributes(AliasDecl, Alias);
237+
}
238+
204239
llvm::Function *CodeGenModule::codegenCXXStructor(GlobalDecl GD) {
205240
const CGFunctionInfo &FnInfo = getTypes().arrangeCXXStructorDeclaration(GD);
206241
auto *Fn = cast<llvm::Function>(

clang/lib/CodeGen/CGCXXABI.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,20 @@ void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
273273
numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
274274
}
275275

276+
void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
277+
QualType eltTy, llvm::Value *&numElements,
278+
llvm::Value *&allocPtr, CharUnits &cookieSize) {
279+
assert(eltTy.isDestructedType());
280+
281+
// Derive a char* in the same address space as the pointer.
282+
ptr = ptr.withElementType(CGF.Int8Ty);
283+
284+
cookieSize = getArrayCookieSizeImpl(eltTy);
285+
Address allocAddr = CGF.Builder.CreateConstInBoundsByteGEP(ptr, -cookieSize);
286+
allocPtr = allocAddr.emitRawPointer(CGF);
287+
numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
288+
}
289+
276290
llvm::Value *CGCXXABI::readArrayCookieImpl(CodeGenFunction &CGF,
277291
Address ptr,
278292
CharUnits cookieSize) {

clang/lib/CodeGen/CGCXXABI.h

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,10 @@ class CGCXXABI {
251251

252252
public:
253253
virtual void emitVirtualObjectDelete(CodeGenFunction &CGF,
254-
const CXXDeleteExpr *DE,
255-
Address Ptr, QualType ElementType,
256-
const CXXDestructorDecl *Dtor) = 0;
254+
const CXXDeleteExpr *DE, Address Ptr,
255+
QualType ElementType,
256+
const CXXDestructorDecl *Dtor,
257+
bool ArrayDeletion) = 0;
257258
virtual void emitRethrow(CodeGenFunction &CGF, bool isNoReturn) = 0;
258259
virtual void emitThrow(CodeGenFunction &CGF, const CXXThrowExpr *E) = 0;
259260
virtual llvm::GlobalVariable *getThrowInfo(QualType T) { return nullptr; }
@@ -275,6 +276,7 @@ class CGCXXABI {
275276
virtual CatchTypeInfo getCatchAllTypeInfo();
276277

277278
virtual bool shouldTypeidBeNullChecked(QualType SrcRecordTy) = 0;
279+
virtual bool hasVectorDeletingDtors() = 0;
278280
virtual void EmitBadTypeidCall(CodeGenFunction &CGF) = 0;
279281
virtual llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
280282
Address ThisPtr,
@@ -485,11 +487,10 @@ class CGCXXABI {
485487
llvm::PointerUnion<const CXXDeleteExpr *, const CXXMemberCallExpr *>;
486488

487489
/// Emit the ABI-specific virtual destructor call.
488-
virtual llvm::Value *
489-
EmitVirtualDestructorCall(CodeGenFunction &CGF, const CXXDestructorDecl *Dtor,
490-
CXXDtorType DtorType, Address This,
491-
DeleteOrMemberCallExpr E,
492-
llvm::CallBase **CallOrInvoke) = 0;
490+
virtual llvm::Value *EmitVirtualDestructorCall(
491+
CodeGenFunction &CGF, const CXXDestructorDecl *Dtor, CXXDtorType DtorType,
492+
Address This, DeleteOrMemberCallExpr E, llvm::CallBase **CallOrInvoke,
493+
bool ArrayDeletion = false) = 0;
493494

494495
virtual void adjustCallArgsForDestructorThunk(CodeGenFunction &CGF,
495496
GlobalDecl GD,
@@ -575,6 +576,12 @@ class CGCXXABI {
575576
QualType ElementType, llvm::Value *&NumElements,
576577
llvm::Value *&AllocPtr, CharUnits &CookieSize);
577578

579+
/// Reads the array cookie associated with the given pointer,
580+
/// that should have one.
581+
virtual void ReadArrayCookie(CodeGenFunction &CGF, Address Ptr,
582+
QualType ElementType, llvm::Value *&NumElements,
583+
llvm::Value *&AllocPtr, CharUnits &CookieSize);
584+
578585
/// Return whether the given global decl needs a VTT parameter.
579586
virtual bool NeedsVTTParameter(GlobalDecl GD);
580587

clang/lib/CodeGen/CGClass.cpp

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,6 +1433,83 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF,
14331433
return true;
14341434
}
14351435

1436+
namespace {
1437+
llvm::Value *LoadThisForDtorDelete(CodeGenFunction &CGF,
1438+
const CXXDestructorDecl *DD) {
1439+
if (Expr *ThisArg = DD->getOperatorDeleteThisArg())
1440+
return CGF.EmitScalarExpr(ThisArg);
1441+
return CGF.LoadCXXThis();
1442+
}
1443+
}
1444+
1445+
void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD,
1446+
CodeGenFunction &CGF,
1447+
llvm::Value *ShouldDeleteCondition) {
1448+
Address ThisPtr = CGF.LoadCXXThisAddress();
1449+
llvm::BasicBlock *ScalarBB = CGF.createBasicBlock("dtor.scalar");
1450+
llvm::BasicBlock *callDeleteBB =
1451+
CGF.createBasicBlock("dtor.call_delete_after_array_destroy");
1452+
llvm::BasicBlock *VectorBB = CGF.createBasicBlock("dtor.vector");
1453+
auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
1454+
llvm::Value *CheckTheBitForArrayDestroy = CGF.Builder.CreateAnd(
1455+
ShouldDeleteCondition,
1456+
llvm::Constant::getIntegerValue(CondTy, llvm::APInt(CondTy->getBitWidth(),
1457+
/*val=*/2)));
1458+
llvm::Value *ShouldDestroyArray =
1459+
CGF.Builder.CreateIsNull(CheckTheBitForArrayDestroy);
1460+
CGF.Builder.CreateCondBr(ShouldDestroyArray, ScalarBB, VectorBB);
1461+
1462+
CGF.EmitBlock(VectorBB);
1463+
1464+
llvm::Value *numElements = nullptr;
1465+
llvm::Value *allocatedPtr = nullptr;
1466+
CharUnits cookieSize;
1467+
QualType EltTy = DD->getThisType()->getPointeeType();
1468+
CGF.CGM.getCXXABI().ReadArrayCookie(CGF, ThisPtr, EltTy, numElements,
1469+
allocatedPtr, cookieSize);
1470+
1471+
// Destroy the elements.
1472+
QualType::DestructionKind dtorKind = EltTy.isDestructedType();
1473+
1474+
assert(dtorKind);
1475+
assert(numElements && "no element count for a type with a destructor!");
1476+
1477+
CharUnits elementSize = CGF.getContext().getTypeSizeInChars(EltTy);
1478+
CharUnits elementAlign =
1479+
ThisPtr.getAlignment().alignmentOfArrayElement(elementSize);
1480+
1481+
llvm::Value *arrayBegin = ThisPtr.emitRawPointer(CGF);
1482+
llvm::Value *arrayEnd = CGF.Builder.CreateInBoundsGEP(
1483+
ThisPtr.getElementType(), arrayBegin, numElements, "delete.end");
1484+
1485+
// We already checked that the array is not 0-length before entering vector
1486+
// deleting dtor.
1487+
CGF.emitArrayDestroy(arrayBegin, arrayEnd, EltTy, elementAlign,
1488+
CGF.getDestroyer(dtorKind),
1489+
/*checkZeroLength*/ false, CGF.needsEHCleanup(dtorKind));
1490+
1491+
llvm::BasicBlock *VectorBBCont = CGF.createBasicBlock("dtor.vector.cont");
1492+
CGF.EmitBlock(VectorBBCont);
1493+
1494+
llvm::Value *CheckTheBitForDeleteCall = CGF.Builder.CreateAnd(
1495+
ShouldDeleteCondition,
1496+
llvm::Constant::getIntegerValue(CondTy, llvm::APInt(CondTy->getBitWidth(),
1497+
/*val=*/1)));
1498+
1499+
llvm::Value *ShouldCallDelete =
1500+
CGF.Builder.CreateIsNull(CheckTheBitForDeleteCall);
1501+
CGF.Builder.CreateCondBr(ShouldCallDelete, CGF.ReturnBlock.getBlock(),
1502+
callDeleteBB);
1503+
CGF.EmitBlock(callDeleteBB);
1504+
const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl);
1505+
const CXXRecordDecl *ClassDecl = Dtor->getParent();
1506+
CGF.EmitDeleteCall(Dtor->getOperatorDelete(), allocatedPtr,
1507+
CGF.getContext().getTagDeclType(ClassDecl));
1508+
1509+
CGF.EmitBranchThroughCleanup(CGF.ReturnBlock);
1510+
CGF.EmitBlock(ScalarBB);
1511+
}
1512+
14361513
/// EmitDestructorBody - Emits the body of the current destructor.
14371514
void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
14381515
const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CurGD.getDecl());
@@ -1462,7 +1539,9 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
14621539
// outside of the function-try-block, which means it's always
14631540
// possible to delegate the destructor body to the complete
14641541
// destructor. Do so.
1465-
if (DtorType == Dtor_Deleting) {
1542+
if (DtorType == Dtor_Deleting || DtorType == Dtor_VectorDeleting) {
1543+
if (CXXStructorImplicitParamValue && DtorType == Dtor_VectorDeleting)
1544+
EmitConditionalArrayDtorCall(Dtor, *this, CXXStructorImplicitParamValue);
14661545
RunCleanupsScope DtorEpilogue(*this);
14671546
EnterDtorCleanups(Dtor, Dtor_Deleting);
14681547
if (HaveInsertPoint()) {
@@ -1491,6 +1570,7 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
14911570
switch (DtorType) {
14921571
case Dtor_Comdat: llvm_unreachable("not expecting a COMDAT");
14931572
case Dtor_Deleting: llvm_unreachable("already handled deleting case");
1573+
case Dtor_VectorDeleting: llvm_unreachable("already handled vector deleting case");
14941574

14951575
case Dtor_Complete:
14961576
assert((Body || getTarget().getCXXABI().isMicrosoft()) &&
@@ -1567,13 +1647,6 @@ void CodeGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &Args)
15671647
}
15681648

15691649
namespace {
1570-
llvm::Value *LoadThisForDtorDelete(CodeGenFunction &CGF,
1571-
const CXXDestructorDecl *DD) {
1572-
if (Expr *ThisArg = DD->getOperatorDeleteThisArg())
1573-
return CGF.EmitScalarExpr(ThisArg);
1574-
return CGF.LoadCXXThis();
1575-
}
1576-
15771650
/// Call the operator delete associated with the current destructor.
15781651
struct CallDtorDelete final : EHScopeStack::Cleanup {
15791652
CallDtorDelete() {}
@@ -1592,8 +1665,12 @@ namespace {
15921665
bool ReturnAfterDelete) {
15931666
llvm::BasicBlock *callDeleteBB = CGF.createBasicBlock("dtor.call_delete");
15941667
llvm::BasicBlock *continueBB = CGF.createBasicBlock("dtor.continue");
1595-
llvm::Value *ShouldCallDelete
1596-
= CGF.Builder.CreateIsNull(ShouldDeleteCondition);
1668+
auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
1669+
llvm::Value *CheckTheBit = CGF.Builder.CreateAnd(
1670+
ShouldDeleteCondition, llvm::Constant::getIntegerValue(
1671+
CondTy, llvm::APInt(CondTy->getBitWidth(),
1672+
/*val=*/1)));
1673+
llvm::Value *ShouldCallDelete = CGF.Builder.CreateIsNull(CheckTheBit);
15971674
CGF.Builder.CreateCondBr(ShouldCallDelete, continueBB, callDeleteBB);
15981675

15991676
CGF.EmitBlock(callDeleteBB);

0 commit comments

Comments
 (0)