mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-22 18:54:02 +01:00
[CodeView] Add support for random access type visitors.
Previously type visitation was done strictly sequentially, and TypeIndexes were computed by incrementing the TypeIndex of the last visited record. This works fine for situations like dumping, but not when you want to visit types in random order. For example, in a debug session someone might lookup a symbol by name, find that it has TypeIndex 10,000 and then want to go straight to TypeIndex 10,000. In order to make this work, the visitation framework needs a mode where it can plumb TypeIndices through the callback pipeline. This patch adds such a mode. In doing so, it is necessary to provide an alternative implementation of TypeDatabase that supports random access, so that is done as well. Nothing actually uses these random access capabilities yet, but this will be done in subsequent patches. Differential Revision: https://reviews.llvm.org/D32928 llvm-svn: 302454
This commit is contained in:
parent
e86bed8cbe
commit
b78cfee1e6
@ -26,6 +26,7 @@ public:
|
||||
|
||||
void addTypeServerHandler(TypeServerHandler &Handler);
|
||||
|
||||
Error visitTypeRecord(CVType &Record, TypeIndex Index);
|
||||
Error visitTypeRecord(CVType &Record);
|
||||
Error visitMemberRecord(CVMemberRecord &Record);
|
||||
|
||||
@ -37,6 +38,9 @@ public:
|
||||
Error visitFieldListMemberStream(BinaryStreamReader Reader);
|
||||
|
||||
private:
|
||||
Expected<bool> handleTypeServer(CVType &Record);
|
||||
Error finishVisitation(CVType &Record);
|
||||
|
||||
/// The interface to the class that gets notified of each visitation.
|
||||
TypeVisitorCallbacks &Callbacks;
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#ifndef LLVM_DEBUGINFO_CODEVIEW_TYPEDATABASE_H
|
||||
#define LLVM_DEBUGINFO_CODEVIEW_TYPEDATABASE_H
|
||||
|
||||
#include "llvm/ADT/BitVector.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/DebugInfo/CodeView/TypeIndex.h"
|
||||
@ -20,6 +21,8 @@
|
||||
namespace llvm {
|
||||
namespace codeview {
|
||||
class TypeDatabase {
|
||||
friend class RandomAccessTypeVisitor;
|
||||
|
||||
public:
|
||||
explicit TypeDatabase(uint32_t ExpectedSize);
|
||||
|
||||
@ -41,7 +44,9 @@ public:
|
||||
|
||||
uint32_t size() const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
uint32_t toArrayIndex(TypeIndex Index) const;
|
||||
|
||||
BumpPtrAllocator Allocator;
|
||||
|
||||
/// All user defined type records in .debug$T live in here. Type indices
|
||||
@ -52,6 +57,28 @@ private:
|
||||
|
||||
StringSaver TypeNameStorage;
|
||||
};
|
||||
|
||||
class RandomAccessTypeDatabase : private TypeDatabase {
|
||||
public:
|
||||
explicit RandomAccessTypeDatabase(uint32_t ExpectedSize);
|
||||
|
||||
/// Records the name of a type, and reserves its type index.
|
||||
void recordType(StringRef Name, TypeIndex Index, const CVType &Data);
|
||||
|
||||
using TypeDatabase::saveTypeName;
|
||||
|
||||
StringRef getTypeName(TypeIndex Index) const;
|
||||
|
||||
const CVType &getTypeRecord(TypeIndex Index) const;
|
||||
CVType &getTypeRecord(TypeIndex Index);
|
||||
|
||||
bool containsTypeIndex(TypeIndex Index) const;
|
||||
|
||||
uint32_t size() const;
|
||||
|
||||
private:
|
||||
BitVector ValidRecords;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,8 @@
|
||||
#ifndef LLVM_DEBUGINFO_CODEVIEW_TYPEDATABASEVISITOR_H
|
||||
#define LLVM_DEBUGINFO_CODEVIEW_TYPEDATABASEVISITOR_H
|
||||
|
||||
#include "llvm/ADT/PointerUnion.h"
|
||||
|
||||
#include "llvm/DebugInfo/CodeView/TypeDatabase.h"
|
||||
#include "llvm/DebugInfo/CodeView/TypeIndex.h"
|
||||
#include "llvm/DebugInfo/CodeView/TypeRecord.h"
|
||||
@ -21,11 +23,14 @@ namespace codeview {
|
||||
/// Dumper for CodeView type streams found in COFF object files and PDB files.
|
||||
class TypeDatabaseVisitor : public TypeVisitorCallbacks {
|
||||
public:
|
||||
explicit TypeDatabaseVisitor(TypeDatabase &TypeDB) : TypeDB(TypeDB) {}
|
||||
explicit TypeDatabaseVisitor(TypeDatabase &TypeDB) : TypeDB(&TypeDB) {}
|
||||
explicit TypeDatabaseVisitor(RandomAccessTypeDatabase &TypeDB)
|
||||
: TypeDB(&TypeDB) {}
|
||||
|
||||
/// Paired begin/end actions for all types. Receives all record data,
|
||||
/// including the fixed-length record prefix.
|
||||
Error visitTypeBegin(CVType &Record) override;
|
||||
Error visitTypeBegin(CVType &Record, TypeIndex Index) override;
|
||||
Error visitTypeEnd(CVType &Record) override;
|
||||
Error visitMemberBegin(CVMemberRecord &Record) override;
|
||||
Error visitMemberEnd(CVMemberRecord &Record) override;
|
||||
@ -39,12 +44,18 @@ public:
|
||||
#include "TypeRecords.def"
|
||||
|
||||
private:
|
||||
StringRef getTypeName(TypeIndex Index) const;
|
||||
StringRef saveTypeName(StringRef Name);
|
||||
|
||||
bool IsInFieldList = false;
|
||||
|
||||
/// Name of the current type. Only valid before visitTypeEnd.
|
||||
StringRef Name;
|
||||
/// Current type index. Only valid before visitTypeEnd, and if we are
|
||||
/// visiting a random access type database.
|
||||
TypeIndex CurrentTypeIndex;
|
||||
|
||||
TypeDatabase &TypeDB;
|
||||
PointerUnion<TypeDatabase *, RandomAccessTypeDatabase *> TypeDB;
|
||||
};
|
||||
|
||||
} // end namespace codeview
|
||||
|
@ -47,6 +47,14 @@ public:
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Error visitTypeBegin(CVType &Record, TypeIndex Index) override {
|
||||
for (auto Visitor : Pipeline) {
|
||||
if (auto EC = Visitor->visitTypeBegin(Record, Index))
|
||||
return EC;
|
||||
}
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Error visitTypeEnd(CVType &Record) override {
|
||||
for (auto Visitor : Pipeline) {
|
||||
if (auto EC = Visitor->visitTypeEnd(Record))
|
||||
|
@ -26,8 +26,15 @@ public:
|
||||
virtual Error visitUnknownType(CVType &Record) { return Error::success(); }
|
||||
/// Paired begin/end actions for all types. Receives all record data,
|
||||
/// including the fixed-length record prefix. visitTypeBegin() should return
|
||||
/// the type of the Record, or an error if it cannot be determined.
|
||||
/// the type of the Record, or an error if it cannot be determined. Exactly
|
||||
/// one of the two visitTypeBegin methods will be called, depending on whether
|
||||
/// records are being visited sequentially or randomly. An implementation
|
||||
/// should be prepared to handle both (or assert if it can't handle random
|
||||
/// access visitation).
|
||||
virtual Error visitTypeBegin(CVType &Record) { return Error::success(); }
|
||||
virtual Error visitTypeBegin(CVType &Record, TypeIndex Index) {
|
||||
return Error::success();
|
||||
}
|
||||
virtual Error visitTypeEnd(CVType &Record) { return Error::success(); }
|
||||
|
||||
virtual Error visitUnknownMember(CVMemberRecord &Record) {
|
||||
|
@ -76,7 +76,7 @@ void CVTypeVisitor::addTypeServerHandler(TypeServerHandler &Handler) {
|
||||
Handlers.push_back(&Handler);
|
||||
}
|
||||
|
||||
Error CVTypeVisitor::visitTypeRecord(CVType &Record) {
|
||||
Expected<bool> CVTypeVisitor::handleTypeServer(CVType &Record) {
|
||||
if (Record.Type == TypeLeafKind::LF_TYPESERVER2 && !Handlers.empty()) {
|
||||
auto TS = deserializeTypeServerRecord(Record);
|
||||
if (!TS)
|
||||
@ -90,16 +90,16 @@ Error CVTypeVisitor::visitTypeRecord(CVType &Record) {
|
||||
|
||||
// If the handler processed the record, return success.
|
||||
if (*ExpectedResult)
|
||||
return Error::success();
|
||||
return true;
|
||||
|
||||
// Otherwise keep searching for a handler, eventually falling out and
|
||||
// using the default record handler.
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto EC = Callbacks.visitTypeBegin(Record))
|
||||
return EC;
|
||||
|
||||
Error CVTypeVisitor::finishVisitation(CVType &Record) {
|
||||
switch (Record.Type) {
|
||||
default:
|
||||
if (auto EC = Callbacks.visitUnknownType(Record))
|
||||
@ -124,6 +124,32 @@ Error CVTypeVisitor::visitTypeRecord(CVType &Record) {
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Error CVTypeVisitor::visitTypeRecord(CVType &Record, TypeIndex Index) {
|
||||
auto ExpectedResult = handleTypeServer(Record);
|
||||
if (!ExpectedResult)
|
||||
return ExpectedResult.takeError();
|
||||
if (*ExpectedResult)
|
||||
return Error::success();
|
||||
|
||||
if (auto EC = Callbacks.visitTypeBegin(Record, Index))
|
||||
return EC;
|
||||
|
||||
return finishVisitation(Record);
|
||||
}
|
||||
|
||||
Error CVTypeVisitor::visitTypeRecord(CVType &Record) {
|
||||
auto ExpectedResult = handleTypeServer(Record);
|
||||
if (!ExpectedResult)
|
||||
return ExpectedResult.takeError();
|
||||
if (*ExpectedResult)
|
||||
return Error::success();
|
||||
|
||||
if (auto EC = Callbacks.visitTypeBegin(Record))
|
||||
return EC;
|
||||
|
||||
return finishVisitation(Record);
|
||||
}
|
||||
|
||||
static Error visitMemberRecord(CVMemberRecord &Record,
|
||||
TypeVisitorCallbacks &Callbacks) {
|
||||
if (auto EC = Callbacks.visitMemberBegin(Record))
|
||||
|
@ -112,16 +112,61 @@ StringRef TypeDatabase::getTypeName(TypeIndex Index) const {
|
||||
}
|
||||
|
||||
const CVType &TypeDatabase::getTypeRecord(TypeIndex Index) const {
|
||||
return TypeRecords[Index.getIndex() - TypeIndex::FirstNonSimpleIndex];
|
||||
return TypeRecords[toArrayIndex(Index)];
|
||||
}
|
||||
|
||||
CVType &TypeDatabase::getTypeRecord(TypeIndex Index) {
|
||||
return TypeRecords[Index.getIndex() - TypeIndex::FirstNonSimpleIndex];
|
||||
return TypeRecords[toArrayIndex(Index)];
|
||||
}
|
||||
|
||||
bool TypeDatabase::containsTypeIndex(TypeIndex Index) const {
|
||||
uint32_t I = Index.getIndex() - TypeIndex::FirstNonSimpleIndex;
|
||||
return I < CVUDTNames.size();
|
||||
return toArrayIndex(Index) < CVUDTNames.size();
|
||||
}
|
||||
|
||||
uint32_t TypeDatabase::size() const { return CVUDTNames.size(); }
|
||||
|
||||
uint32_t TypeDatabase::toArrayIndex(TypeIndex Index) const {
|
||||
assert(Index.getIndex() >= TypeIndex::FirstNonSimpleIndex);
|
||||
return Index.getIndex() - TypeIndex::FirstNonSimpleIndex;
|
||||
}
|
||||
|
||||
RandomAccessTypeDatabase::RandomAccessTypeDatabase(uint32_t ExpectedSize)
|
||||
: TypeDatabase(ExpectedSize) {
|
||||
ValidRecords.resize(ExpectedSize);
|
||||
CVUDTNames.resize(ExpectedSize);
|
||||
TypeRecords.resize(ExpectedSize);
|
||||
}
|
||||
|
||||
void RandomAccessTypeDatabase::recordType(StringRef Name, TypeIndex Index,
|
||||
const CVType &Data) {
|
||||
assert(!containsTypeIndex(Index));
|
||||
uint32_t ZI = Index.getIndex() - TypeIndex::FirstNonSimpleIndex;
|
||||
|
||||
CVUDTNames[ZI] = Name;
|
||||
TypeRecords[ZI] = Data;
|
||||
ValidRecords.set(ZI);
|
||||
}
|
||||
|
||||
StringRef RandomAccessTypeDatabase::getTypeName(TypeIndex Index) const {
|
||||
assert(containsTypeIndex(Index));
|
||||
return TypeDatabase::getTypeName(Index);
|
||||
}
|
||||
|
||||
const CVType &RandomAccessTypeDatabase::getTypeRecord(TypeIndex Index) const {
|
||||
assert(containsTypeIndex(Index));
|
||||
return TypeDatabase::getTypeRecord(Index);
|
||||
}
|
||||
|
||||
CVType &RandomAccessTypeDatabase::getTypeRecord(TypeIndex Index) {
|
||||
assert(containsTypeIndex(Index));
|
||||
return TypeDatabase::getTypeRecord(Index);
|
||||
}
|
||||
|
||||
bool RandomAccessTypeDatabase::containsTypeIndex(TypeIndex Index) const {
|
||||
if (Index.isSimple())
|
||||
return true;
|
||||
|
||||
return ValidRecords.test(toArrayIndex(Index));
|
||||
}
|
||||
|
||||
uint32_t RandomAccessTypeDatabase::size() const { return ValidRecords.count(); }
|
||||
|
@ -15,7 +15,9 @@ using namespace llvm;
|
||||
|
||||
using namespace llvm::codeview;
|
||||
|
||||
Error TypeDatabaseVisitor::visitTypeBegin(CVRecord<TypeLeafKind> &Record) {
|
||||
Error TypeDatabaseVisitor::visitTypeBegin(CVType &Record) {
|
||||
assert(TypeDB.is<TypeDatabase *>());
|
||||
|
||||
assert(!IsInFieldList);
|
||||
// Reset Name to the empty string. If the visitor sets it, we know it.
|
||||
Name = "";
|
||||
@ -28,6 +30,34 @@ Error TypeDatabaseVisitor::visitTypeBegin(CVRecord<TypeLeafKind> &Record) {
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
StringRef TypeDatabaseVisitor::getTypeName(TypeIndex Index) const {
|
||||
if (auto DB = TypeDB.get<TypeDatabase *>())
|
||||
return DB->getTypeName(Index);
|
||||
else if (auto DB = TypeDB.get<RandomAccessTypeDatabase *>())
|
||||
return DB->getTypeName(Index);
|
||||
|
||||
llvm_unreachable("Invalid TypeDB Kind!");
|
||||
}
|
||||
|
||||
StringRef TypeDatabaseVisitor::saveTypeName(StringRef Name) {
|
||||
if (auto DB = TypeDB.get<TypeDatabase *>())
|
||||
return DB->saveTypeName(Name);
|
||||
else if (auto DB = TypeDB.get<RandomAccessTypeDatabase *>())
|
||||
return DB->saveTypeName(Name);
|
||||
|
||||
llvm_unreachable("Invalid TypeDB Kind!");
|
||||
}
|
||||
|
||||
Error TypeDatabaseVisitor::visitTypeBegin(CVType &Record, TypeIndex Index) {
|
||||
assert(TypeDB.is<RandomAccessTypeDatabase *>());
|
||||
|
||||
if (auto EC = visitTypeBegin(Record))
|
||||
return EC;
|
||||
|
||||
CurrentTypeIndex = Index;
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Error TypeDatabaseVisitor::visitTypeEnd(CVType &CVR) {
|
||||
if (CVR.Type == LF_FIELDLIST) {
|
||||
assert(IsInFieldList);
|
||||
@ -39,7 +69,11 @@ Error TypeDatabaseVisitor::visitTypeEnd(CVType &CVR) {
|
||||
// CVUDTNames is indexed by type index, and must have one entry for every
|
||||
// type. Field list members are not recorded, and are only referenced by
|
||||
// their containing field list record.
|
||||
TypeDB.recordType(Name, CVR);
|
||||
if (auto DB = TypeDB.get<TypeDatabase *>())
|
||||
DB->recordType(Name, CVR);
|
||||
else if (auto DB = TypeDB.get<RandomAccessTypeDatabase *>())
|
||||
DB->recordType(Name, CurrentTypeIndex, CVR);
|
||||
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
@ -73,13 +107,13 @@ Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR, ArgListRecord &Args) {
|
||||
uint32_t Size = Indices.size();
|
||||
SmallString<256> TypeName("(");
|
||||
for (uint32_t I = 0; I < Size; ++I) {
|
||||
StringRef ArgTypeName = TypeDB.getTypeName(Indices[I]);
|
||||
StringRef ArgTypeName = getTypeName(Indices[I]);
|
||||
TypeName.append(ArgTypeName);
|
||||
if (I + 1 != Size)
|
||||
TypeName.append(", ");
|
||||
}
|
||||
TypeName.push_back(')');
|
||||
Name = TypeDB.saveTypeName(TypeName);
|
||||
Name = saveTypeName(TypeName);
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
@ -89,13 +123,13 @@ Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR,
|
||||
uint32_t Size = Indices.size();
|
||||
SmallString<256> TypeName("\"");
|
||||
for (uint32_t I = 0; I < Size; ++I) {
|
||||
StringRef ArgTypeName = TypeDB.getTypeName(Indices[I]);
|
||||
StringRef ArgTypeName = getTypeName(Indices[I]);
|
||||
TypeName.append(ArgTypeName);
|
||||
if (I + 1 != Size)
|
||||
TypeName.append("\" \"");
|
||||
}
|
||||
TypeName.push_back('\"');
|
||||
Name = TypeDB.saveTypeName(TypeName);
|
||||
Name = saveTypeName(TypeName);
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
@ -132,26 +166,26 @@ Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR,
|
||||
|
||||
Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR,
|
||||
ProcedureRecord &Proc) {
|
||||
StringRef ReturnTypeName = TypeDB.getTypeName(Proc.getReturnType());
|
||||
StringRef ArgListTypeName = TypeDB.getTypeName(Proc.getArgumentList());
|
||||
StringRef ReturnTypeName = getTypeName(Proc.getReturnType());
|
||||
StringRef ArgListTypeName = getTypeName(Proc.getArgumentList());
|
||||
SmallString<256> TypeName(ReturnTypeName);
|
||||
TypeName.push_back(' ');
|
||||
TypeName.append(ArgListTypeName);
|
||||
Name = TypeDB.saveTypeName(TypeName);
|
||||
Name = saveTypeName(TypeName);
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR,
|
||||
MemberFunctionRecord &MF) {
|
||||
StringRef ReturnTypeName = TypeDB.getTypeName(MF.getReturnType());
|
||||
StringRef ClassTypeName = TypeDB.getTypeName(MF.getClassType());
|
||||
StringRef ArgListTypeName = TypeDB.getTypeName(MF.getArgumentList());
|
||||
StringRef ReturnTypeName = getTypeName(MF.getReturnType());
|
||||
StringRef ClassTypeName = getTypeName(MF.getClassType());
|
||||
StringRef ArgListTypeName = getTypeName(MF.getArgumentList());
|
||||
SmallString<256> TypeName(ReturnTypeName);
|
||||
TypeName.push_back(' ');
|
||||
TypeName.append(ClassTypeName);
|
||||
TypeName.append("::");
|
||||
TypeName.append(ArgListTypeName);
|
||||
Name = TypeDB.saveTypeName(TypeName);
|
||||
Name = saveTypeName(TypeName);
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
@ -171,13 +205,13 @@ Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR, PointerRecord &Ptr) {
|
||||
if (Ptr.isPointerToMember()) {
|
||||
const MemberPointerInfo &MI = Ptr.getMemberInfo();
|
||||
|
||||
StringRef PointeeName = TypeDB.getTypeName(Ptr.getReferentType());
|
||||
StringRef ClassName = TypeDB.getTypeName(MI.getContainingType());
|
||||
StringRef PointeeName = getTypeName(Ptr.getReferentType());
|
||||
StringRef ClassName = getTypeName(MI.getContainingType());
|
||||
SmallString<256> TypeName(PointeeName);
|
||||
TypeName.push_back(' ');
|
||||
TypeName.append(ClassName);
|
||||
TypeName.append("::*");
|
||||
Name = TypeDB.saveTypeName(TypeName);
|
||||
Name = saveTypeName(TypeName);
|
||||
} else {
|
||||
SmallString<256> TypeName;
|
||||
if (Ptr.isConst())
|
||||
@ -187,7 +221,7 @@ Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR, PointerRecord &Ptr) {
|
||||
if (Ptr.isUnaligned())
|
||||
TypeName.append("__unaligned ");
|
||||
|
||||
TypeName.append(TypeDB.getTypeName(Ptr.getReferentType()));
|
||||
TypeName.append(getTypeName(Ptr.getReferentType()));
|
||||
|
||||
if (Ptr.getMode() == PointerMode::LValueReference)
|
||||
TypeName.append("&");
|
||||
@ -197,7 +231,7 @@ Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR, PointerRecord &Ptr) {
|
||||
TypeName.append("*");
|
||||
|
||||
if (!TypeName.empty())
|
||||
Name = TypeDB.saveTypeName(TypeName);
|
||||
Name = saveTypeName(TypeName);
|
||||
}
|
||||
return Error::success();
|
||||
}
|
||||
@ -205,7 +239,7 @@ Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR, PointerRecord &Ptr) {
|
||||
Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR, ModifierRecord &Mod) {
|
||||
uint16_t Mods = static_cast<uint16_t>(Mod.getModifiers());
|
||||
|
||||
StringRef ModifiedName = TypeDB.getTypeName(Mod.getModifiedType());
|
||||
StringRef ModifiedName = getTypeName(Mod.getModifiedType());
|
||||
SmallString<256> TypeName;
|
||||
if (Mods & uint16_t(ModifierOptions::Const))
|
||||
TypeName.append("const ");
|
||||
@ -214,14 +248,14 @@ Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR, ModifierRecord &Mod) {
|
||||
if (Mods & uint16_t(ModifierOptions::Unaligned))
|
||||
TypeName.append("__unaligned ");
|
||||
TypeName.append(ModifiedName);
|
||||
Name = TypeDB.saveTypeName(TypeName);
|
||||
Name = saveTypeName(TypeName);
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Error TypeDatabaseVisitor::visitKnownRecord(CVType &CVR,
|
||||
VFTableShapeRecord &Shape) {
|
||||
Name = TypeDB.saveTypeName("<vftable " + utostr(Shape.getEntryCount()) +
|
||||
" methods>");
|
||||
Name =
|
||||
saveTypeName("<vftable " + utostr(Shape.getEntryCount()) + " methods>");
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user