diff --git a/phpunit/src/NativeClass/NativeObjectStackPromotionCodegenTest.php b/phpunit/src/NativeClass/NativeObjectStackPromotionCodegenTest.php index 737db6f4..62f61467 100644 --- a/phpunit/src/NativeClass/NativeObjectStackPromotionCodegenTest.php +++ b/phpunit/src/NativeClass/NativeObjectStackPromotionCodegenTest.php @@ -33,6 +33,30 @@ final class NativeObjectStackPromotionCodegenTest extends TestCase 'php::Int read();', $header, ); + self::assertStringContainsString( + '~php_nativestackpromotionfixture() noexcept;', + $header, + ); + self::assertStringNotContainsString( + 'virtual ~php_nativestackpromotionfixture()', + $header, + ); + self::assertStringContainsString( + '~php_nativestackfinalizerfixture() noexcept(false);', + $header, + ); + self::assertStringNotContainsString( + 'virtual ~php_nativestackfinalizerfixture()', + $header, + ); + self::assertStringContainsString( + 'virtual ~php_nativestackpromotionbase() noexcept;', + $header, + ); + self::assertStringContainsString( + 'virtual ~php_nativestackpromotionchild() noexcept override;', + $header, + ); self::assertStringContainsString( 'virtual php::Int __typephp_virtual_php_nativestackpromotionbase__readfrombase() override;', $header, diff --git a/src/NativeClass/NativeClassSupportTrait.php b/src/NativeClass/NativeClassSupportTrait.php index 69910e6b..9001104e 100644 --- a/src/NativeClass/NativeClassSupportTrait.php +++ b/src/NativeClass/NativeClassSupportTrait.php @@ -972,6 +972,17 @@ trait NativeClassSupportTrait return false; } + /** Whether this class declares or overrides a Native virtual method slot. */ + protected function nativeObjectHasVirtualMethods(ClassDef $class): bool + { + foreach ([...$class->methods, ...$class->abstractMethodDefs] as $method) { + if ($this->getNativeVirtualMethodSlots($class, $method) !== []) { + return true; + } + } + return false; + } + protected function getNativeObjectArgumentType(ArgInfo $argument): ?string { $class = $argument->declaredClass ?: $argument->class; @@ -2010,6 +2021,8 @@ trait NativeClassSupportTrait foreach ($classes as $class) { $name = $this->getNativeObjectCppName($class); + $usesVirtualClone = $this->nativeObjectUsesVirtualClone($class); + $usesVtable = $usesVirtualClone || $this->nativeObjectHasVirtualMethods($class); $hasNativeDestructor = $this->findNativeObjectMethod( $class->getNamespacedName(false), '__destruct', @@ -2026,9 +2039,13 @@ trait NativeClassSupportTrait $code .= $this->getNativeMethodParameterDeclarations($constructor->functionDef); } $code .= ');' . PHP_EOL; - $code .= ' virtual ~' . $name . '() ' + // The concrete type descriptor invokes the exact destructor, so + // destruction alone must not add a vptr to an otherwise flat class. + $code .= ' ' . ($usesVtable ? 'virtual ' : '') . '~' . $name . '() ' . ($hasNativeDestructor ? 'noexcept(false)' : 'noexcept') - . ($class->extends !== '' && $this->isNativeObjectClass($class->extends) ? ' override' : '') + . ($usesVtable + && $class->extends !== '' + && $this->isNativeObjectClass($class->extends) ? ' override' : '') . ';' . PHP_EOL; if ($class->hasMethod('__destruct')) { $code .= ' void __typephp_finalize_destructor();' . PHP_EOL; @@ -2087,7 +2104,7 @@ trait NativeClassSupportTrait } } } - if ($this->nativeObjectUsesVirtualClone($class)) { + if ($usesVirtualClone) { $code .= ' virtual ' . $name . ' *' . self::NATIVE_VIRTUAL_CLONE_METHOD . '() const'; if ($class->extends !== '' && $this->isNativeObjectClass($class->extends)) { $code .= ' override';