Skip to content

Commit 6387bda

Browse files
authored
Merge pull request #43462 from sharwell/port-safe-handle
Use safe handles for SQLite interop
2 parents 146f920 + 0677267 commit 6387bda

File tree

9 files changed

+422
-68
lines changed

9 files changed

+422
-68
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System;
8+
using System.Runtime.InteropServices;
9+
using Roslyn.Utilities;
10+
11+
namespace Microsoft.CodeAnalysis.Shared.Extensions
12+
{
13+
internal static class SafeHandleExtensions
14+
{
15+
/// <summary>
16+
/// Acquires a lease on a safe handle. The lease increments the reference count of the <see cref="SafeHandle"/>
17+
/// to ensure the handle is not released prior to the lease being released.
18+
/// </summary>
19+
/// <remarks>
20+
/// This method is intended to be used in the initializer of a <c>using</c> statement. Failing to release the
21+
/// lease will permanently prevent the underlying <see cref="SafeHandle"/> from being released by the garbage
22+
/// collector.
23+
/// </remarks>
24+
/// <param name="handle">The <see cref="SafeHandle"/> to lease.</param>
25+
/// <returns>A <see cref="SafeHandleLease"/>, which must be disposed to release the resource.</returns>
26+
/// <exception cref="ObjectDisposedException">If the lease could not be acquired.</exception>
27+
public static SafeHandleLease Lease(this SafeHandle handle)
28+
{
29+
RoslynDebug.AssertNotNull(handle);
30+
31+
var success = false;
32+
try
33+
{
34+
handle.DangerousAddRef(ref success);
35+
if (!success)
36+
throw new ObjectDisposedException(handle.GetType().FullName);
37+
38+
return new SafeHandleLease(handle);
39+
}
40+
catch
41+
{
42+
if (success)
43+
handle.DangerousRelease();
44+
45+
throw;
46+
}
47+
}
48+
}
49+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System;
8+
using System.Runtime.InteropServices;
9+
10+
namespace Microsoft.CodeAnalysis.Shared.Extensions
11+
{
12+
/// <summary>
13+
/// Represents a lease of a <see cref="SafeHandle"/>.
14+
/// </summary>
15+
/// <seealso cref="SafeHandleExtensions.Lease"/>
16+
internal readonly struct SafeHandleLease : IDisposable
17+
{
18+
private readonly SafeHandle? _handle;
19+
20+
internal SafeHandleLease(SafeHandle handle)
21+
=> _handle = handle;
22+
23+
/// <summary>
24+
/// Releases the <see cref="SafeHandle"/> lease. The behavior of this method is unspecified if called more than
25+
/// once.
26+
/// </summary>
27+
public void Dispose()
28+
=> _handle?.DangerousRelease();
29+
}
30+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System.Diagnostics.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.Shared.Extensions;
9+
using SQLitePCL;
10+
11+
namespace Microsoft.CodeAnalysis.SQLite.Interop
12+
{
13+
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Name chosen to match SQLitePCL.raw")]
14+
internal static class NativeMethods
15+
{
16+
public static SafeSqliteHandle sqlite3_open_v2(string filename, int flags, string vfs, out Result result)
17+
{
18+
result = (Result)raw.sqlite3_open_v2(filename, out var wrapper, flags, vfs);
19+
if (result != Result.OK)
20+
{
21+
wrapper = null;
22+
}
23+
24+
try
25+
{
26+
// Always return a non-null handle to match default P/Invoke marshaling behavior. SafeHandle.IsInvalid
27+
// will be true when the handle is not usable, but the handle instance can be disposed either way.
28+
return new SafeSqliteHandle(wrapper);
29+
}
30+
catch
31+
{
32+
raw.sqlite3_close(wrapper);
33+
throw;
34+
}
35+
}
36+
37+
public static SafeSqliteStatementHandle sqlite3_prepare_v2(SafeSqliteHandle db, string sql, out Result result)
38+
{
39+
using var _ = db.Lease();
40+
41+
result = (Result)raw.sqlite3_prepare_v2(db.DangerousGetHandle(), sql, out var wrapper);
42+
if (result != (int)Result.OK)
43+
{
44+
wrapper = null;
45+
}
46+
47+
try
48+
{
49+
// Always return a non-null handle to match default P/Invoke marshaling behavior. SafeHandle.IsInvalid
50+
// will be true when the handle is not usable, but the handle instance can be disposed either way.
51+
return new SafeSqliteStatementHandle(db, wrapper);
52+
}
53+
catch
54+
{
55+
raw.sqlite3_finalize(wrapper);
56+
throw;
57+
}
58+
}
59+
60+
public static SafeSqliteBlobHandle sqlite3_blob_open(SafeSqliteHandle db, string sdb, string table, string col, long rowid, int flags, out Result result)
61+
{
62+
using var _ = db.Lease();
63+
64+
result = (Result)raw.sqlite3_blob_open(db.DangerousGetHandle(), sdb, table, col, rowid, flags, out var wrapper);
65+
if (result != (int)Result.OK)
66+
{
67+
wrapper = null;
68+
}
69+
70+
try
71+
{
72+
// Always return a non-null handle to match default P/Invoke marshaling behavior. SafeHandle.IsInvalid
73+
// will be true when the handle is not usable, but the handle instance can be disposed either way.
74+
return new SafeSqliteBlobHandle(db, wrapper);
75+
}
76+
catch
77+
{
78+
raw.sqlite3_blob_close(wrapper);
79+
throw;
80+
}
81+
}
82+
83+
public static string sqlite3_errmsg(SafeSqliteHandle db)
84+
{
85+
using var _ = db.Lease();
86+
return raw.sqlite3_errmsg(db.DangerousGetHandle());
87+
}
88+
89+
public static string sqlite3_errstr(int rc)
90+
{
91+
return raw.sqlite3_errstr(rc);
92+
}
93+
94+
public static int sqlite3_extended_errcode(SafeSqliteHandle db)
95+
{
96+
using var _ = db.Lease();
97+
return raw.sqlite3_extended_errcode(db.DangerousGetHandle());
98+
}
99+
100+
public static Result sqlite3_busy_timeout(SafeSqliteHandle db, int ms)
101+
{
102+
using var _ = db.Lease();
103+
return (Result)raw.sqlite3_busy_timeout(db.DangerousGetHandle(), ms);
104+
}
105+
106+
public static long sqlite3_last_insert_rowid(SafeSqliteHandle db)
107+
{
108+
using var _ = db.Lease();
109+
return raw.sqlite3_last_insert_rowid(db.DangerousGetHandle());
110+
}
111+
112+
public static int sqlite3_blob_bytes(SafeSqliteBlobHandle blob)
113+
{
114+
using var _ = blob.Lease();
115+
return raw.sqlite3_blob_bytes(blob.DangerousGetHandle());
116+
}
117+
118+
public static Result sqlite3_blob_read(SafeSqliteBlobHandle blob, byte[] b, int n, int offset)
119+
{
120+
using var _ = blob.Lease();
121+
return (Result)raw.sqlite3_blob_read(blob.DangerousGetHandle(), b, n, offset);
122+
}
123+
124+
public static Result sqlite3_reset(SafeSqliteStatementHandle stmt)
125+
{
126+
using var _ = stmt.Lease();
127+
return (Result)raw.sqlite3_reset(stmt.DangerousGetHandle());
128+
}
129+
130+
public static Result sqlite3_step(SafeSqliteStatementHandle stmt)
131+
{
132+
using var _ = stmt.Lease();
133+
return (Result)raw.sqlite3_step(stmt.DangerousGetHandle());
134+
}
135+
136+
public static Result sqlite3_bind_text(SafeSqliteStatementHandle stmt, int index, string val)
137+
{
138+
using var _ = stmt.Lease();
139+
return (Result)raw.sqlite3_bind_text(stmt.DangerousGetHandle(), index, val);
140+
}
141+
142+
public static Result sqlite3_bind_int64(SafeSqliteStatementHandle stmt, int index, long val)
143+
{
144+
using var _ = stmt.Lease();
145+
return (Result)raw.sqlite3_bind_int64(stmt.DangerousGetHandle(), index, val);
146+
}
147+
148+
public static byte[] sqlite3_column_blob(SafeSqliteStatementHandle stmt, int index)
149+
{
150+
using var _ = stmt.Lease();
151+
return raw.sqlite3_column_blob(stmt.DangerousGetHandle(), index);
152+
}
153+
154+
public static int sqlite3_column_int(SafeSqliteStatementHandle stmt, int index)
155+
{
156+
using var _ = stmt.Lease();
157+
return raw.sqlite3_column_int(stmt.DangerousGetHandle(), index);
158+
}
159+
160+
public static long sqlite3_column_int64(SafeSqliteStatementHandle stmt, int index)
161+
{
162+
using var _ = stmt.Lease();
163+
return raw.sqlite3_column_int64(stmt.DangerousGetHandle(), index);
164+
}
165+
166+
public static string sqlite3_column_text(SafeSqliteStatementHandle stmt, int index)
167+
{
168+
using var _ = stmt.Lease();
169+
return raw.sqlite3_column_text(stmt.DangerousGetHandle(), index);
170+
}
171+
}
172+
}

src/Workspaces/Core/Portable/Storage/SQLite/Interop/Result.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.SQLite.Interop
1010
internal enum Result
1111
{
1212
OK = 0, /* Successful result */
13-
// ERROR = 1, /* SQL error or missing database */
13+
ERROR = 1, /* SQL error or missing database */
1414
// INTERNAL = 2, /* Internal logic error in SQLite */
1515
// PERM = 3, /* Access permission denied */
1616
// ABORT = 4, /* Callback routine requested an abort */
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System;
8+
using System.Runtime.InteropServices;
9+
using Microsoft.CodeAnalysis.Shared.Extensions;
10+
using SQLitePCL;
11+
12+
namespace Microsoft.CodeAnalysis.SQLite.Interop
13+
{
14+
internal sealed class SafeSqliteBlobHandle : SafeHandle
15+
{
16+
private readonly sqlite3_blob? _wrapper;
17+
private readonly SafeHandleLease _lease;
18+
19+
public SafeSqliteBlobHandle(SafeSqliteHandle sqliteHandle, sqlite3_blob? wrapper)
20+
: base(invalidHandleValue: IntPtr.Zero, ownsHandle: true)
21+
{
22+
_wrapper = wrapper;
23+
SetHandle(wrapper?.ptr ?? IntPtr.Zero);
24+
_lease = sqliteHandle.Lease();
25+
}
26+
27+
public override bool IsInvalid => handle == IntPtr.Zero;
28+
29+
public new sqlite3_blob DangerousGetHandle()
30+
=> _wrapper!;
31+
32+
protected override bool ReleaseHandle()
33+
{
34+
using var _ = _lease;
35+
var result = (Result)raw.sqlite3_blob_close(_wrapper);
36+
return result == Result.OK;
37+
}
38+
}
39+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System;
8+
using System.Runtime.InteropServices;
9+
using SQLitePCL;
10+
11+
namespace Microsoft.CodeAnalysis.SQLite.Interop
12+
{
13+
internal sealed class SafeSqliteHandle : SafeHandle
14+
{
15+
private readonly sqlite3? _wrapper;
16+
17+
public SafeSqliteHandle(sqlite3? wrapper)
18+
: base(invalidHandleValue: IntPtr.Zero, ownsHandle: true)
19+
{
20+
_wrapper = wrapper;
21+
SetHandle(wrapper?.ptr ?? IntPtr.Zero);
22+
}
23+
24+
public override bool IsInvalid => handle == IntPtr.Zero;
25+
26+
public new sqlite3 DangerousGetHandle()
27+
=> _wrapper!;
28+
29+
protected override bool ReleaseHandle()
30+
{
31+
var result = (Result)raw.sqlite3_close(_wrapper);
32+
return result == Result.OK;
33+
}
34+
}
35+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System;
8+
using System.Runtime.InteropServices;
9+
using Microsoft.CodeAnalysis.Shared.Extensions;
10+
using SQLitePCL;
11+
12+
namespace Microsoft.CodeAnalysis.SQLite.Interop
13+
{
14+
internal sealed class SafeSqliteStatementHandle : SafeHandle
15+
{
16+
private readonly sqlite3_stmt? _wrapper;
17+
private readonly SafeHandleLease _lease;
18+
19+
public SafeSqliteStatementHandle(SafeSqliteHandle sqliteHandle, sqlite3_stmt? wrapper)
20+
: base(invalidHandleValue: IntPtr.Zero, ownsHandle: true)
21+
{
22+
_wrapper = wrapper;
23+
SetHandle(wrapper?.ptr ?? IntPtr.Zero);
24+
_lease = sqliteHandle.Lease();
25+
}
26+
27+
public override bool IsInvalid => handle == IntPtr.Zero;
28+
29+
public new sqlite3_stmt DangerousGetHandle()
30+
=> _wrapper!;
31+
32+
protected override bool ReleaseHandle()
33+
{
34+
using var _ = _lease;
35+
var result = (Result)raw.sqlite3_finalize(_wrapper);
36+
return result == Result.OK;
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)