Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using std::type_info for casting in natvis

In the codebase I'm working, we use std::any instead of void* to pass classes through some generic non-template code. Specifically, we use Visual Studio 2019, its compiler and standard library.

In order to visualize the std::any, microsoft already gives a natvis:

  <Type Name="std::any">
      <Intrinsic Name="has_value"   Expression="_Storage._TypeData != 0"/>
      <Intrinsic Name="_Rep"        Expression="_Storage._TypeData &amp; _Rep_mask"/>
      <Intrinsic Name="type"        Expression="(const type_info*)(_Storage._TypeData &amp; ~_Rep_mask)"/>
      <Intrinsic Name="_Is_trivial" Expression="has_value() &amp;&amp; _Rep() == 0"/>
      <Intrinsic Name="_Is_big"     Expression="has_value() &amp;&amp; _Rep() == 1"/>
      <Intrinsic Name="_Is_small"   Expression="has_value() &amp;&amp; _Rep() == 2"/>
      <DisplayString Condition="!has_value()">[empty]</DisplayString>
      <DisplayString Condition="_Is_trivial() || _Is_small()">[not empty (Small)]</DisplayString>
      <DisplayString Condition="_Is_big()">[not empty (Large)]</DisplayString>
      <Expand>
          <Synthetic Name="has_value">
              <DisplayString>{has_value()}</DisplayString>
          </Synthetic>
          <Synthetic Name="type" Condition="has_value()">
              <DisplayString>{type()}</DisplayString>
          </Synthetic>
          <Synthetic Name="[representation]" Condition="_Is_trivial()">
              <DisplayString>(Small/Trivial Object)</DisplayString>
          </Synthetic>
          <Synthetic Name="[representation]" Condition="_Is_small()">
              <DisplayString>(Small Object)</DisplayString>
          </Synthetic>
          <Synthetic Name="[representation]" Condition="_Is_big()">
              <DisplayString>(Dynamic Allocation)</DisplayString>
          </Synthetic>
      </Expand>
  </Type>

However, this ends up showing us (Small Object) instead of the std::string that we have stored into it. I've already managed to extend this with a few extra lines to get the pointer to the data:

          <Item Name="[castable_ptr]" Condition="_Is_trivial()">(void*)(&amp;_Storage._TrivialData)</Item>
          <Item Name="[castable_ptr]" Condition="_Is_small()">(void*)(&amp;_Storage._SmallStorage._Data)</Item>
          <Item Name="[castable_ptr]" Condition="_Is_big()">(void*)(_Storage._BigStorage._Ptr)</Item>

However, this shows the data as a void*, which you have to manually cast to a pointer of the actual type std::string*. However, this std::any implementation/visualization also comes with a std::type_info. (see field: type) that knows which underlying type we have.

Is there a way to use this std::type_info so that the (void*) can be replaced by a cast to the actual stored type?

EDIT: An example of the information that visual studio provides for the type: {mydll.dll!class std::tuple<__int64,double,double,double> 'RTTI Type Descriptor'} {...} When explicitly casting the address to std::type_info*, I get access to _Data in the debugger, that contains _DecoratedName (.?AV?$tuple@_JNNN@std@@) and _UndecoratedName (nullptr). Unfortunately, I can't seem to find out how to write a cast that leverages this information.

like image 385
JVApen Avatar asked Dec 11 '19 12:12

JVApen


1 Answers

I was not aware of the context (implementation constraints). If using std::any is identified as a strong prerequisite (which I can understand) and before Microsoft improves the native vizualizer, below could be a tactical quick win.

In the natvis xml configuration file:

    <Synthetic Name="[representation]" Condition="_Is_trivial()">
        <!--<DisplayString>(Small/Trivial Object)</DisplayString>-->
        <DisplayString>{_Storage._TrivialData._Val}</DisplayString>
    </Synthetic>
    <Synthetic Name="[representation]" Condition="_Is_small()">
        <!--<DisplayString>{*displayStdAnyContent(*this)}</DisplayString>-->
        <Expand>
            <Item Name="[value]">*displayStdAnyContent(*this)</Item>
        </Expand>
    </Synthetic>
    
    <Type Name="AnyCastedValue&lt;*&gt;">
        <DisplayString>{value}</DisplayString>
        <Expand>
            <Item Name="[value]">value</Item>
        </Expand>
    </Type>

C++

//----------------------------------------------------------------------------------
class IAnyValue
{
public:
    virtual ~IAnyValue() = default;
};

template <typename T>
struct AnyCastedValue final :public IAnyValue
{
    AnyCastedValue(const T& pi_value) :value(pi_value) {}

    T value;
};

struct NonCastableValue final :public IAnyValue
{
    static constexpr auto message = "I'm not castable! Please register my type!";
};

//----------------------------------------------------------------------------------
class IAnyCaster
{
public:
    virtual std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const = 0;
    virtual ~IAnyCaster() = default;
};

template <typename T>
class AnyCaster final :public IAnyCaster
{
public:
    AnyCaster() = default;

    virtual std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const override
    {
        return std::make_unique<AnyCastedValue<T>>(std::any_cast<T>(pi_any));
    }
};
    
//----------------------------------------------------------------------------------
class AnyCasterService final :public std::unordered_map<std::string, std::unique_ptr<IAnyCaster>>
{
public:
    AnyCasterService()
    {
        //here we register the types you would like be able to watch under the debugger
        registerType<int>();
        registerType<std::string>();
    }

    std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const
    {
        auto im = find(pi_any.type().name());
        if (im != end())
        {
            return im->second->castValue(pi_any);
        }
        else return std::make_unique<NonCastableValue>();
    }

private:
    template <typename T>
    void registerType()
    {
        this->insert(std::make_pair(typeid(T).name(),std::make_unique<AnyCaster<T>>()));
    }
};

//----------------------------------------------------------------------------------
std::unique_ptr<IAnyValue> displayStdAnyContent(const std::any& pi_any)
{   
    static const AnyCasterService s_service;
    return s_service.castValue(pi_any);
}

I validated this proposal under VS2017 v15.9.8

Keep in mind that Natvis could only evaluate the method at the runtime on your demand (explicit user call).

std::any l_any;
l_any = 5;
l_any = std::string("it works!");

watch

like image 96
Jeandey Boris Avatar answered Nov 02 '22 13:11

Jeandey Boris