我有一个小项目(约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
几乎是独立的-我尝试使用完整的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
更新:我设法将其剥离几乎为零。我不知道为什么我以前没有成功。签出仓库可能更方便,我只是从那里复制代码。
造成麻烦的类:
#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
clang版本8.0.1