New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SIGSEGV in zend_mm_alloc_small while executing spl_perform_autoload with Saxon extension #12124
Comments
This is not how the autoloader works, the autoloader is triggered when a class is encountered for the first time and it hasn't been loaded yet. As which point it will call whatever functions have been registered in order until the class is loaded or all autoloaders have been called. Can you reproduce this without relying on composer and without relying on Symfony? I will have a glance at the source code of the extension, but my guess here is that the issue is outside of PHP core. But then the autoloader is a bit wonky. |
Hi @Girgias , The last example runs already without the Symfony framework, if that's what you mean. It is only using the Response to trigger the autoloader. Anyway, I think that with your question I came closer to the problem. Check this code: // Class that will be required (SimpleClass.php)
<?php
namespace Dummy\Ns1;
class SimpleClass
{
public function __construct(
protected string $p1 = ''
) {
}
public function write(): void {
echo $this->p1;
}
} Code to run (KO) <?php
// Registers a new loader.
spl_autoload_register(function ($class_name) {
echo sprintf(">> Autoloading '%s' <<", $class_name);
require_once __DIR__ . '/SimpleClass.php';
});
$processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor = $processor->newXslt30Processor();
$compiled = $processor->compileFromString(
file_get_contents(__DIR__.'/template.xslt')
);
$args = [];
$result = $compiled->transformToString($xdmDoc);
echo $result;
echo "\n";
// Crash.
$o = new Dummy\Ns1\SimpleClass('Hello');
echo "\n";
$o->write();
echo "\n";
echo var_dump($o); The result will be zend_mm_heap corrupted Backtrace
zbacktrace
Other testNow, I noticed that in my original code I overwrite the $processor variable with the result of the method newXslt3Processor(). $processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor = $processor->newXslt30Processor(); I did not intend to do that, so let's change it to a new $processor3 (OK): <?php
// Registers a new loader.
spl_autoload_register(function ($class_name) {
echo sprintf(">> Autoloading '%s' <<", $class_name);
require_once __DIR__ . '/SimpleClass.php';
});
$processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor3 = $processor->newXslt30Processor();
$compiled = $processor3->compileFromString(
file_get_contents(__DIR__.'/template.xslt')
);
$args = [];
$result = $compiled->transformToString($xdmDoc);
echo $result;
echo "\n";
$o = new Dummy\Ns1\SimpleClass('Hello');
echo "\n";
$o->write();
echo "\n";
echo var_dump($o); It works! The I changed my example with Symfony (KO): <?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class TestController extends AbstractController
{
// This is a non-working route, triggered by the use of SaxonProcessor
// and the creation of a Response.
// This works too with PHPUnit.
#[Route('/test', name: 'app_test')]
public function index(): Response
{
$processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/../../input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor3 = $processor->newXslt30Processor();
$compiled = $processor3->compileFromString(
file_get_contents(__DIR__.'/../../template.xslt')
);
$args = [];
$result = $compiled->transformToString($xdmDoc);
// Below is the error trigger.
// If you add 'echo $result; exit;', the transformed result will be shown correctly.
$response = new Response($result);
// Never reached.
return $response;
}
} And I get a segmentation fault again, with the same backtrace like in my first message, with spl_perform_autoload and zend_mm_alloc_small. Again, I tried my simple example, this time with Composer (OK): <?php
require_once __DIR__ . '/vendor/autoload.php';
$processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor3 = $processor->newXslt30Processor();
$compiled = $processor3->compileFromString(
file_get_contents(__DIR__.'/template.xslt')
);
$args = [];
$result = $compiled->transformToString($xdmDoc);
echo $result;
echo "\n";
$r = new Symfony\Component\HttpFoundation\Response();
echo var_dump($r); In summary:
|
Are you executing this with opcache or not? And would it be possible to run the minimal case that doesn't use composer with a debug build, as some calls in the back trace are optimized out. |
opcache is disabled in php.ini: opcache.enable=0
So, I removed the distro-provided package and I built PHP 8.2.10 from source with
I tried to run the test case without reassignment and without composer and with the simple autoloader. It did not event start this time:
The constructor for a class of the extension doesn't work, so the test case cannot even run further. But the extension seems loaded, because I got:
I do believe now the best is to report it to the extension author. |
Given that the segfault occurs within that extension, it is likely the cause of this issue. zend_alloc usually segfaults when something has corrupted the allocators metadata. You could try compiling PHP + the extension with the |
I'm closing this issue for now. Let us know if there's anything more we can do. |
Thank you all for your suggestions. |
Hi, The following code also causes the segmentation error: $xslt30processor = (new \Saxon\SaxonProcessor())->newXslt30Processor(); |
Thanks for confirming! I had a quick glance at the extension code, and it seems to be doing some less than optimal stuff at times. Is there a git repo one can send patches to? |
We have a GitHub repository that mirrors our internal development repo. It's currently at https://saxonica.plan.io/projects/saxonmirrorhe/repository In theory, you should be able to check that out and build HE. In practice, I don't think the build parts have been tested recently. That's probably my fault, so sorry about that. In any event, we should be able to apply patches that you provide against that repo. In the longer term, migrating the HE sources to a GitHub repository and improving the build tooling for it is probably a good idea. |
Hi, to solve this SaxonC GC problem I need to increment the refcounted on \Saxon\SaxonProcessor object, but also decrement it later. Is it possible to get some guidance on this or example code of a PHP extension written in C/C++ which does something similar? I see that the zend_object has a variable called zend_refcounted. Also the function Z_COUNTED_P() looks useful too. |
Use the |
Hi, Just to add, the SaxonProcessor object is the one that is getting deleted too early. with the GC_ADDREF it prevents the GC call on it. And it does get GC at the end without the GC_DELREF. |
@ond1
How can you tell? If this is truly the case, then there's some other refcounting bug somewhere, be it in your extension or in php-src. Please compile php-src and your extension with |
Hi, Thanks four your message. I have built PHP from source. But not sure how to build it with my extension. I have dropped my files in the ext directory. But I am thinking I am missing some simple step |
@ond1 It should suffice if you build just your extension with that flag. I suspect the issue is coming from the extension itself. |
Hi, It looks like it's a C++ compiler issue. Please can someone point me to what I am missing? I think there is some option I am missing when I run ./configure. Perhaps with the CC flag. |
@ond1 You don't need to build it with PHP. Just build it like normal and add the |
ok thanks |
I tried the following |
Compiling extension for PHP 8.1. Is his flag for PHP 8.2? |
@ond1 Try |
Thanks. The build worked for the extension. Not seeing any difference in the running of a php script. However with
Is the flag USE_ZEND_ALLOC some debugger option? |
Please ignore my last message as I understand what USE_ZEND_ALLOC is used for. |
Hi @iluuu1994 `void SaxonProcessor_free_storage(zend_object *object) { saxonProcessor_object *obj; obj = (saxonProcessor_object *)((char *)object - SaxonProcessor *saxonProc = obj->saxonProcessor; //efree(obj); - corrupts memory I am failing to understand how and if I should free the saxonProcessor_object given as In the create_handler function I created the obj as follows:
I then changed it to the following:
Any advice is appreciated to understand what I should be doing when the free_obj 'SaxonProcessor_free_storage' function is called. |
That looks fine. If there's a double-free, there's something else also calling |
@ond1 Oh, I didn't look at this well enough. |
Thanks @iluuu1994 for your reply. That would explain it . I took a quick look at the I was just not aware of the |
https://www.phpinternalsbook.com/ is the most up-to-date resource on php-src. |
Description
Hi there,
I am trying to use Saxon to run XSLT transformations from PHP.
Saxon is an XSLT, XPath, and XQuery processor made by Saxonica.
Saxonica produce a PHP extension that allows to call the processor from PHP.
When I run a simple PHP script from the CLI, the transformation works successfully.
When I run a transformation from PHPUnit or Symfony, I encounter a segmentation fault.
The following code:
Resulted in this output:
Which is a successful transformation of that input.xml with the XSL transform.xslt.
However, when I run it inside PHPUnit or Symfony, I encounter an error.
The following code:
Resulted in this output:
But I expected this output instead:
I set up GDB, and attached it to my PHP process, and called the Symfony route again. This produced the following backtrace:
I also produced the following zbacktrace:
I also tried to execute the same code in PHPUnit 9.6.10. It produced the almost identical backtrace:
And the PHPUnit zbacktrace
I am not knowledgeable of PHP's internals, but from the two faillings examples it appears that the issue is present when a PHP method 'loadClass' is called. This causes spl_perform_autoload to run to load a class and to fail deeper in the stack.
Simple test case to reproduce
Following that, I changed my simple test to:
With the following backtrace:
And this short zbacktrace:
So, I can confirm that using the autoloader after using the extension causes the crash.
Now, I am not sure what to do:
How to install the extension
How to build the RPM on Fedora 38:
Then look in results_php-saxon.
PHP Version
PHP 8.2.10
Operating System
Fedora 38 x64-64
The text was updated successfully, but these errors were encountered: