Skip to content

Commit f0fbee1

Browse files
authored
Avoid throw/catch exceptions when requesting optional buffer through IBufferProtocol (#1902)
* Avoid throw/catch exceptions when requesting optional buffer through `IBufferProtocol` * Update after review
1 parent e47fcbe commit f0fbee1

File tree

8 files changed

+69
-24
lines changed

8 files changed

+69
-24
lines changed

src/core/IronPython.Modules/_ctypes/CData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public void Dispose() {
125125

126126
private int _numExports;
127127

128-
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags) {
128+
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags, bool throwOnError) {
129129
if (_disposed) throw new ObjectDisposedException(GetType().Name);
130130
_ = MemHolder; // check if fully initialized
131131
Interlocked.Increment(ref _numExports);

src/core/IronPython.Modules/array.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1037,7 +1037,7 @@ bool ICollection<object>.Remove(object item) {
10371037

10381038
#region IBufferProtocol Members
10391039

1040-
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags) {
1040+
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags, bool throwOnError) {
10411041
return _data.GetBuffer(this, _typeCode.ToString(), flags);
10421042
}
10431043

src/core/IronPython.Modules/mmap.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,10 +1314,13 @@ void IWeakReferenceable.SetFinalizer(WeakRefTracker value) {
13141314

13151315
#endregion
13161316

1317-
public IPythonBuffer GetBuffer(BufferFlags flags = BufferFlags.Simple) {
1318-
if (flags.HasFlag(BufferFlags.Writable) && IsReadOnly)
1319-
throw PythonOps.BufferError("Object is not writable.");
1320-
1317+
public IPythonBuffer? GetBuffer(BufferFlags flags, bool throwOnError) {
1318+
if (flags.HasFlag(BufferFlags.Writable) && IsReadOnly) {
1319+
if (throwOnError) {
1320+
throw PythonOps.BufferError("Object is not writable.");
1321+
}
1322+
return null;
1323+
}
13211324
return new MmapBuffer(this, flags);
13221325
}
13231326

src/core/IronPython/Runtime/BufferProtocol.cs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Buffers;
99
using System.Collections.Generic;
1010
using System.Diagnostics;
11+
using System.Diagnostics.CodeAnalysis;
1112
using System.Runtime.InteropServices;
1213

1314
using IronPython.Runtime.Exceptions;
@@ -17,7 +18,20 @@ namespace IronPython.Runtime {
1718
/// Equivalent functionality of CPython's <a href="https://docs.python.org/3/c-api/buffer.html">Buffer Protocol</a>.
1819
/// </summary>
1920
public interface IBufferProtocol {
20-
IPythonBuffer GetBuffer(BufferFlags flags = BufferFlags.Simple);
21+
/// <summary>
22+
/// Exports the object's data as a buffer.
23+
/// </summary>
24+
/// <param name="flags">
25+
/// Flags specifying the type of buffer the consumer is prepared to deal with.
26+
/// </param>
27+
/// <param name="throwOnError">
28+
/// This parameter is advisory: if true, the method may throw <c>BufferError</c> instead of returning null if the buffer type is not supported,
29+
/// with the exception's message providing more information.
30+
/// </param>
31+
/// <returns>
32+
/// An instance of <see cref="IPythonBuffer"/> if the requested buffer type is supported, or null if not.
33+
/// </returns>
34+
IPythonBuffer? GetBuffer(BufferFlags flags, bool throwOnError);
2135
}
2236

2337
/// <summary>
@@ -119,16 +133,31 @@ public enum BufferFlags {
119133
#endregion
120134
}
121135

122-
internal static class BufferProtocolExtensions {
136+
137+
public static class BufferProtocolExtensions {
138+
public static IPythonBuffer GetBuffer(this IBufferProtocol bufferProtocol, BufferFlags flags = BufferFlags.Simple)
139+
=> bufferProtocol.GetBuffer(flags, throwOnError: true) ?? throw new BufferException("Buffer type not supported");
140+
123141
internal static IPythonBuffer? GetBufferNoThrow(this IBufferProtocol bufferProtocol, BufferFlags flags = BufferFlags.Simple) {
124142
try {
125-
return bufferProtocol.GetBuffer(flags);
143+
return bufferProtocol.GetBuffer(flags, throwOnError: false);
126144
} catch (BufferException) {
127145
return null;
128146
}
129147
}
148+
149+
internal static bool TryGetBuffer(this IBufferProtocol bufferProtocol, BufferFlags flags, [NotNullWhen(true)] out IPythonBuffer? buffer) {
150+
try {
151+
buffer = bufferProtocol.GetBuffer(flags, throwOnError: false);
152+
return buffer is not null;
153+
} catch (BufferException) {
154+
buffer = null;
155+
return false;
156+
}
157+
}
130158
}
131159

160+
132161
/// <summary>
133162
/// Provides low-level read-write access to byte data of the underlying object.
134163
/// </summary>

src/core/IronPython/Runtime/ByteArray.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1584,7 +1584,7 @@ private bool Equals(ReadOnlySpan<byte> other) {
15841584

15851585
#region IBufferProtocol Members
15861586

1587-
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags) {
1587+
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags, bool throwOnError) {
15881588
return _bytes.GetBuffer(this, "B", flags);
15891589
}
15901590

src/core/IronPython/Runtime/Bytes.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,10 +1241,13 @@ Expression IExpressionSerializable.CreateExpression() {
12411241

12421242
#region IBufferProtocol Support
12431243

1244-
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags) {
1245-
if (flags.HasFlag(BufferFlags.Writable))
1246-
throw PythonOps.BufferError("Object is not writable.");
1247-
1244+
IPythonBuffer? IBufferProtocol.GetBuffer(BufferFlags flags, bool throwOnError) {
1245+
if (flags.HasFlag(BufferFlags.Writable)) {
1246+
if (throwOnError) {
1247+
throw PythonOps.BufferError("bytes object is not writable");
1248+
}
1249+
return null;
1250+
}
12481251
return new BytesView(this, flags);
12491252
}
12501253

src/core/IronPython/Runtime/ConversionWrappers.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,13 +436,16 @@ public MemoryBufferProtocolWrapper(Memory<byte> memory) {
436436
_memory = memory;
437437
}
438438

439-
public IPythonBuffer GetBuffer(BufferFlags flags) {
439+
public IPythonBuffer? GetBuffer(BufferFlags flags, bool throwOnError) {
440440
if (_memory.HasValue) {
441441
return new MemoryBufferWrapper(_memory.Value, flags);
442442
}
443443

444444
if (flags.HasFlag(BufferFlags.Writable)) {
445-
throw Operations.PythonOps.BufferError("ReadOnlyMemory is not writable.");
445+
if (throwOnError) {
446+
throw Operations.PythonOps.BufferError("ReadOnlyMemory is not writable.");
447+
}
448+
return null;
446449
}
447450

448451
return new MemoryBufferWrapper(_rom, flags);

src/core/IronPython/Runtime/MemoryView.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -963,32 +963,39 @@ void IWeakReferenceable.SetFinalizer(WeakRefTracker value) {
963963

964964
#region IBufferProtocol Members
965965

966-
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags) {
966+
IPythonBuffer? IBufferProtocol.GetBuffer(BufferFlags flags, bool throwOnError) {
967967
CheckBuffer();
968968

969969
if (flags.HasFlag(BufferFlags.Writable) && _isReadOnly)
970-
throw PythonOps.BufferError("memoryview: underlying buffer is not writable");
970+
return ReportError("memoryview: underlying buffer is not writable");
971971

972972
if (flags.HasFlag(BufferFlags.CContiguous) && !_isCContig)
973-
throw PythonOps.BufferError("memoryview: underlying buffer is not C-contiguous");
973+
return ReportError("memoryview: underlying buffer is not C-contiguous");
974974

975975
if (flags.HasFlag(BufferFlags.FContiguous) && !_isFContig)
976-
throw PythonOps.BufferError("memoryview: underlying buffer is not Fortran contiguous");
976+
return ReportError("memoryview: underlying buffer is not Fortran contiguous");
977977

978978
if (flags.HasFlag(BufferFlags.AnyContiguous) && !_isCContig && !_isFContig)
979-
throw PythonOps.BufferError("memoryview: underlying buffer is not contiguous");
979+
return ReportError("memoryview: underlying buffer is not contiguous");
980980

981981
// TODO: Support for suboffsets
982982
//if (!flags.HasFlag(!BufferFlags.Indirect) && _suboffsets != null)
983-
// throw PythonOps.BufferError("memoryview: underlying buffer requires suboffsets");
983+
// return ReportError("memoryview: underlying buffer requires suboffsets");
984984

985985
if (!flags.HasFlag(BufferFlags.Strides) && !_isCContig)
986-
throw PythonOps.BufferError("memoryview: underlying buffer is not C-contiguous");
986+
return ReportError("memoryview: underlying buffer is not C-contiguous");
987987

988988
if (!flags.HasFlag(BufferFlags.ND) && flags.HasFlag(BufferFlags.Format))
989-
throw PythonOps.BufferError("memoryview: cannot cast to unsigned bytes if the format flag is present");
989+
return ReportError("memoryview: cannot cast to unsigned bytes if the format flag is present");
990990

991991
return new MemoryView(this, flags);
992+
993+
IPythonBuffer? ReportError(string msg) {
994+
if (throwOnError) {
995+
throw PythonOps.BufferError(msg);
996+
}
997+
return null;
998+
}
992999
}
9931000

9941001
#endregion

0 commit comments

Comments
 (0)