It sounds obvious, but sometimes it needs to be stated. For instance, let's say that you are allocating your own IRP, your context contains I/O related data (like a URB) and you encounter the issue where the DeviceObject passed to your I/O completion routine is NULL. Adding another stack location is one solution I wrote about, but it is a little complicated and has a certain black magic feeling to it, besides it does not work with any IoBuildXxx API calls. Since you are allocating memory for your context, just allocate a little bit more. Instead of allocating just the URB

PURB urb = ExAllocatePoolWithTag(NonPagedPool, sizeof(URB), ...);
PIRP irp = IoAllocateIrp(...);
...
// format the URB
IoSetCompletionRoutine(Irp, CompletionRoutine, urb, TRUE, TRUE, TRUE);
...
IoCallDriver(..., Irp);
...

NTSTATUS CompletionRoutine(PDEVIC_OBJECT Device, PIRP Irp, PURB Urb)
{
     // Do not touch Device, it is NULL!
     // Process Urb and Irp results
     ExFreePool(Urb);
     IoFreeIrp(Irp);

     return STATUS_MORE_PROCESSING_REQUIRED;
}

allocate a structure to contain the URB and your DeviceObject pointer (or WDFDEVICE or anything else)

typedef struct _COMPLETION_DATA {
    PDEVICE_OBJECT DeviceObject;
    URB Urb;
} COMPLETION_DATA, *PCOMPLETION_DATA;

PCOMPLETION_DATA data = ExAllocatePoolWithTag(NonPagedPool, sizeof(COMPLETION_DATA), ...);
PIRP irp = IoAllocateIrp(...);
...
// format data->Urb
data->DeviceObject = DeviceObject;
IoSetCompletionRoutine(Irp, CompletionRoutine, data, TRUE, TRUE, TRUE);
...
IoCallDriver(..., Irp);
...

NTSTATUS CompletionRoutine(PDEVIC_OBJECT Device, PIRP Irp, PCOMPLETION_DATA Data)
{
     // Do not touch Device, it is NULL, but Data->Device is valid!
     
     // Process Data->Urb and Irp results
     ExFreePool(Data);
     IoFreeIrp(Irp);

     return STATUS_MORE_PROCESSING_REQUIRED;
}

Like I said, it is an obvious thing to do :), but when you are in the depths of debugging a bugcheck, sometimes the obvious solution is the last thing you think of.