vendredi 14 décembre 2018

Static allocation and placement new result in null pointer dereference

I am working on an embedded platform where heap allocation is discouraged. I also have circular dependencies during construction. Given these constraints my team designed a static allocator class which is used to allocate memory in the .bss section and then construct the object in a deferred fashion.

The issue we face is during the deferred construction the compiler generated code tries to reference data in the statically allocated memory that hasn't been constructed yet - the data are zero on our platform when unconstructed - which causes a null pointer dereference crashing the system.

The crashes can be resolved by reordering the construction order of the classes. Unfortunately I haven't been able to create a minimum reproduction of the issue. Additionally the problem gets worse and harder to manage when virtual inheritance is involved.

We have experienced the issue targeting armclang and visual studio compilers so it seems like we are likely doing something out of the C++ specification.

Static Allocator Code:

template <class UnderlyingType, typename... Args>
class StaticAllocator
{
private:
    typedef std::uint64_t BaseDataType;

    // Define a tuple of the variadic template parameters with the references removed
    using TupleWithRefsRemoved = std::tuple<typename std::remove_reference<Args>::type...>;

    // A function that strips return the ref-less template arguments
    template <typename... T>
    TupleWithRefsRemoved removeRefsFromTupleMembers(std::tuple<T...> const& t)
    {
        return TupleWithRefsRemoved{ t };
    }

public:
    StaticAllocator()
    {
        const auto ptr = reinterpret_cast<UnderlyingType *>(&m_underlyingData);
        assert(ptr != nullptr);
    }

    virtual StaticAllocator* clone() const
    {
        return new StaticAllocator<UnderlyingType, Args...>(*this);
    }

    UnderlyingType *getPtr()
    {
        return reinterpret_cast<UnderlyingType *>(&m_underlyingData);
    }

    const UnderlyingType *getPtr() const
    {
        return reinterpret_cast<const UnderlyingType *>(&m_underlyingData);
    }

    UnderlyingType *operator->()
    {
        return getPtr();
    }

    const UnderlyingType *operator->() const
    {
        return getPtr();
    }

    UnderlyingType &operator*()
    {
        return *getPtr();
    }

    const UnderlyingType &operator*() const
    {
        return *getPtr();
    }

    operator UnderlyingType *()
    {
        return getPtr();
    }

    operator const UnderlyingType *() const
    {
        return getPtr();
    }

    void construct(Args... args)
    {
        _construct(TupleWithRefsRemoved(args...), std::index_sequence_for<Args...>());
    }

    void destroy()
    {
        const auto ptr = getPtr();
        if (ptr != nullptr)
        {
            ptr->~T();
        }
    }

private:
    BaseDataType m_underlyingData[(sizeof(UnderlyingType) + sizeof(BaseDataType) - 1) / sizeof(BaseDataType)];

    // A function that unpacks the tuple of arguments, and constructs them
    template <std::size_t... T>
    void _construct(const std::tuple<Args...>& args, std::index_sequence<T...>)
    {
        new (m_underlyingData) UnderlyingType(std::get<T>(args)...);
    }
};

Simple Usage Example:

class InterfaceA
{
    // Interface functions here
}

class InterfaceB
{
    // Interface functions here
}


class ObjectA : public virtual InterfaceA
{
public:
    ObjectA(InterfaceB* intrB) : m_intrB(intrB) {}

private:
    InterfaceB* m_intrB;
};

class ObjectB : public virtual InterfaceB
{
public:
    ObjectB(InterfaceA* intrA) : m_intrA(intrA) {}

private:
    InterfaceA* m_intrA;
}

StaticAllocator<ObjectA, InterfaceB*> objectAStorage;
StaticAllocator<ObjectB, InterfaceA*> objectBStorage;

// Crashes happen in this function, there are many more objects in our real
// system and the order of the objects effects if the crash occurs.
void initialize_objects()
{
    auto objA = objectAStorage.getPtr();
    auto objB = objectBStorage.getPtr();

    objectAStorage.construct(objB);
    objectBStorage.construct(objA);
}

Aucun commentaire:

Enregistrer un commentaire