Skip to content

Commit 7b79705

Browse files
Merge pull request #14 from estebanzimanyi/fix/const-char-return
Read a borrowed const char * return without freeing it
2 parents feb1777 + 5a785ae commit 7b79705

6 files changed

Lines changed: 90 additions & 22 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ jobs:
3838

3939
- name: Run tests
4040
run: dotnet test MEOS.NET.sln -c Release --no-build
41+
env:
42+
LD_LIBRARY_PATH: ${{ steps.provision.outputs.libmeos-prefix }}/lib
4143

4244
- name: Smoke-test the FFI
4345
run: dotnet run --project ExampleApp/ExampleApp.csproj -c Release --no-build
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using MEOS.NET.Enums;
2+
using MEOS.NET.Types.Temporal.Number.Float;
3+
4+
namespace MEOS.NET.Tests
5+
{
6+
/// <summary>
7+
/// The MEOS functions that answer with a `const char *` hand back a pointer
8+
/// into a static table, so the caller reads it and does not free it. Freeing
9+
/// it takes the process down, which is what these read.
10+
/// </summary>
11+
[TestClass]
12+
public class BorrowedStringTests : MeosTest
13+
{
14+
[TestMethod]
15+
public void InterpolationOfALinearSequenceIsRead()
16+
{
17+
TemporalFloat temp = TemporalFloat.FromString("[25.0@2024-12-06, 27.0@2024-12-07]");
18+
19+
Assert.AreEqual(InterpolationType.Linear, temp.Interpolation());
20+
}
21+
22+
[TestMethod]
23+
public void InterpolationOfADiscreteSequenceIsRead()
24+
{
25+
TemporalFloat temp = TemporalFloat.FromString("{25.0@2024-12-06, 27.0@2024-12-07}");
26+
27+
Assert.AreEqual(InterpolationType.Discrete, temp.Interpolation());
28+
}
29+
30+
[TestMethod]
31+
public void TheSameBorrowedNameIsReadableTwice()
32+
{
33+
TemporalFloat first = TemporalFloat.FromString("[25.0@2024-12-06, 27.0@2024-12-07]");
34+
TemporalFloat second = TemporalFloat.FromString("[1.0@2024-12-06, 2.0@2024-12-07]");
35+
36+
Assert.AreEqual(first.Interpolation(), second.Interpolation());
37+
}
38+
}
39+
}

MEOS.NET.Tests/MeosTest.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using MEOS.NET.Lifecycle;
2+
3+
namespace MEOS.NET.Tests
4+
{
5+
/// <summary>
6+
/// The base every test class derives from, so each test runs against an open
7+
/// MEOS session.
8+
///
9+
/// MEOS keeps its session state — the timezone its text I/O reads and writes
10+
/// through, and the error handler — per thread, and the test host runs a test
11+
/// on whichever thread it has free. A session opened once for the assembly
12+
/// therefore reaches the thread that opened it and no other: elsewhere a
13+
/// timestamp is read in the machine's own timezone rather than the suite's,
14+
/// and an error raised there reaches no handler at all.
15+
/// </summary>
16+
public abstract class MeosTest
17+
{
18+
[TestInitialize]
19+
public void OpenMeosSession() => MEOSLifecycle.Initialize("UTC");
20+
}
21+
}

MEOS.NET.Tests/TemporalGeometryPointTests.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

MEOS.NET/Types/Temporal/Temporal.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using MEOS.NET.Enums;
2+
using MEOS.NET.Errors;
3+
using MEOS.NET.Exceptions;
24
using MEOS.NET.Helpers;
35
using MEOS.NET.Internal;
46
using MEOS.NET.Types.General;
@@ -123,7 +125,13 @@ public TimestampTzSpan BoundingBox()
123125

124126
public InterpolationType Interpolation()
125127
{
126-
var interpolationStr = MEOSExposedFunctions.temporal_interp(this._ptr);
128+
// temporal_interp names one of the interpolations MEOS defines, out of
129+
// its own static table, so the name is there for every temporal value.
130+
var interpolationStr = MEOSExposedFunctions.temporal_interp(this._ptr)
131+
?? throw new MEOSUnspecifiedInternalException(
132+
(int)MEOSErrorCodes.UnspecifiedInternalError,
133+
MEOSErrorCodes.UnspecifiedInternalError,
134+
"MEOS named no interpolation for this temporal value");
127135
return EnumConverter.InterpolationTypeFromString(interpolationStr);
128136
}
129137

tools/codegen.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,17 @@ def is_string_pointer(c_type: str) -> bool:
133133
return t in ("char *", "char**", "char *const *", "const char *")
134134

135135

136+
def is_borrowed_string(c_type: str) -> bool:
137+
"""A ``const char *`` return the caller does not own.
138+
139+
The Utf8 string marshaller frees what it is handed back, which is right for
140+
the ``char *`` MEOS mallocs for the caller and fatal for the ``const char *``
141+
it returns out of a static table — ``interptype_name`` hands back an element
142+
of ``MEOS_INTERPTYPE_NAMES``. A borrowed return comes back as the pointer
143+
itself and is read without a free."""
144+
return " ".join(c_type.split()).startswith("const char *")
145+
146+
136147
def csharp_type_for(canonical: str) -> str:
137148
"""Translate a libclang-canonical C type to a C# type for LibraryImport signatures."""
138149
t = canonical.strip()
@@ -155,6 +166,8 @@ def csharp_param_type(c_type: str, canonical: str) -> str:
155166

156167

157168
def csharp_return_type(c_type: str, canonical: str) -> str:
169+
if is_borrowed_string(c_type):
170+
return "IntPtr"
158171
if is_string_pointer(c_type):
159172
return "string"
160173
return csharp_type_for(canonical)
@@ -472,6 +485,12 @@ def _copy(indent: str) -> list[str]:
472485
def _emit_simple_passthrough(f: dict, ext_params: str, ext_args: str, default_rt: str | None = None) -> list[str]:
473486
name = f["name"]
474487
rt = default_rt or csharp_return_type(f["returnType"]["c"], f["returnType"]["canonical"])
488+
if default_rt is None and is_borrowed_string(f["returnType"]["c"]):
489+
return [
490+
f" public static string? {name}({ext_params})",
491+
f" => Marshal.PtrToStringUTF8("
492+
f"SafeExecution<IntPtr>(() => MEOSExternalFunctions.{name}({ext_args})));",
493+
]
475494
if rt == "void":
476495
return [
477496
f" public static void {name}({ext_params})",

0 commit comments

Comments
 (0)