Browse Source

Fix incorrect dtor call for non-owned instances (#1615)

* Introduce failing test for disposing bug

* Dispose(bool, bool) really shouldn't be public. Clients should call Dipose() instead.
Derived classes may need to call Dispose(bool, bool) so lets make it internal protected.
pull/1621/head
Albert Szilvasy 5 years ago committed by GitHub
parent
commit
24b02e4d86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 52
      src/Generator/Generators/CSharp/CSharpSources.cs
  2. 2
      src/Generator/Types/Std/Stdlib.CSharp.cs
  3. 10
      tests/CSharp/CSharp.Tests.cs
  4. 6
      tests/CSharp/CSharp.cpp
  5. 1
      tests/CSharp/CSharp.h

52
src/Generator/Generators/CSharp/CSharpSources.cs

@ -1879,7 +1879,7 @@ namespace CppSharp.Generators.CSharp
{ {
if (method.IsDestructor) if (method.IsDestructor)
{ {
WriteLine("{0}.Dispose(true);", Helpers.TargetIdentifier); WriteLine("{0}.Dispose(disposing: true, callNativeDtor: true);", Helpers.TargetIdentifier);
return; return;
} }
@ -2200,7 +2200,7 @@ namespace CppSharp.Generators.CSharp
WriteLine("~{0}()", @class.Name); WriteLine("~{0}()", @class.Name);
WriteOpenBraceAndIndent(); WriteOpenBraceAndIndent();
WriteLine("Dispose(false);"); WriteLine($"Dispose(false, callNativeDtor : { Helpers.OwnsNativeInstanceIdentifier} );");
UnindentAndWriteCloseBrace(); UnindentAndWriteCloseBrace();
PopBlock(NewLineKind.BeforeNextBlock); PopBlock(NewLineKind.BeforeNextBlock);
@ -2217,7 +2217,7 @@ namespace CppSharp.Generators.CSharp
WriteLine("public void Dispose()"); WriteLine("public void Dispose()");
WriteOpenBraceAndIndent(); WriteOpenBraceAndIndent();
WriteLine("Dispose(disposing: true);"); WriteLine($"Dispose(disposing: true, callNativeDtor : { Helpers.OwnsNativeInstanceIdentifier} );");
if (Options.GenerateFinalizers) if (Options.GenerateFinalizers)
WriteLine("GC.SuppressFinalize(this);"); WriteLine("GC.SuppressFinalize(this);");
@ -2225,13 +2225,19 @@ namespace CppSharp.Generators.CSharp
PopBlock(NewLineKind.BeforeNextBlock); PopBlock(NewLineKind.BeforeNextBlock);
} }
// Generate Dispose(bool) method // Declare partial method that the partial class can implement to participate
// in dispose.
PushBlock(BlockKind.Method); PushBlock(BlockKind.Method);
Write("public "); WriteLine("partial void DisposePartial(bool disposing);");
PopBlock(NewLineKind.BeforeNextBlock);
// Generate Dispose(bool, bool) method
PushBlock(BlockKind.Method);
Write("internal protected ");
if (!@class.IsValueType) if (!@class.IsValueType)
Write(hasBaseClass ? "override " : "virtual "); Write(hasBaseClass ? "override " : "virtual ");
WriteLine("void Dispose(bool disposing)"); WriteLine("void Dispose(bool disposing, bool callNativeDtor )");
WriteOpenBraceAndIndent(); WriteOpenBraceAndIndent();
if (@class.IsRefType) if (@class.IsRefType)
@ -2251,6 +2257,13 @@ namespace CppSharp.Generators.CSharp
} }
} }
// TODO: if disposing == true we should delegate to the dispose methods of references
// we hold to other generated type instances since those instances could also hold
// references to unmanaged memory.
//
// Delegate to partial method if implemented
WriteLine("DisposePartial(disposing);");
var dtor = @class.Destructors.FirstOrDefault(); var dtor = @class.Destructors.FirstOrDefault();
if (dtor != null && dtor.Access != AccessSpecifier.Private && if (dtor != null && dtor.Access != AccessSpecifier.Private &&
@class.HasNonTrivialDestructor && !@class.IsAbstract) @class.HasNonTrivialDestructor && !@class.IsAbstract)
@ -2259,7 +2272,29 @@ namespace CppSharp.Generators.CSharp
if (!Options.CheckSymbols || if (!Options.CheckSymbols ||
Context.Symbols.FindLibraryBySymbol(dtor.Mangled, out library)) Context.Symbols.FindLibraryBySymbol(dtor.Mangled, out library))
{ {
WriteLine("if (disposing)"); // Normally, calling the native dtor should be controlled by whether or not we
// we own the underlying instance. (i.e. Helpers.OwnsNativeInstanceIdentifier).
// However, there are 2 situations when the caller needs to have direct control
//
// 1. When we have a virtual dtor on the native side we detour the vtable entry
// even when we don't own the underlying native instance. I think we do this
// so that the managed side can null out the __Instance pointer and remove the
// instance from the NativeToManagedMap. Of course, this is somewhat half-hearted
// since we can't/don't do this when there's no virtual dtor available to detour.
// Anyway, we must be able to call the native dtor in this case even if we don't
/// own the underlying native instance.
//
// 2. When we we pass a disposable object to a function "by value" then the callee
// calls the dtor on the argument so our marshalling code must have a way from preventing
// a duplicate call. Here's a native function that exhibits this behavior:
// void f(std::string f)
// {
// ....
// compiler generates call to f.dtor() at the end of function
// }
//
// IDisposable.Dispose() and Object.Finalize() set callNativeDtor = Helpers.OwnsNativeInstanceIdentifier
WriteLine($"if (callNativeDtor)");
if (@class.IsDependent || dtor.IsVirtual) if (@class.IsDependent || dtor.IsVirtual)
WriteOpenBraceAndIndent(); WriteOpenBraceAndIndent();
else else
@ -2282,9 +2317,6 @@ namespace CppSharp.Generators.CSharp
// unmanaged memory isn't always initialized and/or a reference may be owned by the // unmanaged memory isn't always initialized and/or a reference may be owned by the
// native side. // native side.
// //
// TODO: We should delegate to the dispose methods of references we hold to other
// generated type instances since those instances could also hold references to
// unmanaged memory.
foreach (var field in @class.Layout.Fields.Where(f => f.QualifiedType.Type.IsConstCharString())) foreach (var field in @class.Layout.Fields.Where(f => f.QualifiedType.Type.IsConstCharString()))
{ {
var ptr = $"(({Helpers.InternalStruct}*){Helpers.InstanceIdentifier})->{field.Name}"; var ptr = $"(({Helpers.InternalStruct}*){Helpers.InstanceIdentifier})->{field.Name}";

2
src/Generator/Types/Std/Stdlib.CSharp.cs

@ -343,7 +343,7 @@ namespace CppSharp.Types.Std
assign.Name}({varBasicString}, {ctx.Parameter.Name});"); assign.Name}({varBasicString}, {ctx.Parameter.Name});");
ctx.Return.Write($"{varBasicString}.{Helpers.InstanceIdentifier}"); ctx.Return.Write($"{varBasicString}.{Helpers.InstanceIdentifier}");
ctx.Cleanup.WriteLine($@"{varBasicString}.Dispose({ ctx.Cleanup.WriteLine($@"{varBasicString}.Dispose({
(!Type.IsAddress() || ctx.Parameter?.IsIndirect == true ? "false" : string.Empty)});"); (!Type.IsAddress() || ctx.Parameter?.IsIndirect == true ? "disposing: true, callNativeDtor:false" : string.Empty)});");
} }
} }

10
tests/CSharp/CSharp.Tests.cs

@ -497,6 +497,7 @@ public unsafe class CSharpTests
[Test] [Test]
public void TestCallingVirtualDtor() public void TestCallingVirtualDtor()
{ {
CallDtorVirtually.Destroyed = false;
using (var callDtorVirtually = new CallDtorVirtually()) using (var callDtorVirtually = new CallDtorVirtually())
{ {
var hasVirtualDtor1 = CallDtorVirtually.GetHasVirtualDtor1(callDtorVirtually); var hasVirtualDtor1 = CallDtorVirtually.GetHasVirtualDtor1(callDtorVirtually);
@ -505,6 +506,15 @@ public unsafe class CSharpTests
Assert.That(CallDtorVirtually.Destroyed, Is.True); Assert.That(CallDtorVirtually.Destroyed, Is.True);
} }
[Test]
public void TestNonOwning()
{
CallDtorVirtually.Destroyed = false;
var nonOwned = CallDtorVirtually.NonOwnedInstance;
nonOwned.Dispose();
Assert.That(CallDtorVirtually.Destroyed, Is.False);
}
[Test] [Test]
public void TestParamTypeToInterfacePass() public void TestParamTypeToInterfacePass()
{ {

6
tests/CSharp/CSharp.cpp

@ -1019,6 +1019,12 @@ HasVirtualDtor1* CallDtorVirtually::getHasVirtualDtor1(HasVirtualDtor1* returned
return returned; return returned;
} }
CallDtorVirtually nonOwnedInstance;
CallDtorVirtually* CallDtorVirtually::getNonOwnedInstance()
{
return &nonOwnedInstance;
}
TestOverrideFromSecondaryBase::TestOverrideFromSecondaryBase() TestOverrideFromSecondaryBase::TestOverrideFromSecondaryBase()
{ {
} }

1
tests/CSharp/CSharp.h

@ -704,6 +704,7 @@ public:
~CallDtorVirtually(); ~CallDtorVirtually();
static bool Destroyed; static bool Destroyed;
static HasVirtualDtor1* getHasVirtualDtor1(HasVirtualDtor1* returned); static HasVirtualDtor1* getHasVirtualDtor1(HasVirtualDtor1* returned);
static CallDtorVirtually* getNonOwnedInstance();
}; };
class HasProtectedNestedAnonymousType class HasProtectedNestedAnonymousType

Loading…
Cancel
Save