Skip to content

Gendarme.Rules.BadPractice.PreferSafeHandleRule(2.10)

Sebastien Pouliot edited this page Feb 9, 2011 · 3 revisions

PreferSafeHandleRule

Assembly: Gendarme.Rules.BadPractice
Version: 2.10

Description

In general it is best to interop with native code using System.Runtime.InteropServices.SafeHandle instead of System.IntPtr or System.UIntPtr because:

  • SafeHandles are type safe.
  • SafeHandles are guaranteed to be disposed of during exceptional conditions like a thread aborting unexpectedly or a stack overflow.
  • SafeHandles are not vulnerable to reycle attacks.
  • You don't need to write a finalizer which can be tricky to do because they execute within their own thread, may execute on partially constructed objects, and normally tear down the application if you allow an exception to escape from them.

Examples

Bad example:

using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
// If cleaning up the native resource in a timely manner is important you can
// implement IDisposable.
public sealed class Database {
    ~Database ()
    {
        // This will execute even if the ctor throws so it is important to check
        // to see if the fields are initialized.
        if (m_database != IntPtr.Zero) {
            NativeMethods.sqlite3_close (m_database);
        }
    }
    public Database (string path)
    {
        NativeMethods.OpenFlags flags = NativeMethods.OpenFlags.READWRITE | NativeMethods.OpenFlags.CREATE;
        int err = NativeMethods.sqlite3_open_v2 (path, out m_database, flags, IntPtr.Zero);
        // handle errors
    }
    // exec and query methods would go here
    [SuppressUnmanagedCodeSecurity]
    private static class NativeMethods {
        [Flags]
        public enum OpenFlags : int {
            READONLY = 0x00000001,
            READWRITE = 0x00000002,
            CREATE = 0x00000004,
            // ...
        }
        [DllImport ("sqlite3")]
        public static extern int sqlite3_close (IntPtr db);
        [DllImport ("sqlite3")]
        public static extern int sqlite3_open_v2 (string fileName, out IntPtr db, OpenFlags flags, IntPtr module);
    }
    private IntPtr m_database;
}

Good example:

using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
// If cleaning up the native resource in a timely manner is important you can
// implement IDisposable, but you do not need to implement a finalizer because
// SafeHandle will take care of the cleanup.
internal sealed class Database {
    public Database (string path)
    {
        NativeMethods.OpenFlags flags = NativeMethods.OpenFlags.READWRITE | NativeMethods.OpenFlags.CREATE;
        m_database = new SqlitePtr (path, flags);
    }
    // exec and query methods would go here
    // This corresponds to a native sqlite3*.
    [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
    [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
    private sealed class SqlitePtr : SafeHandle {
        public SqlitePtr (string path, NativeMethods.OpenFlags flags) : base (IntPtr.Zero, true)
        {
            int err = NativeMethods.sqlite3_open_v2 (path, out handle, flags, IntPtr.Zero);
            // handle errors
        }
        public override bool IsInvalid {
            get {
                return (handle == IntPtr.Zero);
            }
        }
        // This will not be called if the handle is invalid. Note that this method should not throw.
        [ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
        protected override bool ReleaseHandle ()
        {
            NativeMethods.sqlite3_close (this);
            return true;
        }
    }
    [SuppressUnmanagedCodeSecurity]
    private static class NativeMethods {
        [Flags]
        public enum OpenFlags : int {
            READONLY = 0x00000001,
            READWRITE = 0x00000002,
            CREATE = 0x00000004,
            // ...
        }
        [DllImport ("sqlite3")]
        public static extern int sqlite3_close (SqlitePtr db);
        // Open must take an IntPtr but all other methods take a type safe SqlitePtr.
        [DllImport ("sqlite3")]
        public static extern int sqlite3_open_v2 (string fileName, out IntPtr db, OpenFlags flags, IntPtr module);
    }
    private SqlitePtr m_database;
}

Notes

  • This rule is available since Gendarme 2.6
Clone this wiki locally