0

我有一个小项目(大约 2k 行代码),它可以使用 clang 和 gcc 进行编译。gcc 给出以下错误(-O0 优化级别):

/home/nikita/projects/curse_dim/bellman/include/bellman/bellman_operators/qfunc.hpp:10:7: runtime error: member call on address 0x7fff96470b80 which does not point to an object of type 'ICloneable'
0x7fff96470b80: note: object has invalid vptr
 fc fd fe ff  00 00 00 00 00 00 00 00  c0 16 00 00 20 61 00 00  00 18 00 00 20 61 00 00  00 18 00 00
              ^~~~~~~~~~~~~~~~~~~~~~~
              invalid vptr

它由一段简单的代码触发:

class DiscreteQFuncEst final : public EnableClone<DiscreteQFuncEst, InheritFrom<IQFuncEstimate>> {
public:
    DiscreteQFuncEst() = default;
    ...
protected:
    std::vector<FloatT> values_;
    size_t num_actions_;
    std::optional<ParticleCluster> particle_cluster_;
};

#include <bellman/bellman_operators/uniform_operator.hpp>

int main() {
    DiscreteQFuncEst est;
    static_assert(std::is_convertible_v<DiscreteQFuncEst*, ICloneable*>);
    bool should_be_true = dynamic_cast<ICloneable*>(&est) != 0; // <- UBSan is mad here
    bool has_failed = !should_be_true;
    return has_failed;
}

clang++ 也给出了“成员调用......”,但在不同的代码段上。

我无法在一个简短的示例中重现它。EnableClone几乎是独立的——我尝试使用 full 创建一个最小示例cloneable.hpp,但无法以这种方式重现 UBSan 错误。

我怀疑 ODR 是这种情况,所以我添加-Wodr了编译标志,但它什么也没给我。

我尝试使用 gdb 来检查 vptrs,但我不完全确定,但它们似乎没问题:

(gdb) p est
$1 = {<EnableClone<DiscreteQFuncEst, InheritFrom<IQFuncEstimate> >> = {<_CloneableImpl<DiscreteQFuncEst, InheritFrom<IQFuncEstimate>, false, true>> = {<InheritFrom<IQFuncEstimate>> = {<IQFuncEstimate> = {<EnableCloneInterface<IQFuncEstimate, void>> = {<_CloneableImpl<IQFuncEstimate, void, true, false>> = {<ICloneable> = {
                _vptr.ICloneable = 0x555555b20b18 <vtable for DiscreteQFuncEst+40>}, <No data fields>}, <No data fields>}, <No data fields>}, <No data fields>}, <No data fields>}, <No data fields>}, values_ = std::vector of length 0, capacity 0, num_actions_ = 0, 
  particle_cluster_ = std::optional<ParticleCluster> [no contained value]}
(gdb) p est.~ICloneable 
$2 = {void (ICloneable * const, int)} 0x555555914644 <ICloneable::~ICloneable()>
(gdb) p ICloneable::~ICloneable
$3 = {void (ICloneable * const)} 0x555555914644 <ICloneable::~ICloneable()>

EnableClone实现:https ://github.com/npetrenko/curse_dim/blob/4c80eccf637bf351ef708c04c432a5c18e41dcfd/bellman/include/bellman/cloneable.hpp

如果我向 ICloneable 添加一个具有副作用的析构函数

virtual ~ICloneable() {
    std::cerr << "Destructor works!\n";
}

然后DiscreteQFuncEst在 gcc 的 UBSan 抱怨的示例中打印预期的破坏结果(-O0,并且 ICloneable 是一个虚拟基础,所以我希望虚拟 ptr 遍历没有被优化)。

我发现了几个报告误报结果的错误报告,例如: https ://bugs.llvm.org/show_bug.cgi?id=39191

不过,与报告不同的是,我不会弄乱可见性,而且我编写的所有库都是静态链接的。

这是假阳性吗?如果没有,我怎么可能调试这个?还是我能以某种方式确保自己确实是 UB?

谢谢!

重现步骤:

git clone --recursive https://github.com/npetrenko/curse_dim/ && cd curse_dim && git checkout ub_reproduce

并查看 README.md

更新:我设法将它剥离到几乎为零。我不知道为什么我以前没有成功。签出 repo 可能更方便,我只是从那里复制代码。

造成麻烦的类:

#pragma once

#include <type_traits>

class ICloneable {
public:
    virtual ~ICloneable() = default;
};

template <class T>
struct InheritFrom : public T {
    using T::T;
};

template <class Derived, class AnotherBase, bool derived_is_abstract,
          bool base_is_cloneable = std::is_base_of_v<ICloneable, AnotherBase>>
class _CloneableImpl;

// three identical implementations, only the inheritance is different

#define Implement(IsAbstract)                                                                     \
    /* "no base is defined" case*/                                                                \
    template <class Derived>                                                                      \
    class _CloneableImpl<Derived, void, IsAbstract, false> : public virtual ICloneable {          \
    };                                                                                            \
                                                                                                  \
    /* Base is defined, and already provides ICloneable*/                                         \
    template <class Derived, class AnotherBase>                                                   \
    class _CloneableImpl<Derived, AnotherBase, IsAbstract, true> : public AnotherBase {           \
    };                                                                                            \
                                                                                                  \
    /* Base is defined, but has no ICloneable*/                                                   \
    template <class Derived, class AnotherBase>                                                   \
    class _CloneableImpl<Derived, AnotherBase, IsAbstract, false> : public AnotherBase,           \
                                                                    public virtual ICloneable {   \
    };

Implement(false) 
Implement(true)

#undef Implement

template <class Derived, class AnotherBase = void>
class EnableClone : public _CloneableImpl<Derived, AnotherBase, false> {
};

template <class Derived, class AnotherBase = void>
class EnableCloneInterface : public _CloneableImpl<Derived, AnotherBase, true> {
};

UBSan 错误触发代码示例:

#include "cloneable.hpp"

class IQFuncEstimate : public EnableCloneInterface<IQFuncEstimate> {
public:
    virtual ~IQFuncEstimate() = default;
};

class DiscreteQFuncEst final : public EnableClone<DiscreteQFuncEst, InheritFrom<IQFuncEstimate>> {
};

int main() {
    DiscreteQFuncEst est;
    static_assert(std::is_convertible_v<DiscreteQFuncEst*, ICloneable*>);
    bool should_be_true = dynamic_cast<ICloneable*>(&est) != 0;
    bool has_failed = !should_be_true;
    return has_failed;
}

编译:

g++ -I../include -Wall -Wextra -Wpedantic -std=c++17 -fsanitize=address,undefined -fno-sanitize-recover=all ../ub_example/ub_example.cpp

(替换g++clang++让 UBSan 开心)

g++ (GCC) 9.1.0

铿锵声版本 8.0.1

4

0 回答 0