Commit 2c4fdd71b3f for php

commit 2c4fdd71b3f94828402a7b90666357ce41090e3e
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Wed Oct 28 14:26:57 2026 -0400

    ext/com_dotnet: Fix GH-25008 event source typelib crash

    php_com_locate_typeinfo() stored the default source IID and called
    ITypeLib_GetTypeInfoOfGuid() on a NULL typelib. Resolve the source
    interface from the coclass instead: IProvideClassInfo2::GetClassInfo()
    returns it, and GetRefTypeInfo() on the implemented interface with that
    IID follows references into other type libraries. A source that is not a
    dispinterface still raises "Type kind must be dispatchable";
    php_com_process_typeinfo() returned true after that error, so
    com_event_sink() walked an uninitialized name map. It now returns false.

    Fixes GH-25008
    Closes GH-25020

diff --git a/NEWS b/NEWS
index 68b413c08ee..daf52ae14d9 100644
--- a/NEWS
+++ b/NEWS
@@ -52,6 +52,10 @@ PHP                                                                        NEWS
     (ndossche, devnexen)
   . Fixed bug GH-9670 (List -C and -q in the CLI help). (Juan Versolato Lopes)

+- COM:
+  . Fixed bug GH-25008 (com_event_sink() and com_print_typeinfo() crash
+    resolving an event source with no loaded typelib). (Ilia Alshanetsky)
+
 - Curl:
   . Fix use-after-free of CURLOPT_STDERR stream on cloned curl handle.
     (lacatoire)
diff --git a/ext/com_dotnet/com_typeinfo.c b/ext/com_dotnet/com_typeinfo.c
index 28306b5609b..3917437ebbc 100644
--- a/ext/com_dotnet/com_typeinfo.c
+++ b/ext/com_dotnet/com_typeinfo.c
@@ -302,6 +302,39 @@ PHP_COM_DOTNET_API ITypeLib *php_com_load_typelib_via_cache(const char *search_s
 	return TL;
 }

+static ITypeInfo *php_com_find_impl_typeinfo(ITypeInfo *coinfo, const GUID *iid)
+{
+	ITypeInfo *found = NULL;
+	TYPEATTR *coattr;
+
+	if (FAILED(ITypeInfo_GetTypeAttr(coinfo, &coattr))) {
+		return NULL;
+	}
+
+	for (unsigned int i = 0; i < coattr->cImplTypes && !found; i++) {
+		HREFTYPE rt;
+		ITypeInfo *implinfo;
+		TYPEATTR *implattr;
+
+		if (FAILED(ITypeInfo_GetRefTypeOfImplType(coinfo, i, &rt)) ||
+				FAILED(ITypeInfo_GetRefTypeInfo(coinfo, rt, &implinfo))) {
+			continue;
+		}
+		if (SUCCEEDED(ITypeInfo_GetTypeAttr(implinfo, &implattr))) {
+			if (IsEqualGUID(&implattr->guid, iid)) {
+				found = implinfo;
+			}
+			ITypeInfo_ReleaseTypeAttr(implinfo, implattr);
+		}
+		if (!found) {
+			ITypeInfo_Release(implinfo);
+		}
+	}
+
+	ITypeInfo_ReleaseTypeAttr(coinfo, coattr);
+	return found;
+}
+
 ITypeInfo *php_com_locate_typeinfo(zend_string *type_lib_name, php_com_dotnet_object *obj,
 	zend_string *dispatch_name, bool sink)
 {
@@ -318,6 +351,14 @@ ITypeInfo *php_com_locate_typeinfo(zend_string *type_lib_name, php_com_dotnet_ob

 				if (SUCCEEDED(IDispatch_QueryInterface(V_DISPATCH(&obj->v), &IID_IProvideClassInfo2, (void**)&pci2))) {
 					gotguid = SUCCEEDED(IProvideClassInfo2_GetGUID(pci2, GUIDKIND_DEFAULT_SOURCE_DISP_IID, &iid));
+					if (gotguid) {
+						ITypeInfo *coinfo;
+
+						if (SUCCEEDED(IProvideClassInfo2_GetClassInfo(pci2, &coinfo))) {
+							typeinfo = php_com_find_impl_typeinfo(coinfo, &iid);
+							ITypeInfo_Release(coinfo);
+						}
+					}
 					IProvideClassInfo2_Release(pci2);
 				}
 				if (!gotguid && SUCCEEDED(IDispatch_QueryInterface(V_DISPATCH(&obj->v), &IID_IProvideClassInfo, (void**)&pci))) {
@@ -408,8 +449,6 @@ ITypeInfo *php_com_locate_typeinfo(zend_string *type_lib_name, php_com_dotnet_ob


 		efree(olename);
-	} else if (gotguid) {
-		ITypeLib_GetTypeInfoOfGuid(typelib, &iid, &typeinfo);
 	}

 	if (typelib) {
@@ -639,6 +678,8 @@ bool php_com_process_typeinfo(ITypeInfo *typeinfo, HashTable *id_to_name, bool p
 		}
 	} else {
 		zend_throw_error(NULL, "Type kind must be dispatchable, %08x given", attr->typekind);
+		ITypeInfo_ReleaseTypeAttr(typeinfo, attr);
+		return false;
 	}

 	ITypeInfo_ReleaseTypeAttr(typeinfo, attr);
diff --git a/ext/com_dotnet/tests/gh25008.phpt b/ext/com_dotnet/tests/gh25008.phpt
new file mode 100644
index 00000000000..eba601c9d1d
--- /dev/null
+++ b/ext/com_dotnet/tests/gh25008.phpt
@@ -0,0 +1,58 @@
+--TEST--
+GH-25008 (com_event_sink and com_print_typeinfo crash on a NULL source typelib)
+--EXTENSIONS--
+com_dotnet
+--INI--
+com.autoregister_typelib=0
+--SKIPIF--
+<?php
+try {
+    new COM('WinHttp.WinHttpRequest.5.1');
+    new COM('Scripting.Dictionary');
+} catch (com_exception $e) {
+    die('skip WinHttp or Scripting.Dictionary is not available');
+}
+?>
+--FILE--
+<?php
+$o = new COM('WinHttp.WinHttpRequest.5.1', null, CP_UTF8);
+
+try {
+    var_dump(com_event_sink($o, new stdClass));
+    echo "sink: no error\n";
+} catch (Error $e) {
+    echo 'sink: ', $e::class, ': ', $e->getMessage(), "\n";
+}
+
+ob_start();
+$events = com_print_typeinfo($o, null, true);
+$events_dump = ob_get_clean();
+echo 'events: ';
+var_dump($events);
+echo (str_contains($events_dump, 'class IWinHttpRequestEvents')
+    && str_contains($events_dump, '{F97F4E15-B787-4212-80D1-D380CBBF982E}')
+    && str_contains($events_dump, 'function OnResponseStart'))
+    ? "events interface\n" : "events missing\n";
+
+ob_start();
+$request = com_print_typeinfo($o, null, false);
+$request_dump = ob_get_clean();
+echo 'request: ';
+var_dump($request);
+echo (str_contains($request_dump, 'class IWinHttpRequest')
+    && str_contains($request_dump, '{016FE2EC-B2C8-45F8-B23B-39E53A75396B}'))
+    ? "request interface\n" : "request missing\n";
+
+$d = new COM('Scripting.Dictionary');
+echo 'dictionary: ';
+var_dump(com_event_sink($d, new stdClass));
+?>
+--EXPECTF--
+sink: Error: Type kind must be dispatchable, 00000003 given
+events: bool(true)
+events interface
+request: bool(true)
+request interface
+dictionary:
+Warning: com_event_sink(): IProvideClassInfo: this code not yet written! in %s on line %d
+bool(false)