Using Unmanaged creates a dangling pointer?

I'm using Unmanaged to create a C accessible library in Swift. Swift exposes two functions to C, to create and destroy an object. The Object is defined as an opaque pointer in C:

@_cdecl("ObjectCreate")
public func ObjectCreate() -> OpaquePointer
{
    let retained = Unmanaged.passRetained(MyClass()).toOpaque();
    return OpaquePointer(retained);
}

@_cdecl("ObjectRelease")
public func ObjectRelease(pointer: OpaquePointer?) -> Void
{
    Unmanaged<MyClass>.fromOpaque(UnsafeRawPointer(pointer!)).release();
    // Also tried takeRetainedValue()
}

How am I not getting a segmentation fault when executing the following C code:

typedef struct Object *ObjectRef;

int
main()
{
	ObjectRef test = ObjectCreate();
	ObjectRelease(test);

	printf("%u\n", *(int*)(test)); // How is this legal?
    int *intPtr = (int*)(test);
    *intPtr = 123; // How is this also legal?

	return 0;
}

Note that I'm using the Embedded flag when compiling.

The only reason you'd get a segmentation fault is if test was pointing into unmapped memory or memory that your process didn't own. If you did the same thing with malloc and free you still probably wouldn't get a segfault.

1 Like

No, I tried using malloc and free and it indeed gives a segmentation fault.

It doesn't have to:

It's entirely coincidental whether it will or not on any given environment. The fact that it rarely causes something as obvious as a segfault is why use after free is such a pernicious bug.

2 Likes

Is there anything I can do in the Swift side to make sure the pointer becomes invalid ?

The only thing I can think of off the top of my head is that ObjectFree can take a pointer to the pointer and reset it to nil after you free it.

@_cdecl("ObjectRelease")
public func ObjectRelease(pointer: inout OpaquePointer?) -> Void
{
    Unmanaged<MyClass>.fromOpaque(UnsafeRawPointer(pointer!)).release();
    pointer = nil
    // Also tried takeRetainedValue()
}
typedef struct Object *ObjectRef;

int
main()
{
	ObjectRef test = ObjectCreate();
	ObjectRelease(&test);

	printf("%u\n", *(int*)(test)); // How is this legal?
    int *intPtr = (int*)(test);
    *intPtr = 123; // How is this also legal?

	return 0;
}

Apparently you can't have inout parameters with @_cdecl, I tried the consuming keyword but it didn't change the actual pointer value any help?

Does this work?

public func ObjectRelease(pointer: UnsafePointer<OpaquePointer?>?) -> Void {
    guard let pointer else { return }
    Unmanaged<MyClass>.fromOpaque(UnsafeRawPointer(pointer.pointee!)).release()
    pointer.pointee = nil
}

I get the following error:
error: cannot assign to property: 'pointee' is a get-only property

works when I change the type to UnsafeMutablePointer

Yeah, I got a little C brained there. Mutability is explicit.

You need to make that UnsafeMutablePointer (instead of UnsafePointer).

[Edit: removed reference to guard var as mutable pointers have nonmutating set]