Skip to content

Commit b14038a

Browse files
authored
Value types may generate Dispose (mono#1787)
1 parent 3b2a15d commit b14038a

File tree

2 files changed

+77
-36
lines changed

2 files changed

+77
-36
lines changed

src/Generator/Generators/CSharp/CSharpSources.cs

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ public override void GenerateClassSpecifier(Class @class)
781781
}
782782
}
783783

784-
if (@class.IsGenerated && isBindingGen && @class.IsRefType && !@class.IsOpaque)
784+
if (@class.IsGenerated && isBindingGen && NeedsDispose(@class) && !@class.IsOpaque)
785785
{
786786
bases.Add("IDisposable");
787787
}
@@ -790,6 +790,12 @@ public override void GenerateClassSpecifier(Class @class)
790790
Write(" : {0}", string.Join(", ", bases));
791791
}
792792

793+
private bool NeedsDispose(Class @class)
794+
{
795+
return @class.IsRefType || @class.IsValueType &&
796+
(@class.GetConstCharFieldProperties().Any() || @class.HasNonTrivialDestructor);
797+
}
798+
793799
private bool CanUseSequentialLayout(Class @class)
794800
{
795801
if (@class.IsUnion || @class.HasUnionFields)
@@ -2223,25 +2229,24 @@ public void GenerateClassConstructors(Class @class)
22232229
GenerateMethod(ctor, @class);
22242230
}
22252231

2226-
if (@class.IsRefType)
2227-
{
2228-
// We used to always call GenerateClassFinalizer here. However, the
2229-
// finalizer calls Dispose which is conditionally implemented below.
2230-
// Instead, generate the finalizer only if Dispose is also implemented.
2232+
// We used to always call GenerateClassFinalizer here. However, the
2233+
// finalizer calls Dispose which is conditionally implemented below.
2234+
// Instead, generate the finalizer only if Dispose is also implemented.
22312235

2232-
// ensure any virtual dtor in the chain is called
2233-
var dtor = @class.Destructors.FirstOrDefault(d => d.Access != AccessSpecifier.Private);
2234-
if (ShouldGenerateClassNativeField(@class) ||
2235-
(dtor != null && (dtor.IsVirtual || @class.HasNonTrivialDestructor)) ||
2236-
// virtual destructors in abstract classes may lack a pointer in the v-table
2237-
// so they have to be called by symbol; thus we need an explicit Dispose override
2238-
@class.IsAbstract)
2239-
if (!@class.IsOpaque)
2240-
{
2236+
// ensure any virtual dtor in the chain is called
2237+
var dtor = @class.Destructors.FirstOrDefault(d => d.Access != AccessSpecifier.Private);
2238+
if (ShouldGenerateClassNativeField(@class) ||
2239+
(dtor != null && (dtor.IsVirtual || @class.HasNonTrivialDestructor)) ||
2240+
// virtual destructors in abstract classes may lack a pointer in the v-table
2241+
// so they have to be called by symbol; thus we need an explicit Dispose override
2242+
@class.IsAbstract)
2243+
if (!@class.IsOpaque)
2244+
{
2245+
if (@class.IsRefType)
22412246
GenerateClassFinalizer(@class);
2247+
if (NeedsDispose(@class))
22422248
GenerateDisposeMethods(@class);
2243-
}
2244-
}
2249+
}
22452250
}
22462251

22472252
private void GenerateClassFinalizer(Class @class)
@@ -2250,19 +2255,23 @@ private void GenerateClassFinalizer(Class @class)
22502255
return;
22512256

22522257
using (PushWriteBlock(BlockKind.Finalizer, $"~{@class.Name}()", NewLineKind.BeforeNextBlock))
2253-
WriteLine($"Dispose(false, callNativeDtor : {Helpers.OwnsNativeInstanceIdentifier} );");
2258+
WriteLine($"Dispose(false, callNativeDtor: {Helpers.OwnsNativeInstanceIdentifier});");
22542259
}
22552260

22562261
private void GenerateDisposeMethods(Class @class)
22572262
{
22582263
var hasBaseClass = @class.HasBaseClass && @class.BaseClass.IsRefType;
22592264

2265+
var hasDtorParam = @class.IsRefType;
2266+
22602267
// Generate the IDispose Dispose() method.
22612268
if (!hasBaseClass)
22622269
{
22632270
using (PushWriteBlock(BlockKind.Method, "public void Dispose()", NewLineKind.BeforeNextBlock))
22642271
{
2265-
WriteLine($"Dispose(disposing: true, callNativeDtor : {Helpers.OwnsNativeInstanceIdentifier} );");
2272+
WriteLine(hasDtorParam
2273+
? $"Dispose(disposing: true, callNativeDtor: {Helpers.OwnsNativeInstanceIdentifier});"
2274+
: "Dispose(disposing: true);");
22662275
if (Options.GenerateFinalizerFor(@class))
22672276
WriteLine("GC.SuppressFinalize(this);");
22682277
}
@@ -2276,7 +2285,10 @@ private void GenerateDisposeMethods(Class @class)
22762285

22772286
// Generate Dispose(bool, bool) method
22782287
var ext = !@class.IsValueType ? (hasBaseClass ? "override " : "virtual ") : string.Empty;
2279-
using var _ = PushWriteBlock(BlockKind.Method, $"internal protected {ext}void Dispose(bool disposing, bool callNativeDtor )", NewLineKind.BeforeNextBlock);
2288+
var protectionLevel = @class.IsValueType ? "private" : "internal protected";
2289+
using var _ = PushWriteBlock(BlockKind.Method,
2290+
$"{protectionLevel} {ext}void Dispose(bool disposing{(hasDtorParam ? ", bool callNativeDtor" : "")})",
2291+
NewLineKind.BeforeNextBlock);
22802292

22812293
if (@class.IsRefType)
22822294
{
@@ -2323,7 +2335,7 @@ private void GenerateDisposeMethods(Class @class)
23232335
// instance from the NativeToManagedMap. Of course, this is somewhat half-hearted
23242336
// since we can't/don't do this when there's no virtual dtor available to detour.
23252337
// Anyway, we must be able to call the native dtor in this case even if we don't
2326-
/// own the underlying native instance.
2338+
// own the underlying native instance.
23272339
//
23282340
// 2. When we we pass a disposable object to a function "by value" then the callee
23292341
// calls the dtor on the argument so our marshalling code must have a way from preventing
@@ -2335,41 +2347,67 @@ private void GenerateDisposeMethods(Class @class)
23352347
// }
23362348
//
23372349
// IDisposable.Dispose() and Object.Finalize() set callNativeDtor = Helpers.OwnsNativeInstanceIdentifier
2338-
WriteLine("if (callNativeDtor)");
2339-
if (@class.IsDependent || dtor.IsVirtual)
2340-
WriteOpenBraceAndIndent();
2341-
else
2342-
Indent();
2350+
if (hasDtorParam)
2351+
{
2352+
WriteLine("if (callNativeDtor)");
2353+
if (@class.IsDependent || dtor.IsVirtual)
2354+
WriteOpenBraceAndIndent();
2355+
else
2356+
Indent();
2357+
}
2358+
23432359
if (dtor.IsVirtual)
23442360
this.GenerateMember(@class, c => GenerateDestructorCall(
23452361
c is ClassTemplateSpecialization ?
23462362
c.Methods.First(m => m.InstantiatedFrom == dtor) : dtor));
23472363
else
23482364
this.GenerateMember(@class, c => GenerateMethodBody(c, dtor));
2349-
if (@class.IsDependent || dtor.IsVirtual)
2350-
UnindentAndWriteCloseBrace();
2351-
else
2352-
Unindent();
2365+
2366+
if (hasDtorParam)
2367+
{
2368+
if (@class.IsDependent || dtor.IsVirtual)
2369+
UnindentAndWriteCloseBrace();
2370+
else
2371+
Unindent();
2372+
}
23532373
}
23542374
}
23552375

23562376
// If we have any fields holding references to unmanaged memory allocated here, free the
23572377
// referenced memory. Don't rely on testing if the field's IntPtr is IntPtr.Zero since
23582378
// unmanaged memory isn't always initialized and/or a reference may be owned by the
23592379
// native side.
2360-
//
2380+
2381+
string ptr;
2382+
if (@class.IsValueType)
2383+
{
2384+
ptr = $"{Helpers.InstanceIdentifier}Ptr";
2385+
WriteLine($"fixed ({Helpers.InternalStruct}* {ptr} = &{Helpers.InstanceIdentifier})");
2386+
WriteOpenBraceAndIndent();
2387+
}
2388+
else
2389+
{
2390+
ptr = $"(({Helpers.InternalStruct}*){Helpers.InstanceIdentifier})";
2391+
}
2392+
23612393
foreach (var prop in @class.GetConstCharFieldProperties())
23622394
{
23632395
string name = prop.Field.OriginalName;
2364-
var ptr = $"(({Helpers.InternalStruct}*){Helpers.InstanceIdentifier})->{name}";
23652396
WriteLine($"if (__{name}_OwnsNativeMemory)");
2366-
WriteLineIndent($"Marshal.FreeHGlobal({ptr});");
2397+
WriteLineIndent($"Marshal.FreeHGlobal({ptr}->{name});");
23672398
}
23682399

2369-
WriteLine("if ({0})", Helpers.OwnsNativeInstanceIdentifier);
2370-
WriteLineIndent("Marshal.FreeHGlobal({0});", Helpers.InstanceIdentifier);
2400+
if (@class.IsValueType)
2401+
{
2402+
UnindentAndWriteCloseBrace();
2403+
}
2404+
else
2405+
{
2406+
WriteLine("if ({0})", Helpers.OwnsNativeInstanceIdentifier);
2407+
WriteLineIndent("Marshal.FreeHGlobal({0});", Helpers.InstanceIdentifier);
23712408

2372-
WriteLine("{0} = IntPtr.Zero;", Helpers.InstanceIdentifier);
2409+
WriteLine("{0} = IntPtr.Zero;", Helpers.InstanceIdentifier);
2410+
}
23732411
}
23742412

23752413
private bool GenerateDestructorCall(Method dtor)

tests/dotnet/CSharp/CSharp.Tests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2034,6 +2034,7 @@ public void TestValueTypeStringMember()
20342034
test.CharPtrMember = "test2";
20352035
Assert.AreEqual("test", test.StringMember);
20362036
Assert.AreEqual("test2", test.CharPtrMember);
2037+
test.Dispose();
20372038
}
20382039

20392040
[Test]
@@ -2047,6 +2048,7 @@ public void TestValueTypeStringMemberDefaulted()
20472048
test.CharPtrMember = "test2";
20482049
Assert.AreEqual("test", test.StringMember);
20492050
Assert.AreEqual("test2", test.CharPtrMember);
2051+
test.Dispose();
20502052
}
20512053

20522054
[Test]
@@ -2059,5 +2061,6 @@ public void TestValueTypeStringMemberDefaultedCtor()
20592061
test.CharPtrMember = "test2";
20602062
Assert.AreEqual("test", test.StringMember);
20612063
Assert.AreEqual("test2", test.CharPtrMember);
2064+
test.Dispose();
20622065
}
20632066
}

0 commit comments

Comments
 (0)