Clang Parser Oddities

Here are some issues I’ve run into when using the new clang parser.

Tested on IDA Version 9.3sp2 on Windows.
The database is a generic MSVC x86 executable.

For these examples I’m using the following clang options (default Windows 32bit preset):

-target i386-pc-win32

All other compiler settings are default. The snippets are parsed using Parse C Header File...

Member function pointer of the wrong size for the platform.

struct Foo
{
    void (Foo::*mptr)();
};

static_assert(sizeof(Foo) == 0x4, "Wrong size");

Expected: Size of Foo is 0x4
Actual: Size of Foo is 0x8

Note that the static_assert is not tripped in this case.

Class templates with nested scoped enumeration produces an empty type.

template <typename T>
struct Foo
{
    enum class Bar { a, b };
};

void Func(Foo<int>::Bar);

Expected: Foo<int>::Bar contains a & b.
Actual: Foo<int>::Bar is empty.

Note that using an unscoped enumeration produces the expected result.

The using directive does not update the resulting typedef if that typedef already exists.

To reproduce this either:

  • Create a typedef named MyType with some other type, then parse the snippet.
  • Parse the snippet once, change the type of MyType, parse the snipped again.
using MyType = int;

struct Foo
{
    MyType a;
};

Expected: MyType is redefined as a typedef to int.
Actual: MyType remains unchanged.

Forward declaration of a class type using an elaborated type specifier silently corrupts the vtable of that type when parsing the same input a second time.

void SomeFunc(class Foo*); // Evil forward declaration

class Base
{
    virtual void VFunc_01();
};

class Foo : public Base
{
    virtual void VFunc_01() override;
    virtual void VFunc_02();
};

class Bar : public Foo
{
    virtual void VFunc_01() override;
    virtual void VFunc_02() override;
};

Parsing this snipped produces the expected vtables at first, however if parsed again VFunc_02 is no longer present in both Foo and Bar.

I have also observed other effects, for instance if Base is forward declared instead of Foo in the snipped above the this pointer type of VFunc_01 changes from Foo/Bar to Base when parsed the second time.

Note that this does not occur when using the compiler option: No IDA specific extensions.

Hi @HSUnit

Thanks for the feedback! I’ll reply in order.

Member function pointer of the wrong size for the platform.

The size of the pointer is determined by the binary, not by the parser options. Can you check whether the app is 32-bit or 64-bit?

Class templates with nested scoped enumeration produces an empty type.

I created an internal ticket. We’re investigating this.

The using directive does not update the resulting typedef if that typedef already exists.

We’ll take care of that.

Forward declaration of a class type using an elaborated type specifier silently corrupts the vtable of that type when parsing the same input a second time.

How did you parse this input? Using Ctrl+F9, or “Insert/Edit Type”.

The executable is indeed 32-bit, and to be clear regular pointers and function pointers are the expected size. I parsed all the examples using Ctrl+F9.

The following have been already addressed an will be available in the next 9.4 beta release. If you’re on the beta testers list, you can even check them out (next beta expected soon).

  • The “using” directive does not update the resulting typedef if it already exists :white_check_mark:
  • class templates with nested scoped enumeration produces an empty type :white_check_mark:

Thanks again for coming up with clear examples, it helped us locate the issues more quickly.

Currently looking into this one: “Member function pointer of the wrong size for the platform.”. Just to clarify, I think the sizeof is compiler dependent. With MSVC, you’re getting sizeof(Foo) == 0x4, but the IDA uses a clang parser. The same code fails the assertion when compiling a 32-bit executable on my Mac:

$ clang++ -m32 -o x.o x.cpp
x.cpp:6:15: error: static assertion failed due to
      requirement 'sizeof(Foo) == 4': Wrong size
    6 | static_assert(sizeof(Foo) == 0x4, "Wrong size");
      |               ^~~~~~~~~~~~~~~~~~
x.cpp:6:27: note: expression evaluates to '8 == 4'
    6 | static_assert(sizeof(Foo) == 0x4, "Wrong size");
      |               ~~~~~~~~~~~~^~~~~~
1 error generated.

I personally don’t think there is much we can do about it, but I asked a colleague to try and reproduce it on Windows. If I’m not mistaken, clang creates an 8 bytes struct regardless of the OS.

As for the last one “Forward declaration of a class type using an elaborated type specifier silently corrupts the vtable of that type when parsing the same input a second time.”, I can’t promise something yet for 9.4. The reason this is tricky is because it seems to come from the llvm code used as the parser backend.

Follow-up on the sizeof(Foo) problem. On Windows, try adding these to “Options > Compiler options > Arguments”:

-m32 -target i686-pc-windows-msvc

It’s these arguments which no longer cause the assertion to trigger on Windows.

Or, even -target i686-pc-windows could do the trick.

EDIT: although this solution works with the native clang while compiling something, it might not work with the clang integrated in IDA, even on windows, so it might be again the way we’re wiring things internally :thinking:

EDIT 2: we have a fix for this in the next 9.4 beta