Diving into PHP Internals: A quick attempt to understand that weird error

Weird Error

One of my colleagues sent the following error message in slack

That’s weird. I tried for string type and it’s the same.

<?php
function fs(string $a) {
print $a;
}
fs(“hello”);
?>

This is what i got

Catchable fatal error: Argument 1 passed to fs() must be an instance of string, string given ….

Why? It’s not making any sense. The next step is to understand why this is happening and if others have come across this. I was wondering if there’s a fix. People are baffled with the same error message.

People gave sane answers — that’s what it is — “don’t think hard” is what i understood from the answers. Use PHP 7 and you won’t get into that issue. PHP5 has that issue.

As of PHP 7.0 type declarations allow scalar types, so these types are now available: self, array, callable, bool, float, int, string. The first three were available in PHP 5, but the last four are new in PHP 7. If you use anything else (e.g. integer or boolean) that will be interpreted as a class name.

I wasn’t happy. Just because “PHP manual says” so doesn’t make sense. I needed to dig into this.

PHP Source and Build

This was the first time, I looked into php source code. I downloaded php code from github but had no idea on what and how. There’s no ./configure file as per instructions, I was stuck in the very first step. I need to build php locally and then look around and locate where this message is coming from. Then only (if luck allows) i might understand what’s going on and might be able to fix. Everything was blackbox to me. I haven’t looked at C code since my college days— decade ago. I was happy that I could make some sense of the code, but compiler is something i haven’t studied yet. I don’t know what are prerequisites to understand php source code or to begin with reading php source code.

One site lead to another and then another and landed here. Next I downloaded http://php.net/downloads.php, which i was able to build.

I know that the build time has to be less if i want to do hit-and-trial of finding the source of error, otherwise i would be wasting my time, energy and would probably never come back to this.

To create a build that only contains the minimal amount of extensions use the “--disable-all” option
./configure --disable-all
./make

This error seems part of core, so it’s good to have minimal build with minimal extensions. It’s around a minute time to build; it looked practical to make this attempt. After make, PHP cli version is built at sapi\cli\php. And the build was successful with minimal version.

I ran my test.php with the locally built php and still the same case :(

Futile Exploration

The next was to find out what’s going on in the compiling process. I was lost, i didn’t know what to look for, where to look for. I was just randomly going through the C code and there were lots and lots of files and folders insides folders.

For some reason, I was looking for some files with names containing main. I saw php_main.h and found number of interesting functions. php_execute_script seemed right to start my journey.

Content from "php_main.h"

I dived into php_execute_script in main.c and added random printf text here and there. I saw my text messages in terminal when i executed my test.php file in cli — a smile to start with. That wasn’t enough. I need to understand a little more if i have to find and fix that error. It appeared i need to deep-dive into php internals, zend engine, compilers, opcodes, …

I am starting to learn some jargons. Now I needed to see how opcodes work, perhaps listing the opcodes would point to that error. That might lead to the position where that function call is being made. I was making assumptions.

I am starting to feel the power of PHP now since everything is C. I remember i used to complain about how PHP is not that good, it’s type system has flaws and this and that. I never thought of looking into the insides. There’s big love-hate relationship with php.

Exploring Opcodes with Vulcan Logic Dissembler

“Opcode” search lead to “VLD”, which dumps PHP opcodes. Exciting.

I downloaded VLD source, compiled and used that to see the opcodes. I copied the vld.so to the test folder where test.php is present and loaded the .so file locally

/Users/anjesh/Dev/source/php-5.6.30/sapi/cli/php -dextension=./vld.so -dvld.active=1 test.php

I managed to run it and saw what these opcodes are.

Opcodes outputs using vld.so

It was interesting. I was still lost.

Function call codes and random printf

Julien Pauli talked about function calls with snippets of source codes. I felt i was close to something — now that i seemed to know where that call is being made. But the problem is with the arguments being passed to the function, and the code was still hard to follow.

Function call code from http://jpauli.github.io/2015/01/22/on-php-function-calls.html

That article code lead to zend_vm_execute.c file and saw execute_ex loop function, which was mentioned somewhere in the internet.

I was putting printf messages here and there hoping to find that location from where that message is being printed.

My random printf messages taking over PHP cli outputs

More search, more exploration

Finally after digging code for 4–5 hours, it occurred to me after 2 days — “why don’t i search for the string an instance of from the error message?”. That was present in a number of phpt test files and then one result from zend_execute.c caught my eyes.

Next i searched for the function zend_verify_arg_class_kind where this function is called. Finally zend_execute.c seems to have the answer to the mystery.

I was reading codes, adding more printf here and there to see which if-else code is being executed.

Finally the following change got reflected in the output.

Voila, I was into something. I still haven’t understood how things work , but i was able to make changes in the right place. I changed the content of that weird error message.

And the ultimate fix

After some exploration, i saw where that two versions of string in the error message are coming from. And both these names are same when i compared them.

Printing type names in zend_execute.c

Just one string comparison function does the job and that weird message is no more.

Final fix in zend_execute.c using strcmp (string comparison function)

Here’s a final example to show that error message doesn’t appear anymore if the types match.

<?php
function fs(string $a) {
print $a . "\n";
}
function fsbool(boolean $a) {
print $a . "\n";
}
fsbool(true);
fs("hello");
fs(4);
?>

Now i can sleep in peace. No more weird mysterious error.

I don’t know if this is the right fix. I don’t know the implication of that extra 2 lines in other parts of the code.

My journey ends here.

Final notes

I was able to write this blog because i was taking notes all the time. I didn’t intend to write this blog when i started the journey. I was looking for some quick code exploring process for the first timers without bogging down into too much details but couldn’t find any. This blog might give sense to wanna-be-explorers on how i did it but yours might be totally different from mine. For the interested ones, see my notes below.