mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2025-07-07 10:40:38 +00:00
Use DW_AT_MIPS_linkage_name if it is available to get names of functions with arguments during symbol dumping.
A=Rafael Ávila de Espíndola <respindola@mozilla.com> R=ted at https://breakpad.appspot.com/457002/ git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1059 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
75f6a6bf95
commit
5e76d87dfe
|
@ -39,6 +39,7 @@
|
||||||
#include "common/dwarf_cu_to_module.h"
|
#include "common/dwarf_cu_to_module.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <cxxabi.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ using std::set;
|
||||||
using std::vector;
|
using std::vector;
|
||||||
|
|
||||||
// Data provided by a DWARF specification DIE.
|
// Data provided by a DWARF specification DIE.
|
||||||
//
|
//
|
||||||
// In DWARF, the DIE for a definition may contain a DW_AT_specification
|
// In DWARF, the DIE for a definition may contain a DW_AT_specification
|
||||||
// attribute giving the offset of the corresponding declaration DIE, and
|
// attribute giving the offset of the corresponding declaration DIE, and
|
||||||
// the definition DIE may omit information given in the declaration. For
|
// the definition DIE may omit information given in the declaration. For
|
||||||
|
@ -218,6 +219,14 @@ class DwarfCUToModule::GenericDIEHandler: public dwarf2reader::DIEHandler {
|
||||||
DIEContext *parent_context_;
|
DIEContext *parent_context_;
|
||||||
uint64 offset_;
|
uint64 offset_;
|
||||||
|
|
||||||
|
// Place the name in the global set of strings. Even though this looks
|
||||||
|
// like a copy, all the major std::string implementations use reference
|
||||||
|
// counting internally, so the effect is to have all the data structures
|
||||||
|
// share copies of strings whenever possible.
|
||||||
|
// FIXME: Should this return something like a string_ref to avoid the
|
||||||
|
// assumption about how strings are implemented?
|
||||||
|
string AddStringToPool(const string &str);
|
||||||
|
|
||||||
// If this DIE has a DW_AT_declaration attribute, this is its value.
|
// If this DIE has a DW_AT_declaration attribute, this is its value.
|
||||||
// It is false on DIEs with no DW_AT_declaration attribute.
|
// It is false on DIEs with no DW_AT_declaration attribute.
|
||||||
bool declaration_;
|
bool declaration_;
|
||||||
|
@ -230,6 +239,11 @@ class DwarfCUToModule::GenericDIEHandler: public dwarf2reader::DIEHandler {
|
||||||
// The value of the DW_AT_name attribute, or the empty string if the
|
// The value of the DW_AT_name attribute, or the empty string if the
|
||||||
// DIE has no such attribute.
|
// DIE has no such attribute.
|
||||||
string name_attribute_;
|
string name_attribute_;
|
||||||
|
|
||||||
|
// The demangled value of the DW_AT_MIPS_linkage_name attribute, or the empty
|
||||||
|
// string if the DIE has no such attribute or its content could not be
|
||||||
|
// demangled.
|
||||||
|
string demangled_name_;
|
||||||
};
|
};
|
||||||
|
|
||||||
void DwarfCUToModule::GenericDIEHandler::ProcessAttributeUnsigned(
|
void DwarfCUToModule::GenericDIEHandler::ProcessAttributeUnsigned(
|
||||||
|
@ -273,20 +287,26 @@ void DwarfCUToModule::GenericDIEHandler::ProcessAttributeReference(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string DwarfCUToModule::GenericDIEHandler::AddStringToPool(const string &str) {
|
||||||
|
pair<set<string>::iterator, bool> result =
|
||||||
|
cu_context_->file_context->file_private->common_strings.insert(str);
|
||||||
|
return *result.first;
|
||||||
|
}
|
||||||
|
|
||||||
void DwarfCUToModule::GenericDIEHandler::ProcessAttributeString(
|
void DwarfCUToModule::GenericDIEHandler::ProcessAttributeString(
|
||||||
enum DwarfAttribute attr,
|
enum DwarfAttribute attr,
|
||||||
enum DwarfForm form,
|
enum DwarfForm form,
|
||||||
const string &data) {
|
const string &data) {
|
||||||
switch (attr) {
|
switch (attr) {
|
||||||
case dwarf2reader::DW_AT_name: {
|
case dwarf2reader::DW_AT_name:
|
||||||
// Place the name in our global set of strings, and then use the
|
name_attribute_ = AddStringToPool(data);
|
||||||
// string from the set. Even though the assignment looks like a copy,
|
break;
|
||||||
// all the major std::string implementations use reference counting
|
case dwarf2reader::DW_AT_MIPS_linkage_name: {
|
||||||
// internally, so the effect is to have all our data structures share
|
char* demangled = abi::__cxa_demangle(data.c_str(), NULL, NULL, NULL);
|
||||||
// copies of strings whenever possible.
|
if (demangled) {
|
||||||
pair<set<string>::iterator, bool> result =
|
demangled_name_ = AddStringToPool(demangled);
|
||||||
cu_context_->file_context->file_private->common_strings.insert(data);
|
free(reinterpret_cast<void*>(demangled));
|
||||||
name_attribute_ = *result.first;
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: break;
|
default: break;
|
||||||
|
@ -294,6 +314,11 @@ void DwarfCUToModule::GenericDIEHandler::ProcessAttributeString(
|
||||||
}
|
}
|
||||||
|
|
||||||
string DwarfCUToModule::GenericDIEHandler::ComputeQualifiedName() {
|
string DwarfCUToModule::GenericDIEHandler::ComputeQualifiedName() {
|
||||||
|
// Use DW_AT_MIPS_linkage_name if it is available. It is already qualified,
|
||||||
|
// so there is no need to add enclosing_name.
|
||||||
|
if (!demangled_name_.empty())
|
||||||
|
return demangled_name_;
|
||||||
|
|
||||||
// Find our unqualified name. If the DIE has its own DW_AT_name
|
// Find our unqualified name. If the DIE has its own DW_AT_name
|
||||||
// attribute, then use that; otherwise, check our specification.
|
// attribute, then use that; otherwise, check our specification.
|
||||||
const string *unqualified_name;
|
const string *unqualified_name;
|
||||||
|
|
|
@ -202,7 +202,8 @@ class CUFixtureBase {
|
||||||
// address, and size. Call EndAttributes and Finish; one cannot
|
// address, and size. Call EndAttributes and Finish; one cannot
|
||||||
// define children of the defined function's DIE.
|
// define children of the defined function's DIE.
|
||||||
void DefineFunction(DIEHandler *parent, const string &name,
|
void DefineFunction(DIEHandler *parent, const string &name,
|
||||||
Module::Address address, Module::Address size);
|
Module::Address address, Module::Address size,
|
||||||
|
const char* mangled_name);
|
||||||
|
|
||||||
// Create a declaration DIE as a child of PARENT with the given
|
// Create a declaration DIE as a child of PARENT with the given
|
||||||
// offset, tag and name. If NAME is the empty string, don't provide
|
// offset, tag and name. If NAME is the empty string, don't provide
|
||||||
|
@ -452,7 +453,8 @@ DIEHandler *CUFixtureBase::StartSpecifiedDIE(DIEHandler *parent,
|
||||||
|
|
||||||
void CUFixtureBase::DefineFunction(dwarf2reader::DIEHandler *parent,
|
void CUFixtureBase::DefineFunction(dwarf2reader::DIEHandler *parent,
|
||||||
const string &name, Module::Address address,
|
const string &name, Module::Address address,
|
||||||
Module::Address size) {
|
Module::Address size,
|
||||||
|
const char* mangled_name) {
|
||||||
dwarf2reader::AttributeList func_attrs;
|
dwarf2reader::AttributeList func_attrs;
|
||||||
func_attrs.push_back(make_pair(dwarf2reader::DW_AT_name,
|
func_attrs.push_back(make_pair(dwarf2reader::DW_AT_name,
|
||||||
dwarf2reader::DW_FORM_strp));
|
dwarf2reader::DW_FORM_strp));
|
||||||
|
@ -475,6 +477,11 @@ void CUFixtureBase::DefineFunction(dwarf2reader::DIEHandler *parent,
|
||||||
func->ProcessAttributeUnsigned(dwarf2reader::DW_AT_high_pc,
|
func->ProcessAttributeUnsigned(dwarf2reader::DW_AT_high_pc,
|
||||||
dwarf2reader::DW_FORM_addr,
|
dwarf2reader::DW_FORM_addr,
|
||||||
address + size);
|
address + size);
|
||||||
|
if (mangled_name)
|
||||||
|
func->ProcessAttributeString(dwarf2reader::DW_AT_MIPS_linkage_name,
|
||||||
|
dwarf2reader::DW_FORM_strp,
|
||||||
|
mangled_name);
|
||||||
|
|
||||||
ProcessStrangeAttributes(func);
|
ProcessStrangeAttributes(func);
|
||||||
EXPECT_TRUE(func->EndAttributes());
|
EXPECT_TRUE(func->EndAttributes());
|
||||||
func->Finish();
|
func->Finish();
|
||||||
|
@ -675,7 +682,7 @@ void CUFixtureBase::TestLine(int i, int j,
|
||||||
#define PushLine(a,b,c,d) TRACE(PushLine((a),(b),(c),(d)))
|
#define PushLine(a,b,c,d) TRACE(PushLine((a),(b),(c),(d)))
|
||||||
#define SetLanguage(a) TRACE(SetLanguage(a))
|
#define SetLanguage(a) TRACE(SetLanguage(a))
|
||||||
#define StartCU() TRACE(StartCU())
|
#define StartCU() TRACE(StartCU())
|
||||||
#define DefineFunction(a,b,c,d) TRACE(DefineFunction((a),(b),(c),(d)))
|
#define DefineFunction(a,b,c,d,e) TRACE(DefineFunction((a),(b),(c),(d),(e)))
|
||||||
#define DeclarationDIE(a,b,c,d) TRACE(DeclarationDIE((a),(b),(c),(d)))
|
#define DeclarationDIE(a,b,c,d) TRACE(DeclarationDIE((a),(b),(c),(d)))
|
||||||
#define DefinitionDIE(a,b,c,d,e,f) TRACE(DefinitionDIE((a),(b),(c),(d),(e),(f)))
|
#define DefinitionDIE(a,b,c,d,e,f) TRACE(DefinitionDIE((a),(b),(c),(d),(e),(f)))
|
||||||
#define TestFunctionCount(a) TRACE(TestFunctionCount(a))
|
#define TestFunctionCount(a) TRACE(TestFunctionCount(a))
|
||||||
|
@ -691,7 +698,7 @@ TEST_F(SimpleCU, OneFunc) {
|
||||||
|
|
||||||
StartCU();
|
StartCU();
|
||||||
DefineFunction(&root_handler_, "function1",
|
DefineFunction(&root_handler_, "function1",
|
||||||
0x938cf8c07def4d34ULL, 0x55592d727f6cd01fLL);
|
0x938cf8c07def4d34ULL, 0x55592d727f6cd01fLL, NULL);
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
|
||||||
TestFunctionCount(1);
|
TestFunctionCount(1);
|
||||||
|
@ -701,6 +708,18 @@ TEST_F(SimpleCU, OneFunc) {
|
||||||
246571772);
|
246571772);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SimpleCU, MangledName) {
|
||||||
|
PushLine(0x938cf8c07def4d34ULL, 0x55592d727f6cd01fLL, "line-file", 246571772);
|
||||||
|
|
||||||
|
StartCU();
|
||||||
|
DefineFunction(&root_handler_, "function1",
|
||||||
|
0x938cf8c07def4d34ULL, 0x55592d727f6cd01fLL, "_ZN1n1fEi");
|
||||||
|
root_handler_.Finish();
|
||||||
|
|
||||||
|
TestFunctionCount(1);
|
||||||
|
TestFunction(0, "n::f(int)", 0x938cf8c07def4d34ULL, 0x55592d727f6cd01fLL);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(SimpleCU, IrrelevantRootChildren) {
|
TEST_F(SimpleCU, IrrelevantRootChildren) {
|
||||||
StartCU();
|
StartCU();
|
||||||
dwarf2reader::AttributeList no_attrs;
|
dwarf2reader::AttributeList no_attrs;
|
||||||
|
@ -805,7 +824,7 @@ TEST_F(SimpleCU, UnnamedFunction) {
|
||||||
|
|
||||||
StartCU();
|
StartCU();
|
||||||
DefineFunction(&root_handler_, "",
|
DefineFunction(&root_handler_, "",
|
||||||
0x72b80e41a0ac1d40ULL, 0x537174f231ee181cULL);
|
0x72b80e41a0ac1d40ULL, 0x537174f231ee181cULL, NULL);
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
|
||||||
TestFunctionCount(1);
|
TestFunctionCount(1);
|
||||||
|
@ -882,10 +901,10 @@ TEST_P(FuncLinePairing, Pairing) {
|
||||||
StartCU();
|
StartCU();
|
||||||
DefineFunction(&root_handler_, "function1",
|
DefineFunction(&root_handler_, "function1",
|
||||||
s.functions[0].start,
|
s.functions[0].start,
|
||||||
s.functions[0].end - s.functions[0].start);
|
s.functions[0].end - s.functions[0].start, NULL);
|
||||||
DefineFunction(&root_handler_, "function2",
|
DefineFunction(&root_handler_, "function2",
|
||||||
s.functions[1].start,
|
s.functions[1].start,
|
||||||
s.functions[1].end - s.functions[1].start);
|
s.functions[1].end - s.functions[1].start, NULL);
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
|
||||||
TestFunctionCount(2);
|
TestFunctionCount(2);
|
||||||
|
@ -929,7 +948,8 @@ TEST_F(FuncLinePairing, FuncsNoLines) {
|
||||||
EXPECT_CALL(reporter_, UncoveredFunction(_)).WillOnce(Return());
|
EXPECT_CALL(reporter_, UncoveredFunction(_)).WillOnce(Return());
|
||||||
|
|
||||||
StartCU();
|
StartCU();
|
||||||
DefineFunction(&root_handler_, "function1", 0x127da12ffcf5c51fULL, 0x1000U);
|
DefineFunction(&root_handler_, "function1", 0x127da12ffcf5c51fULL, 0x1000U,
|
||||||
|
NULL);
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
|
||||||
TestFunctionCount(1);
|
TestFunctionCount(1);
|
||||||
|
@ -941,8 +961,8 @@ TEST_F(FuncLinePairing, GapThenFunction) {
|
||||||
PushLine(10, 2, "line-file-1", 263008005);
|
PushLine(10, 2, "line-file-1", 263008005);
|
||||||
|
|
||||||
StartCU();
|
StartCU();
|
||||||
DefineFunction(&root_handler_, "function1", 10, 2);
|
DefineFunction(&root_handler_, "function1", 10, 2, NULL);
|
||||||
DefineFunction(&root_handler_, "function2", 20, 2);
|
DefineFunction(&root_handler_, "function2", 20, 2, NULL);
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
|
||||||
TestFunctionCount(2);
|
TestFunctionCount(2);
|
||||||
|
@ -967,10 +987,10 @@ TEST_F(FuncLinePairing, GCCAlignmentStretch) {
|
||||||
PushLine(20, 10, "line-file", 61661044);
|
PushLine(20, 10, "line-file", 61661044);
|
||||||
|
|
||||||
StartCU();
|
StartCU();
|
||||||
DefineFunction(&root_handler_, "function1", 10, 5);
|
DefineFunction(&root_handler_, "function1", 10, 5, NULL);
|
||||||
// five-byte gap between functions, covered by line 63351048.
|
// five-byte gap between functions, covered by line 63351048.
|
||||||
// This should not elicit a warning.
|
// This should not elicit a warning.
|
||||||
DefineFunction(&root_handler_, "function2", 20, 10);
|
DefineFunction(&root_handler_, "function2", 20, 10, NULL);
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
|
||||||
TestFunctionCount(2);
|
TestFunctionCount(2);
|
||||||
|
@ -991,8 +1011,8 @@ TEST_F(FuncLinePairing, LineAtEndOfAddressSpace) {
|
||||||
EXPECT_CALL(reporter_, UncoveredLine(_)).WillOnce(Return());
|
EXPECT_CALL(reporter_, UncoveredLine(_)).WillOnce(Return());
|
||||||
|
|
||||||
StartCU();
|
StartCU();
|
||||||
DefineFunction(&root_handler_, "function1", 0xfffffffffffffff0ULL, 6);
|
DefineFunction(&root_handler_, "function1", 0xfffffffffffffff0ULL, 6, NULL);
|
||||||
DefineFunction(&root_handler_, "function2", 0xfffffffffffffffaULL, 5);
|
DefineFunction(&root_handler_, "function2", 0xfffffffffffffffaULL, 5, NULL);
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
|
||||||
TestFunctionCount(2);
|
TestFunctionCount(2);
|
||||||
|
@ -1012,7 +1032,7 @@ TEST_F(FuncLinePairing, WarnOnceFunc) {
|
||||||
EXPECT_CALL(reporter_, UncoveredFunction(_)).WillOnce(Return());
|
EXPECT_CALL(reporter_, UncoveredFunction(_)).WillOnce(Return());
|
||||||
|
|
||||||
StartCU();
|
StartCU();
|
||||||
DefineFunction(&root_handler_, "function", 10, 11);
|
DefineFunction(&root_handler_, "function", 10, 11, NULL);
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
|
||||||
TestFunctionCount(1);
|
TestFunctionCount(1);
|
||||||
|
@ -1029,8 +1049,8 @@ TEST_F(FuncLinePairing, WarnOnceLine) {
|
||||||
EXPECT_CALL(reporter_, UncoveredLine(_)).WillOnce(Return());
|
EXPECT_CALL(reporter_, UncoveredLine(_)).WillOnce(Return());
|
||||||
|
|
||||||
StartCU();
|
StartCU();
|
||||||
DefineFunction(&root_handler_, "function1", 11, 1);
|
DefineFunction(&root_handler_, "function1", 11, 1, NULL);
|
||||||
DefineFunction(&root_handler_, "function2", 13, 1);
|
DefineFunction(&root_handler_, "function2", 13, 1, NULL);
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
|
||||||
TestFunctionCount(2);
|
TestFunctionCount(2);
|
||||||
|
@ -1062,8 +1082,8 @@ TEST_P(CXXQualifiedNames, TwoFunctions) {
|
||||||
DIEHandler *enclosure_handler = StartNamedDIE(&root_handler_, tag,
|
DIEHandler *enclosure_handler = StartNamedDIE(&root_handler_, tag,
|
||||||
"Enclosure");
|
"Enclosure");
|
||||||
EXPECT_TRUE(enclosure_handler != NULL);
|
EXPECT_TRUE(enclosure_handler != NULL);
|
||||||
DefineFunction(enclosure_handler, "func_B", 10, 1);
|
DefineFunction(enclosure_handler, "func_B", 10, 1, NULL);
|
||||||
DefineFunction(enclosure_handler, "func_C", 20, 1);
|
DefineFunction(enclosure_handler, "func_C", 20, 1, NULL);
|
||||||
enclosure_handler->Finish();
|
enclosure_handler->Finish();
|
||||||
delete enclosure_handler;
|
delete enclosure_handler;
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
@ -1087,7 +1107,7 @@ TEST_P(CXXQualifiedNames, FuncInEnclosureInNamespace) {
|
||||||
DIEHandler *enclosure_handler = StartNamedDIE(namespace_handler, tag,
|
DIEHandler *enclosure_handler = StartNamedDIE(namespace_handler, tag,
|
||||||
"Enclosure");
|
"Enclosure");
|
||||||
EXPECT_TRUE(enclosure_handler != NULL);
|
EXPECT_TRUE(enclosure_handler != NULL);
|
||||||
DefineFunction(enclosure_handler, "function", 10, 1);
|
DefineFunction(enclosure_handler, "function", 10, 1, NULL);
|
||||||
enclosure_handler->Finish();
|
enclosure_handler->Finish();
|
||||||
delete enclosure_handler;
|
delete enclosure_handler;
|
||||||
namespace_handler->Finish();
|
namespace_handler->Finish();
|
||||||
|
@ -1114,7 +1134,7 @@ TEST_F(CXXQualifiedNames, FunctionInClassInStructInNamespace) {
|
||||||
DIEHandler *class_handler
|
DIEHandler *class_handler
|
||||||
= StartNamedDIE(struct_handler, dwarf2reader::DW_TAG_class_type,
|
= StartNamedDIE(struct_handler, dwarf2reader::DW_TAG_class_type,
|
||||||
"class_C");
|
"class_C");
|
||||||
DefineFunction(class_handler, "function_D", 10, 1);
|
DefineFunction(class_handler, "function_D", 10, 1, NULL);
|
||||||
class_handler->Finish();
|
class_handler->Finish();
|
||||||
delete class_handler;
|
delete class_handler;
|
||||||
struct_handler->Finish();
|
struct_handler->Finish();
|
||||||
|
@ -1160,7 +1180,7 @@ TEST_P(QualifiedForLanguage, MemberFunction) {
|
||||||
DIEHandler *class_handler
|
DIEHandler *class_handler
|
||||||
= StartNamedDIE(&root_handler_, dwarf2reader::DW_TAG_class_type,
|
= StartNamedDIE(&root_handler_, dwarf2reader::DW_TAG_class_type,
|
||||||
"class_A");
|
"class_A");
|
||||||
DefineFunction(class_handler, "function_B", 10, 1);
|
DefineFunction(class_handler, "function_B", 10, 1, NULL);
|
||||||
class_handler->Finish();
|
class_handler->Finish();
|
||||||
delete class_handler;
|
delete class_handler;
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
@ -1184,7 +1204,7 @@ TEST_P(QualifiedForLanguage, MemberFunctionSignedLanguage) {
|
||||||
DIEHandler *class_handler
|
DIEHandler *class_handler
|
||||||
= StartNamedDIE(&root_handler_, dwarf2reader::DW_TAG_class_type,
|
= StartNamedDIE(&root_handler_, dwarf2reader::DW_TAG_class_type,
|
||||||
"class_A");
|
"class_A");
|
||||||
DefineFunction(class_handler, "function_B", 10, 1);
|
DefineFunction(class_handler, "function_B", 10, 1, NULL);
|
||||||
class_handler->Finish();
|
class_handler->Finish();
|
||||||
delete class_handler;
|
delete class_handler;
|
||||||
root_handler_.Finish();
|
root_handler_.Finish();
|
||||||
|
@ -1286,7 +1306,7 @@ TEST_F(Specifications, NamedScopeDeclarationParent) {
|
||||||
0x419bb1d12f9a73a2ULL, "class-definition-name");
|
0x419bb1d12f9a73a2ULL, "class-definition-name");
|
||||||
ASSERT_TRUE(class_handler != NULL);
|
ASSERT_TRUE(class_handler != NULL);
|
||||||
DefineFunction(class_handler, "function",
|
DefineFunction(class_handler, "function",
|
||||||
0x5d13433d0df13d00ULL, 0x48ebebe5ade2cab4ULL);
|
0x5d13433d0df13d00ULL, 0x48ebebe5ade2cab4ULL, NULL);
|
||||||
class_handler->Finish();
|
class_handler->Finish();
|
||||||
delete class_handler;
|
delete class_handler;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue