3 +----------------------------------------------------------------------+
5 +----------------------------------------------------------------------+
6 | Copyright (c) 2014 Facebook, Inc. (http://www.facebook.com) |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 A helper script to check that HNI signatures in a PHP file match how the
19 functions are defined in the C++ file.
21 Hack method names are case sensitive.
23 Classes and toplevel functions in Hack are not yet case sensitive,
24 but this tool intentionally requires matching case between PHP bindings
25 and C++ implementations.
28 function parse_php_functions(string $file):
29 ConstMap
<string, Pair
<string, ConstVector
<string>>> {
30 $source = file_get_contents($file);
35 // Don't handle methods yet, so function can't be indented
37 "#<<[^>]*__Native([^>]*)>>\nfunction +([^(]*)\(([^)]*)\) *: *(.+?);#m";
41 if (preg_match_all($function_regex, $source, $matches, PREG_SET_ORDER
)) {
42 foreach($matches as $match) {
43 $nativeArgs = $match[1];
45 if (strpos($nativeArgs, '"ActRec"') !== false) {
46 // ActRec functions have a specific structure
48 $argTypes = Vector
{'actrec'};
51 $retType = explode('<', $match[4], 2)[0];
52 $argTypes = Vector
{};
54 $args = preg_split('/\s*,\s*/', $argList);
55 if (count($args) > 7 && (in_array('float', $args)
56 ||
in_array('double', $args))) {
58 $argTypes = Vector
{'actrec'};
59 } else if (count($args) > 15) {
61 $argTypes = Vector
{'actrec'};
63 foreach($args as $arg) {
64 $type = preg_split('/\s*\$/', $arg)[0];
65 $type = explode('<', $type, 2)[0];
67 // Special case varargs
68 $vargTypes = Vector
{'int'};
69 $vargTypes->addAll($argTypes);
70 $vargTypes[] = 'array';
71 $argTypes = $vargTypes;
79 $functions[$name] = Pair
{ $retType, $argTypes };
85 function parse_cpp_functions(string $file):
86 ConstMap
<string, Pair
<string, ConstVector
<string>>> {
87 $source = file_get_contents($file);
92 // Don't handle methods yet, so function can't be indented
94 "#^(?:static )?(\S+) +HHVM_FUNCTION\(([^,)]+)(?:, *)?([^)]*)\)#m";
99 if (preg_match_all($function_regex, $source, $matches, PREG_SET_ORDER
)) {
100 foreach($matches as $match) {
102 $argList = $match[3];
103 $retType = $match[1];
104 $argTypes = Vector
{};
106 $args = preg_split('/\s*,\s*/', $argList);
107 foreach($args as $arg) {
108 $type = preg_split('# */ *#', $arg)[0];
109 $type = implode(' ', explode(' ', $type, -1));
113 $functions[$name] = Pair
{ $retType, $argTypes };
119 function parse_php_methods(string $file):
120 ConstMap
<string, Pair
<string, ConstVector
<string>>> {
121 $source = file_get_contents($file);
126 $class_regex = "#class ([^\\s{/]+)[^{/\\)]*\\{(.*?)\n\\}#ms";
128 "#<<[^>]*__Native([^>]*)>>\n\\s*.*?function +([^(]*)\(([^)]*)\) *: *(.+?);#m";
133 if (preg_match_all($class_regex, $source, $classes, PREG_SET_ORDER
)) {
134 foreach ($classes as $class) {
138 if (preg_match_all($method_regex, $source, $matches, PREG_SET_ORDER
)) {
139 foreach($matches as $match) {
140 $nativeArgs = $match[1];
142 if (strpos($nativeArgs, '"ActRec"') !== false) {
143 // ActRec functions have a specific structure
145 $argTypes = Vector
{'actrec'};
147 $argList = $match[3];
148 $retType = explode('<', $match[4], 2)[0];
149 $argTypes = Vector
{};
151 $args = preg_split('/\s*,\s*/', $argList);
152 if (count($args) > 7 && (in_array('float', $args)
153 ||
in_array('double', $args))) {
155 $argTypes = Vector
{'actrec'};
156 } else if (count($args) > 15) {
158 $argTypes = Vector
{'actrec'};
160 foreach($args as $arg) {
161 $type = preg_split('/\s*\$/', $arg)[0];
162 $type = explode('<', $type, 2)[0];
163 if ($type == '...') {
164 // Special case varargs
165 $vargTypes = Vector
{'int'};
166 $vargTypes->addAll($argTypes);
167 $vargTypes[] = 'array';
168 $argTypes = $vargTypes;
176 $methods["$cname::$mname"] = Pair
{ $retType, $argTypes };
185 function parse_cpp_methods(string $file):
186 ConstMap
<string, Pair
<string, ConstVector
<string>>> {
187 $source = file_get_contents($file);
192 // Don't handle methods yet, so function can't be indented
194 "#^(?:static )?(\S+) +HHVM_(?:STATIC_)?METHOD\(([^,)]+),\s+([^,)]+)(?:, *)?([^)]*)\)#m";
199 if (preg_match_all($method_regex, $source, $matches, PREG_SET_ORDER
)) {
200 foreach($matches as $match) {
203 $argList = $match[4];
204 $retType = $match[1];
205 $argTypes = Vector
{};
207 $args = preg_split('/\s*,\s*/', $argList);
208 foreach($args as $arg) {
209 $type = preg_split('# */ *#', $arg)[0];
210 $type = implode(' ', explode(' ', $type, -1));
214 $methods["$cname::$mname"] = Pair
{ $retType, $argTypes };
220 function match_return_type(string $php, string $cpp): bool {
221 if ($php[0] == '?') {
222 $expected = 'Variant';
224 switch (strtolower($php)) {
231 $expected = 'int64_t';
235 $expected = 'double';
241 $expected = 'String';
247 $expected = 'Resource';
251 $expected = 'Variant';
254 $expected = 'TypedValue*';
258 $expected = 'Object';
262 // Special case for ints
266 return $cpp == $expected;
269 function match_arg_type(string $php, string $cpp): bool {
270 if ($php[0] == '@') {
271 $php = substr($php, 1);
273 if ($php[0] == '?') {
274 $expected = 'const Variant&';
276 switch (strtolower(strtok($php, ' &'))) {
283 $expected = 'int64_t';
287 $expected = 'double';
290 // Shouldn't have void as an argument type
293 $expected = 'const String&';
296 $expected = 'const Array&';
299 $expected = 'const Resource&';
303 $expected = 'const Variant&';
306 $expected = 'ActRec*';
310 $expected = 'const Object&';
315 // Special case for ints
319 return $cpp == $expected;
322 function check_types(ConstMap
<string, Pair
<string, ConstVector
<string>>> $php,
323 ConstMap
<string, Pair
<string, ConstVector
<string>>> $cpp):
326 foreach($php as $name => $types) {
327 if (!isset($cpp[$name])) {
329 printf("Unimplemented native function '%s'\n", $name);
332 $cppTypes = $cpp[$name];
333 if (!match_return_type($types[0], $cppTypes[0])) {
335 printf("Mismatched return type for function '%s'. PHP: %s C++: %s\n",
336 $name, $types[0], $cppTypes[0]);
338 if ($types[1]->count() != $cppTypes[1]->count()) {
340 printf("Unequal number of arguments for function '%s'\n", $name);
343 foreach($types[1] as $idx => $t) {
344 if (!match_arg_type($t, $cppTypes[1][$idx])) {
346 printf("Mismatched argument type for function '%s' at index '%d'."
347 . " PHP: %s C++: %s\n", $name, $idx, $t, $cppTypes[1][$idx]);
356 $argv = $_SERVER['argv'];
357 // expects argv[1] to be the C++ file, argv[2] to be the PHP file
358 if (!isset($argv[2]) ||
!$argv[2]) {
359 fwrite(STDERR
, "Usage: {$argv[0]} <extfile.cpp> <extfile.php>\n");
363 $phpFuncs = parse_php_functions($argv[2]);
364 $cppFuncs = parse_cpp_functions($argv[1]);
366 $cppMeths = parse_cpp_methods($argv[1]);
367 $phpMeths = parse_php_methods($argv[2]);
369 $funcs = check_types($phpFuncs, $cppFuncs);
370 $meths = check_types($phpMeths, $cppMeths);
372 if ($funcs ||
$meths) {
373 echo "See https://github.com/facebook/hhvm/wiki/Extension-API for what types",