Skip to content

Commit 123d4b9

Browse files
committed
Clean-up another state throw issue
1 parent 0f4cfd8 commit 123d4b9

1 file changed

Lines changed: 48 additions & 36 deletions

File tree

src/Npgsql/Internal/PgConverter.cs

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -174,33 +174,39 @@ public Size BindAsObject(in BindContext context, object? value, ref object? writ
174174
try
175175
{
176176
size = BindValueAsObject(context, value, ref writeState);
177+
178+
if ((originalWriteState is not null || context.IsBindFixedSize) && !ReferenceEquals(originalWriteState, writeState)
179+
&& (context.IsBindFixedSize || writeState is null || originalWriteState is IDisposable))
180+
ThrowWriteStateLifecycleViolation(context.IsBindFixedSize);
181+
182+
// Catches non-Exact sizes from both paths (the IsBindFixedSize Kind check is folded in here —
183+
// a converter declaring fixed-size that returned a non-Exact size trips this same throw).
184+
switch (size.Kind)
185+
{
186+
case SizeKind.UpperBound:
187+
ThrowHelper.ThrowInvalidOperationException($"{nameof(SizeKind.UpperBound)} is not a valid return value for BindValue.");
188+
break;
189+
case SizeKind.Unknown:
190+
ThrowHelper.ThrowInvalidOperationException($"{nameof(SizeKind.Unknown)} is not a valid return value for BindValue.");
191+
break;
192+
}
177193
}
178194
catch
179195
{
180196
// Contract: writeState transitions to null on throw. BindValue is free to assign partial
181197
// state to writeState as it works (composing converters do this so their wrapper's Dispose
182198
// can clean up populated slots). The framework's safety net here disposes anything still
183-
// in writeState and nulls it, so callers see a uniform "clean on throw" semantic.
184-
if (writeState is IDisposable d)
185-
d.Dispose();
186-
writeState = null;
199+
// observable to us and nulls the slot, so callers see a uniform "clean on throw" semantic.
200+
// Both current and original may need disposing on a lifecycle-violation swap; the
201+
// ReferenceEquals gate collapses to a single Dispose on the legal no-swap case.
202+
// Null first, then dispose: a throwing Dispose must not leave callers with a non-null
203+
// writeState pointing at a half-disposed object — they'd dispose it again.
204+
(var current, writeState) = (writeState, null);
205+
(current as IDisposable)?.Dispose();
206+
if (!ReferenceEquals(current, originalWriteState))
207+
(originalWriteState as IDisposable)?.Dispose();
187208
throw;
188209
}
189-
if ((originalWriteState is not null || context.IsBindFixedSize) && !ReferenceEquals(originalWriteState, writeState)
190-
&& (context.IsBindFixedSize || writeState is null || originalWriteState is IDisposable))
191-
ThrowWriteStateLifecycleViolation(context.IsBindFixedSize);
192-
193-
// Catches non-Exact sizes from both paths (the IsBindFixedSize Kind check is folded in here —
194-
// a converter declaring fixed-size that returned a non-Exact size trips this same throw).
195-
switch (size.Kind)
196-
{
197-
case SizeKind.UpperBound:
198-
ThrowHelper.ThrowInvalidOperationException($"{nameof(SizeKind.UpperBound)} is not a valid return value for BindValue.");
199-
break;
200-
case SizeKind.Unknown:
201-
ThrowHelper.ThrowInvalidOperationException($"{nameof(SizeKind.Unknown)} is not a valid return value for BindValue.");
202-
break;
203-
}
204210

205211
return size;
206212
}
@@ -344,31 +350,37 @@ public Size Bind(in BindContext context,
344350
try
345351
{
346352
size = BindValue(context, value, ref writeState);
353+
354+
if ((originalWriteState is not null || context.IsBindFixedSize) && !ReferenceEquals(originalWriteState, writeState)
355+
&& (context.IsBindFixedSize || writeState is null || originalWriteState is IDisposable))
356+
ThrowWriteStateLifecycleViolation(context.IsBindFixedSize);
357+
358+
switch (size.Kind)
359+
{
360+
case SizeKind.UpperBound:
361+
ThrowHelper.ThrowInvalidOperationException($"{nameof(SizeKind.UpperBound)} is not a valid return value for {nameof(BindValue)}.");
362+
break;
363+
case SizeKind.Unknown:
364+
ThrowHelper.ThrowInvalidOperationException($"{nameof(SizeKind.Unknown)} is not a valid return value for {nameof(BindValue)}.");
365+
break;
366+
}
347367
}
348368
catch
349369
{
350370
// Contract: writeState transitions to null on throw. BindValue is free to assign partial
351371
// state to writeState as it works (composing converters do this so their wrapper's Dispose
352372
// can clean up populated slots). The framework's safety net here disposes anything still
353-
// in writeState and nulls it, so callers see a uniform "clean on throw" semantic.
354-
if (writeState is IDisposable d)
355-
d.Dispose();
356-
writeState = null;
373+
// observable to us and nulls the slot, so callers see a uniform "clean on throw" semantic.
374+
// Both current and original may need disposing on a lifecycle-violation swap; the
375+
// ReferenceEquals gate collapses to a single Dispose on the legal no-swap case.
376+
// Null first, then dispose: a throwing Dispose must not leave callers with a non-null
377+
// writeState pointing at a half-disposed object — they'd dispose it again.
378+
(var current, writeState) = (writeState, null);
379+
(current as IDisposable)?.Dispose();
380+
if (!ReferenceEquals(current, originalWriteState))
381+
(originalWriteState as IDisposable)?.Dispose();
357382
throw;
358383
}
359-
if ((originalWriteState is not null || context.IsBindFixedSize) && !ReferenceEquals(originalWriteState, writeState)
360-
&& (context.IsBindFixedSize || writeState is null || originalWriteState is IDisposable))
361-
ThrowWriteStateLifecycleViolation(context.IsBindFixedSize);
362-
363-
switch (size.Kind)
364-
{
365-
case SizeKind.UpperBound:
366-
ThrowHelper.ThrowInvalidOperationException($"{nameof(SizeKind.UpperBound)} is not a valid return value for {nameof(BindValue)}.");
367-
break;
368-
case SizeKind.Unknown:
369-
ThrowHelper.ThrowInvalidOperationException($"{nameof(SizeKind.Unknown)} is not a valid return value for {nameof(BindValue)}.");
370-
break;
371-
}
372384

373385
return size;
374386
}

0 commit comments

Comments
 (0)