/hphp/tools/check_native_signatures.php
PHP | 367 lines | 312 code | 23 blank | 32 comment | 62 complexity | 3b43f3aae2a5f1dcfa951d72c7f14905 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-2-Clause, BSD-3-Clause, MPL-2.0-no-copyleft-exception, MIT, LGPL-2.0, Apache-2.0
- <?hh
- /*
- +----------------------------------------------------------------------+
- | HipHop for PHP |
- +----------------------------------------------------------------------+
- | Copyright (c) 2014 Facebook, Inc. (http://www.facebook.com) |
- +----------------------------------------------------------------------+
- | This source file is subject to version 3.01 of the PHP license, |
- | that is bundled with this package in the file LICENSE, and is |
- | available through the world-wide-web at the following url: |
- | http://www.php.net/license/3_01.txt |
- | If you did not receive a copy of the PHP license and are unable to |
- | obtain it through the world-wide-web, please send a note to |
- | license@php.net so we can mail you a copy immediately. |
- +----------------------------------------------------------------------+
- */
- /*
- A helper script to check that HNI signatures in a PHP file match how the
- functions are defined in the C++ file.
- Currently only matches functions, so won't match classes/methods.
- */
- // expects argv[1] to be the C++ file, argv[2] to be the PHP file
- if (empty($_SERVER['argv'][2])) {
- fwrite(STDERR, "Usage: {$_SERVER['argv'][0]} <extfile.cpp> <extfile.php>\n");
- exit(1);
- }
- function parse_php_functions(string $file):
- ConstMap<string, Pair<string, ConstVector<string>>> {
- $source = file_get_contents($file);
- if (!$source) {
- return ImmMap {};
- }
- // Don't handle methods yet, so function can't be indented
- $function_regex =
- "#<<[^>]*__Native([^>]*)>>\nfunction +([^(]*)\(([^)]*)\) *: *(.+?);#m";
- $functions = Map {};
- $matches = [];
- if (preg_match_all($function_regex, $source, $matches, PREG_SET_ORDER)) {
- foreach($matches as $match) {
- $nativeArgs = $match[1];
- $name = $match[2];
- if (strpos($nativeArgs, '"ActRec"') !== false) {
- // ActRec functions have a specific structure
- $retType = 'actrec';
- $argTypes = Vector {'actrec'};
- } else {
- $argList = $match[3];
- $retType = explode('<', $match[4], 2)[0];
- $argTypes = Vector {};
- if ($argList) {
- $args = preg_split('/\s*,\s*/', $argList);
- if (count($args) > 7 && (in_array('float', $args)
- || in_array('double', $args))) {
- $retType = 'actrec';
- $argTypes = Vector {'actrec'};
- } else if (count($args) > 15) {
- $retType = 'actrec';
- $argTypes = Vector {'actrec'};
- } else {
- foreach($args as $arg) {
- $type = preg_split('/\s*\$/', $arg)[0];
- $type = explode('<', $type, 2)[0];
- if ($type == '...') {
- // Special case varargs
- $vargTypes = Vector {'int'};
- $vargTypes->addAll($argTypes);
- $vargTypes[] = 'array';
- $argTypes = $vargTypes;
- } else {
- $argTypes[] = $type;
- }
- }
- }
- }
- }
- $functions[strtolower($name)] = Pair { $retType, $argTypes };
- }
- }
- return $functions;
- }
- function parse_cpp_functions(string $file):
- ConstMap<string, Pair<string, ConstVector<string>>> {
- $source = file_get_contents($file);
- if (!$source) {
- return ImmMap {};
- }
- // Don't handle methods yet, so function can't be indented
- $function_regex =
- "#^(?:static )?(\S+) +HHVM_FUNCTION\(([^,)]+)(?:, *)?([^)]*)\)#m";
- $functions = Map {};
- $matches = [];
- if (preg_match_all($function_regex, $source, $matches, PREG_SET_ORDER)) {
- foreach($matches as $match) {
- $name = $match[2];
- $argList = $match[3];
- $retType = $match[1];
- $argTypes = Vector {};
- if ($argList) {
- $args = preg_split('/\s*,\s*/', $argList);
- foreach($args as $arg) {
- $type = preg_split('# */ *#', $arg)[0];
- $type = implode(' ', explode(' ', $type, -1));
- $argTypes[] = $type;
- }
- }
- $functions[strtolower($name)] = Pair { $retType, $argTypes };
- }
- }
- return $functions;
- }
- function parse_php_methods(string $file):
- ConstMap<string, Pair<string, ConstVector<string>>> {
- $source = file_get_contents($file);
- if (!$source) {
- return ImmMap {};
- }
- $class_regex = "#class ([^\\s{/]+)[^{/\\)]*\\{(.*?)\n\\}#ms";
- $method_regex =
- "#<<[^>]*__Native([^>]*)>>\n\\s*.*?function +([^(]*)\(([^)]*)\) *: *(.+?);#m";
- $methods = Map {};
- $classes = [];
- if (preg_match_all($class_regex, $source, $classes, PREG_SET_ORDER)) {
- foreach ($classes as $class) {
- $cname = $class[1];
- $source = $class[2];
- $matches = [];
- if (preg_match_all($method_regex, $source, $matches, PREG_SET_ORDER)) {
- foreach($matches as $match) {
- $nativeArgs = $match[1];
- $mname = $match[2];
- if (strpos($nativeArgs, '"ActRec"') !== false) {
- // ActRec functions have a specific structure
- $retType = 'actrec';
- $argTypes = Vector {'actrec'};
- } else {
- $argList = $match[3];
- $retType = explode('<', $match[4], 2)[0];
- $argTypes = Vector {};
- if ($argList) {
- $args = preg_split('/\s*,\s*/', $argList);
- if (count($args) > 7 && (in_array('float', $args)
- || in_array('double', $args))) {
- $retType = 'actrec';
- $argTypes = Vector {'actrec'};
- } else if (count($args) > 15) {
- $retType = 'actrec';
- $argTypes = Vector {'actrec'};
- } else {
- foreach($args as $arg) {
- $type = preg_split('/\s*\$/', $arg)[0];
- $type = explode('<', $type, 2)[0];
- if ($type == '...') {
- // Special case varargs
- $vargTypes = Vector {'int'};
- $vargTypes->addAll($argTypes);
- $vargTypes[] = 'array';
- $argTypes = $vargTypes;
- } else {
- $argTypes[] = $type;
- }
- }
- }
- }
- }
- $methods[strtolower("$cname::$mname")] = Pair { $retType, $argTypes };
- }
- }
- }
- }
- return $methods;
- }
- function parse_cpp_methods(string $file):
- ConstMap<string, Pair<string, ConstVector<string>>> {
- $source = file_get_contents($file);
- if (!$source) {
- return ImmMap {};
- }
- // Don't handle methods yet, so function can't be indented
- $method_regex =
- "#^(?:static )?(\S+) +HHVM_(?:STATIC_)?METHOD\(([^,)]+),\s+([^,)]+)(?:, *)?([^)]*)\)#m";
- $methods = Map {};
- $matches = [];
- if (preg_match_all($method_regex, $source, $matches, PREG_SET_ORDER)) {
- foreach($matches as $match) {
- $cname = $match[2];
- $mname = $match[3];
- $argList = $match[4];
- $retType = $match[1];
- $argTypes = Vector {};
- if ($argList) {
- $args = preg_split('/\s*,\s*/', $argList);
- foreach($args as $arg) {
- $type = preg_split('# */ *#', $arg)[0];
- $type = implode(' ', explode(' ', $type, -1));
- $argTypes[] = $type;
- }
- }
- $methods[strtolower("$cname::$mname")] = Pair { $retType, $argTypes };
- }
- }
- return $methods;
- }
- function match_return_type(string $php, string $cpp): bool {
- if ($php[0] == '?') {
- $expected = 'Variant';
- } else {
- switch (strtolower($php)) {
- case 'bool':
- case 'boolean':
- $expected = 'bool';
- break;
- case 'int':
- case 'long':
- $expected = 'int64_t';
- break;
- case 'float':
- case 'double':
- $expected = 'double';
- break;
- case 'void':
- $expected = 'void';
- break;
- case 'string':
- $expected = 'String';
- break;
- case 'array':
- $expected = 'Array';
- break;
- case 'resource':
- $expected = 'Resource';
- break;
- case 'mixed':
- case 'callable':
- $expected = 'Variant';
- break;
- case 'actrec':
- $expected = 'TypedValue*';
- break;
- case 'object':
- default:
- $expected = 'Object';
- break;
- }
- }
- // Special case for ints
- if ($cpp == 'int') {
- $cpp = 'int64_t';
- }
- return $cpp == $expected;
- }
- function match_arg_type(string $php, string $cpp): bool {
- if ($php[0] == '@') {
- $php = substr($php, 1);
- }
- if ($php[0] == '?') {
- $expected = 'const Variant&';
- } else {
- switch (strtolower(strtok($php, ' &'))) {
- case 'bool':
- case 'boolean':
- $expected = 'bool';
- break;
- case 'int':
- case 'long':
- $expected = 'int64_t';
- break;
- case 'float':
- case 'double':
- $expected = 'double';
- break;
- case 'void':
- // Shouldn't have void as an argument type
- return false;
- case 'string':
- $expected = 'const String&';
- break;
- case 'array':
- $expected = 'const Array&';
- break;
- case 'resource':
- $expected = 'const Resource&';
- break;
- case 'mixed':
- case 'callable':
- $expected = 'const Variant&';
- break;
- case 'actrec':
- $expected = 'ActRec*';
- break;
- case 'object':
- default:
- $expected = 'const Object&';
- break;
- }
- }
- $cpp = trim($cpp);
- // Special case for ints
- if ($cpp == 'int') {
- $cpp = 'int64_t';
- }
- return $cpp == $expected;
- }
- function check_types(ConstMap<string, Pair<string, ConstVector<string>>> $php,
- ConstMap<string, Pair<string, ConstVector<string>>> $cpp):
- bool {
- $errored = false;
- foreach($php as $name => $types) {
- if (!isset($cpp[$name])) {
- $errored = true;
- printf("Unimplemented native function '%s'\n", $name);
- continue;
- }
- $cppTypes = $cpp[$name];
- if (!match_return_type($types[0], $cppTypes[0])) {
- $errored = true;
- printf("Mismatched return type for function '%s'. PHP: %s C++: %s\n",
- $name, $types[0], $cppTypes[0]);
- }
- if ($types[1]->count() != $cppTypes[1]->count()) {
- $errored = true;
- printf("Unequal number of arguments for function '%s'\n", $name);
- continue;
- }
- foreach($types[1] as $idx => $t) {
- if (!match_arg_type($t, $cppTypes[1][$idx])) {
- $errored = true;
- printf("Mismatched argument type for function '%s' at index '%d'."
- . " PHP: %s C++: %s\n", $name, $idx, $t, $cppTypes[1][$idx]);
- }
- }
- }
- return $errored;
- }
- $phpFuncs = parse_php_functions($_SERVER['argv'][2]);
- $cppFuncs = parse_cpp_functions($_SERVER['argv'][1]);
- $cppMeths = parse_cpp_methods($_SERVER['argv'][1]);
- $phpMeths = parse_php_methods($_SERVER['argv'][2]);
- $funcs = check_types($phpFuncs, $cppFuncs);
- $meths = check_types($phpMeths, $cppMeths);
- if ($funcs || $meths) {
- echo "See https://github.com/facebook/hhvm/wiki/Extension-API for what types",
- " map to what\n";
- }