Safe References API

API Reference


The term "reference" is used to mean "opaque data that refers to some conceptual object". It is intentionally vague to support "information hiding". Behind the scenes, different implementations can use almost anything that fits into a pointer as a "reference". Often, they are indexes into arrays or actual pointers to memory objects. When passing those references through an API to outside clients, the implementation becomes exposed to crash bugs when clients pass those references back into the API damaged or stale ("stale" meaning something that has been deleted).

Safe References are designed to help protect against damaged or stale references being used by clients.

Create Safe Reference

Client calls an API's "Create" function:

  • "Create" function creates an object.
  • "Create" function creates a "Safe Reference" for the new object le_ref_CreateRef()
  • "Create" function returns the Safe Reference.

Lookup Pointer

Followed by:

Client calls another API function, passing in the Safe Reference:

  • API function translates the Safe Reference back into an object pointer le_ref_Lookup()
  • API function acts on the object.

Delete Safe Reference

Finishing with:

Client calls API's "Delete" function, passing in the Safe Reference:

  • "Delete" function translates the Safe Reference back into a pointer to its object.
  • "Delete" function invalidates the Safe Reference le_ref_DeleteRef()
  • "Delete" function deletes the object.

At this point, if the Client calls an API function and passes that same (now invalid) Safe Reference (or if the client accidentally passes in some garbage value, like a pointer or zero), the API function will try to translate that into an object pointer. But it'll be told that it's an invalid Safe Reference. The API function can then handle it gracefully, rather than just acting as if it were a valid reference and clobbering the object's deallocated memory or some other object that's reusing the old object's memory.

Create Reference Map

A Reference Map object can be used to create Safe References and keep track of the mappings from Safe References to pointers. At start-up, a Reference Map can be created dynamically by calling le_ref_CreateMap(), or can be allocated statically at compile time via LE_REF_DEFINE_STATIC_MAP() and initialized via le_ref_InitStaticMap().

Multithreading

This API's functions are reentrant, but not thread safe. If there's the slightest possibility the same Reference Map will be accessed by two threads at the same time, use a mutex or some other thread synchronization mechanism to protect the Reference Map from concurrent access.

Sample Code

Here's an API Definition sample:

// Opaque reference to Foo objects.
typedef struct xyz_foo_Obj* xyz_foo_ObjRef_t;
 
xyz_foo_Ref_t xyz_foo_CreateObject
(
void
);
 
void xyz_foo_DoSomething
(
xyz_foo_Ref_t objRef
);
 
void xyz_foo_DeleteObject
(
xyz_foo_Ref_t objRef
);

Here's an API Implementation sample:

// Maximum number of Foo objects we expect to have at one time.
#define MAX_FOO_OBJECTS 27
 
// Actual Foo objects.
typedef struct
{
...
}
Foo_t;
 
// Pool from which Foo objects are allocated.
le_mem_PoolRef_t FooPool;
 
// Safe Reference Map for Foo objects.
le_ref_MapRef_t FooRefMap;
 
{
// Create the Foo object pool.
FooPool = le_mem_CreatePool("FooPool", sizeof(Foo_t));
le_mem_ExpandPool(FooPool, MAX_FOO_OBJECTS);
 
// Create the Safe Reference Map to use for Foo object Safe References.
FooRefMap = le_ref_CreateMap("FooMap", MAX_FOO_OBJECTS);
};
 
xyz_foo_Ref_t xyz_foo_CreateObject
(
void
)
{
Foo_t* fooPtr = le_mem_ForceAlloc(FooPool);
 
// Initialize the new Foo object.
...
 
// Create and return a Safe Reference for this Foo object.
return le_ref_CreateRef(FooRefMap, fooPtr);
}
 
void xyz_foo_DoSomething
(
xyz_foo_Ref_t objRef
)
{
Foo_t* fooPtr = le_ref_Lookup(FooRefMap, objRef);
 
if (fooPtr == NULL)
{
LE_CRIT("Invalid reference (%p) provided!", objRef);
return;
}
 
// Do something to the object.
...
}
 
void xyz_foo_DeleteObject
(
xyz_foo_Ref_t objRef
)
{
Foo_t* fooPtr = le_ref_Lookup(FooRefMap, objRef);
 
if (fooPtr == NULL)
{
LE_CRIT("Invalid reference (%p) provided!", objRef);
return;
}
 
// Invalidate the Safe Reference.
le_ref_DeleteRef(FooRefMap, objRef);
 
// Release the Foo object.
le_mem_Release(fooPtr);
}