This is an automated email from the ASF dual-hosted git repository. leginee pushed a commit to branch win10-msvc-trunk in repository https://gitbox.apache.org/repos/asf/openoffice.git
commit a3156910b1af41bd7af5f541f99fcb655fd494ab Author: Peter Kovacs <[email protected]> AuthorDate: Fri Aug 21 20:01:43 2026 +0200 climaker: do not let TypeResolve re-enter itself Process is terminated due to StackOverflowException. climaker compiled and ran --help after the C++/CLI port, then died emitting cli_uretypes.dll. A managed stack showed 164 nested TypeEmitter::type_resolve frames on unoidl.com.sun.star.container.XElementAccess. AppDomain::TypeResolve is not re-entrancy-guarded. type_resolve began by asking the ModuleBuilder for the very name it had just been asked about, and when that name cannot be resolved the runtime raises TypeResolve again, for the same name, straight back into the handler. It only bites the SECOND time an interface is looked up: the first pass DefineType's it, and the recursion needs a type that has been defined but not yet created. Tracing shows exactly that -- XElementAccess resolved, defined and added to m_incomplete_ifaces on the way through XEnumerationAccess, then asked for again as a base of XIdentifierAccess, and never returning. The code already knew about this state. get_type carries the table lookup COMMENTED OUT, under //We get the type from the ModuleBuilder even if the type is not complete //but have been defined. which was true of the .NET this was written against and is not true of .NET 4, where ModuleBuilder::GetType returns null for a TypeBuilder that has not been CreateType'd. VC9 targeted 2.0/3.5; the v142 toolset targets 4.x. That runtime change is the difference, not the syntax port. So: consult m_incomplete_ifaces BEFORE the ModuleBuilder, in both get_type and type_resolve, and keep a set of names currently being answered so a handler that is re-entered for one of them returns nullptr -- "I cannot help", which is true, and ends the chain rather than extending it. climaker now exits 0 and emits a 108KB cli_uretypes.dll. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01W7pjcp2sXU1HaUwXT7kc29 --- main/cli_ure/source/climaker/climaker_emit.cxx | 70 +++++++++++++++++++++----- main/cli_ure/source/climaker/climaker_share.h | 3 ++ 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/main/cli_ure/source/climaker/climaker_emit.cxx b/main/cli_ure/source/climaker/climaker_emit.cxx index db66f769c4..72ff713839 100644 --- a/main/cli_ure/source/climaker/climaker_emit.cxx +++ b/main/cli_ure/source/climaker/climaker_emit.cxx @@ -284,15 +284,43 @@ Assembly ^ TypeEmitter::type_resolve( ::System::Object ^, ::System::ResolveEventArgs ^ args ) { ::System::String ^ cts_name = args->Name; - ::System::Type ^ ret_type = m_module_builder->GetType( - cts_name, false /* no exc */ ); - if (nullptr == ret_type) + + // Two things have to happen before anything else, and both exist because + // AppDomain::TypeResolve is not re-entrancy-guarded: asking the + // ModuleBuilder for a name it cannot resolve raises TypeResolve again, for + // the same name, and lands straight back here. + // + // First: refuse to answer a name we are already answering. Returning + // nullptr tells the runtime we cannot help, which is true, and ends the + // chain instead of extending it. + if (m_resolving->ContainsKey( cts_name )) + return nullptr; + + // Second: consult the incomplete-interface table BEFORE the ModuleBuilder. + // A type that has been DefineType'd but not yet CreateType'd is in that + // table, and on .NET 4 ModuleBuilder::GetType does not return it -- so + // asking the ModuleBuilder first is both slower and the thing that + // recurses. + ::System::Type ^ ret_type = nullptr; { iface_entry ^ entry = dynamic_cast< iface_entry ^ >( m_incomplete_ifaces[ cts_name ] ); if (nullptr != entry) ret_type = entry->m_type_builder; } + + if (nullptr == ret_type) + { + m_resolving->Add( cts_name, cts_name ); + try + { + ret_type = m_module_builder->GetType( cts_name, false /* no exc */ ); + } + finally + { + m_resolving->Remove( cts_name ); + } + } if (nullptr == ret_type) { sal_Int32 len = m_extra_assemblies->Length; @@ -322,15 +350,32 @@ Assembly ^ TypeEmitter::type_resolve( ::System::String ^ cts_name, bool throw_exc ) { ::System::Type ^ ret_type = m_module_builder->GetType( cts_name, false ); - //We get the type from the ModuleBuilder even if the type is not complete - //but have been defined. - //if (ret_type == 0) - //{ - // iface_entry ^ entry = dynamic_cast< iface_entry ^ >( - // m_incomplete_ifaces[ cts_name ] ); - // if (nullptr != entry) - // ret_type = entry->m_type_builder; - //} + + // The lines below were commented out with the note "We get the type from + // the ModuleBuilder even if the type is not complete but have been + // defined." That was true of the .NET this was written against; it is not + // true of .NET 4, where ModuleBuilder::GetType returns null for a type that + // has been DefineType'd but not yet CreateType'd. + // + // Leaving them commented out is not merely a missed lookup -- it does not + // terminate. The caller falls through to Type::GetType, which raises + // AppDomain::TypeResolve, whose handler is type_resolve, which asks + // ModuleBuilder::GetType for the same name, which raises TypeResolve + // again. AppDomain::TypeResolve has no re-entrancy guard, so climaker + // dies with a StackOverflowException the second time any interface is + // looked up -- the first time defines it, the second recurses: + // + // Process is terminated due to StackOverflowException. + // + // The incomplete-interface table is exactly the right answer here, which + // is presumably why it was written in the first place. + if (ret_type == nullptr) + { + iface_entry ^ entry = dynamic_cast< iface_entry ^ >( + m_incomplete_ifaces[ cts_name ] ); + if (nullptr != entry) + ret_type = entry->m_type_builder; + } //try the cli_basetypes assembly if (ret_type == nullptr) { @@ -2234,6 +2279,7 @@ TypeEmitter::TypeEmitter( m_type_Exception( nullptr ), m_type_RuntimeException( nullptr ), m_incomplete_ifaces( gcnew ::System::Collections::Hashtable() ), + m_resolving( gcnew ::System::Collections::Hashtable() ), m_incomplete_structs( gcnew ::System::Collections::Hashtable() ), m_incomplete_services(gcnew ::System::Collections::Hashtable() ), m_incomplete_singletons(gcnew ::System::Collections::Hashtable() ), diff --git a/main/cli_ure/source/climaker/climaker_share.h b/main/cli_ure/source/climaker/climaker_share.h index df2447c940..68e21da01c 100644 --- a/main/cli_ure/source/climaker/climaker_share.h +++ b/main/cli_ure/source/climaker/climaker_share.h @@ -169,6 +169,9 @@ ref class TypeEmitter : public ::System::IDisposable ::System::Reflection::Emit::TypeBuilder ^ m_type_builder; }; ::System::Collections::Hashtable ^ m_incomplete_ifaces; + // Names type_resolve is part-way through answering. AppDomain's + // TypeResolve event has no re-entrancy guard of its own. + ::System::Collections::Hashtable ^ m_resolving; ::System::Type ^ complete_iface_type( iface_entry ^ entry ); ref class struct_entry
