diff --git a/include/clang/Interpreter/CppInterOp.h b/include/clang/Interpreter/CppInterOp.h index dd05c6def..cfea4bcfb 100644 --- a/include/clang/Interpreter/CppInterOp.h +++ b/include/clang/Interpreter/CppInterOp.h @@ -446,7 +446,8 @@ namespace Cpp { /// Gets the address of the variable, you can use it to get the /// value stored in the variable. - CPPINTEROP_API intptr_t GetVariableOffset(TCppScope_t var); + CPPINTEROP_API intptr_t GetVariableOffset(TCppScope_t var, + TCppScope_t parent = nullptr); /// Checks if the provided variable is a 'Public' variable. CPPINTEROP_API bool IsPublicVariable(TCppScope_t var); diff --git a/lib/Interpreter/CppInterOp.cpp b/lib/Interpreter/CppInterOp.cpp index 111c3897c..417290c3f 100644 --- a/lib/Interpreter/CppInterOp.cpp +++ b/lib/Interpreter/CppInterOp.cpp @@ -35,6 +35,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/raw_os_ostream.h" +#include #include #include #include @@ -1222,20 +1223,22 @@ namespace Cpp { return 0; } - intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D) { + intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D, + CXXRecordDecl* BaseCXXRD) { if (!D) return 0; auto& C = I.getSema().getASTContext(); if (auto* FD = llvm::dyn_cast(D)) { - const clang::RecordDecl* RD = FD->getParent(); + clang::RecordDecl* FieldParentRecordDecl = FD->getParent(); intptr_t offset = C.toCharUnitsFromBits(C.getFieldOffset(FD)).getQuantity(); - while (RD->isAnonymousStructOrUnion()) { - const clang::RecordDecl* anon = RD; - RD = llvm::dyn_cast(anon->getParent()); - for (auto F = RD->field_begin(); F != RD->field_end(); ++F) { + while (FieldParentRecordDecl->isAnonymousStructOrUnion()) { + clang::RecordDecl* anon = FieldParentRecordDecl; + FieldParentRecordDecl = llvm::dyn_cast(anon->getParent()); + for (auto F = FieldParentRecordDecl->field_begin(); + F != FieldParentRecordDecl->field_end(); ++F) { const auto* RT = F->getType()->getAs(); if (!RT) continue; @@ -1246,6 +1249,46 @@ namespace Cpp { } offset += C.toCharUnitsFromBits(C.getFieldOffset(FD)).getQuantity(); } + if (BaseCXXRD && BaseCXXRD != FieldParentRecordDecl) { + // FieldDecl FD belongs to some class C, but the base class BaseCXXRD is + // not C. That means BaseCXXRD derives from C. Offset needs to be + // calculated for Derived class + + // Depth first Search is performed to the class that declears FD from + // the base class + std::vector stack; + std::map direction; + stack.push_back(BaseCXXRD); + while (!stack.empty()) { + CXXRecordDecl* RD = stack.back(); + stack.pop_back(); + size_t num_bases = GetNumBases(RD); + bool flag = false; + for (size_t i = 0; i < num_bases; i++) { + auto* CRD = static_cast(GetBaseClass(RD, i)); + direction[CRD] = RD; + if (CRD == FieldParentRecordDecl) { + flag = true; + break; + } + stack.push_back(CRD); + } + if (flag) + break; + } + if (auto* RD = llvm::dyn_cast(FieldParentRecordDecl)) { + // add in the offsets for the (multi level) base classes + while (BaseCXXRD != RD) { + CXXRecordDecl* Parent = direction.at(RD); + offset += C.getASTRecordLayout(Parent) + .getBaseClassOffset(RD) + .getQuantity(); + RD = Parent; + } + } else { + assert(false && "Unreachable"); + } + } return offset; } @@ -1321,9 +1364,11 @@ namespace Cpp { return 0; } - intptr_t GetVariableOffset(TCppScope_t var) { + intptr_t GetVariableOffset(TCppScope_t var, TCppScope_t parent) { auto* D = static_cast(var); - return GetVariableOffset(getInterp(), D); + auto* RD = + llvm::dyn_cast_or_null(static_cast(parent)); + return GetVariableOffset(getInterp(), D, RD); } // Check if the Access Specifier of the variable matches the provided value. diff --git a/unittests/CppInterOp/VariableReflectionTest.cpp b/unittests/CppInterOp/VariableReflectionTest.cpp index 10ab3a461..e913f38b8 100644 --- a/unittests/CppInterOp/VariableReflectionTest.cpp +++ b/unittests/CppInterOp/VariableReflectionTest.cpp @@ -7,6 +7,8 @@ #include "gtest/gtest.h" +#include + using namespace TestUtils; using namespace llvm; using namespace clang; @@ -258,6 +260,88 @@ TEST(VariableReflectionTest, GetVariableOffset) { EXPECT_TRUE((bool) Cpp::GetVariableOffset(VD_C_s_a)); } +#define CODE \ + class BaseA { \ + public: \ + virtual ~BaseA() {} \ + int a; \ + BaseA(int a) : a(a) {} \ + }; \ + \ + class BaseB : public BaseA { \ + public: \ + virtual ~BaseB() {} \ + std::string b; \ + BaseB(int x, std::string b) : BaseA(x), b(b) {} \ + }; \ + \ + class Base1 { \ + public: \ + virtual ~Base1() {} \ + int i; \ + std::string s; \ + Base1(int i, std::string s) : i(i), s(s) {} \ + }; \ + \ + class MyKlass : public BaseB, public Base1 { \ + public: \ + virtual ~MyKlass() {} \ + int k; \ + MyKlass(int k, int i, int x, std::string b, std::string s) \ + : BaseB(x, b), Base1(i, s), k(k) {} \ + } my_k(5, 4, 3, "Cpp", "Python"); + +CODE + +TEST(VariableReflectionTest, VariableOffsetsWithInheritance) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + + Cpp::Declare("#include"); + +#define Stringify(s) Stringifyx(s) +#define Stringifyx(...) #__VA_ARGS__ + Cpp::Declare(Stringify(CODE)); +#undef Stringifyx +#undef Stringify +#undef CODE + + Cpp::TCppScope_t myklass = Cpp::GetNamed("MyKlass"); + EXPECT_TRUE(myklass); + + size_t num_bases = Cpp::GetNumBases(myklass); + EXPECT_EQ(num_bases, 2); + + std::vector datamembers; + Cpp::GetDatamembers(myklass, datamembers); + for (size_t i = 0; i < num_bases; i++) { + Cpp::TCppScope_t base = Cpp::GetBaseClass(myklass, i); + EXPECT_TRUE(base); + for (size_t i = 0; i < Cpp::GetNumBases(base); i++) { + Cpp::TCppScope_t bbase = Cpp::GetBaseClass(base, i); + EXPECT_TRUE(base); + Cpp::GetDatamembers(bbase, datamembers); + } + Cpp::GetDatamembers(base, datamembers); + } + EXPECT_EQ(datamembers.size(), 5); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[0], myklass), + ((intptr_t)&(my_k.k)) - ((intptr_t)&(my_k))); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[1], myklass), + ((intptr_t)&(my_k.a)) - ((intptr_t)&(my_k))); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[2], myklass), + ((intptr_t)&(my_k.b)) - ((intptr_t)&(my_k))); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[3], myklass), + ((intptr_t)&(my_k.i)) - ((intptr_t)&(my_k))); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[4], myklass), + ((intptr_t)&(my_k.s)) - ((intptr_t)&(my_k))); +} + TEST(VariableReflectionTest, IsPublicVariable) { std::vector Decls, SubDecls; std::string code = R"(