Add aws sqs rust sdk to fbsource/third-party
[hiphop-php.git] / hphp / tools / check_native_signatures.php
blob09dccb0b0bb2f3982fd9b7744c94ac9b19becf3c
1 <?hh
2 /*
3 +----------------------------------------------------------------------+
4 | HipHop for PHP |
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);
31 if (!$source) {
32 return ImmMap {};
35 // Don't handle methods yet, so function can't be indented
36 $function_regex =
37 "#<<[^>]*__Native([^>]*)>>\nfunction +([^(]*)\(([^)]*)\) *: *(.+?);#m";
39 $functions = Map {};
40 $matches = vec[];
41 if (preg_match_all($function_regex, $source, $matches, PREG_SET_ORDER)) {
42 foreach($matches as $match) {
43 $nativeArgs = $match[1];
44 $name = $match[2];
45 if (strpos($nativeArgs, '"ActRec"') !== false) {
46 // ActRec functions have a specific structure
47 $retType = 'actrec';
48 $argTypes = Vector {'actrec'};
49 } else {
50 $argList = $match[3];
51 $retType = explode('<', $match[4], 2)[0];
52 $argTypes = Vector {};
53 if ($argList) {
54 $args = preg_split('/\s*,\s*/', $argList);
55 if (count($args) > 7 && (in_array('float', $args)
56 || in_array('double', $args))) {
57 $retType = 'actrec';
58 $argTypes = Vector {'actrec'};
59 } else if (count($args) > 15) {
60 $retType = 'actrec';
61 $argTypes = Vector {'actrec'};
62 } else {
63 foreach($args as $arg) {
64 $type = preg_split('/\s*\$/', $arg)[0];
65 $type = explode('<', $type, 2)[0];
66 if ($type == '...') {
67 // Special case varargs
68 $vargTypes = Vector {'int'};
69 $vargTypes->addAll($argTypes);
70 $vargTypes[] = 'array';
71 $argTypes = $vargTypes;
72 } else {
73 $argTypes[] = $type;
79 $functions[$name] = Pair { $retType, $argTypes };
82 return $functions;
85 function parse_cpp_functions(string $file):
86 ConstMap<string, Pair<string, ConstVector<string>>> {
87 $source = file_get_contents($file);
88 if (!$source) {
89 return ImmMap {};
92 // Don't handle methods yet, so function can't be indented
93 $function_regex =
94 "#^(?:static )?(\S+) +HHVM_FUNCTION\(([^,)]+)(?:, *)?([^)]*)\)#m";
96 $functions = Map {};
97 $matches = vec[];
99 if (preg_match_all($function_regex, $source, $matches, PREG_SET_ORDER)) {
100 foreach($matches as $match) {
101 $name = $match[2];
102 $argList = $match[3];
103 $retType = $match[1];
104 $argTypes = Vector {};
105 if ($argList) {
106 $args = preg_split('/\s*,\s*/', $argList);
107 foreach($args as $arg) {
108 $type = preg_split('# */ *#', $arg)[0];
109 $type = implode(' ', explode(' ', $type, -1));
110 $argTypes[] = $type;
113 $functions[$name] = Pair { $retType, $argTypes };
116 return $functions;
119 function parse_php_methods(string $file):
120 ConstMap<string, Pair<string, ConstVector<string>>> {
121 $source = file_get_contents($file);
122 if (!$source) {
123 return ImmMap {};
126 $class_regex = "#class ([^\\s{/]+)[^{/\\)]*\\{(.*?)\n\\}#ms";
127 $method_regex =
128 "#<<[^>]*__Native([^>]*)>>\n\\s*.*?function +([^(]*)\(([^)]*)\) *: *(.+?);#m";
130 $methods = Map {};
131 $classes = vec[];
133 if (preg_match_all($class_regex, $source, $classes, PREG_SET_ORDER)) {
134 foreach ($classes as $class) {
135 $cname = $class[1];
136 $source = $class[2];
137 $matches = vec[];
138 if (preg_match_all($method_regex, $source, $matches, PREG_SET_ORDER)) {
139 foreach($matches as $match) {
140 $nativeArgs = $match[1];
141 $mname = $match[2];
142 if (strpos($nativeArgs, '"ActRec"') !== false) {
143 // ActRec functions have a specific structure
144 $retType = 'actrec';
145 $argTypes = Vector {'actrec'};
146 } else {
147 $argList = $match[3];
148 $retType = explode('<', $match[4], 2)[0];
149 $argTypes = Vector {};
150 if ($argList) {
151 $args = preg_split('/\s*,\s*/', $argList);
152 if (count($args) > 7 && (in_array('float', $args)
153 || in_array('double', $args))) {
154 $retType = 'actrec';
155 $argTypes = Vector {'actrec'};
156 } else if (count($args) > 15) {
157 $retType = 'actrec';
158 $argTypes = Vector {'actrec'};
159 } else {
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;
169 } else {
170 $argTypes[] = $type;
176 $methods["$cname::$mname"] = Pair { $retType, $argTypes };
182 return $methods;
185 function parse_cpp_methods(string $file):
186 ConstMap<string, Pair<string, ConstVector<string>>> {
187 $source = file_get_contents($file);
188 if (!$source) {
189 return ImmMap {};
192 // Don't handle methods yet, so function can't be indented
193 $method_regex =
194 "#^(?:static )?(\S+) +HHVM_(?:STATIC_)?METHOD\(([^,)]+),\s+([^,)]+)(?:, *)?([^)]*)\)#m";
196 $methods = Map {};
197 $matches = dict[];
199 if (preg_match_all($method_regex, $source, $matches, PREG_SET_ORDER)) {
200 foreach($matches as $match) {
201 $cname = $match[2];
202 $mname = $match[3];
203 $argList = $match[4];
204 $retType = $match[1];
205 $argTypes = Vector {};
206 if ($argList) {
207 $args = preg_split('/\s*,\s*/', $argList);
208 foreach($args as $arg) {
209 $type = preg_split('# */ *#', $arg)[0];
210 $type = implode(' ', explode(' ', $type, -1));
211 $argTypes[] = $type;
214 $methods["$cname::$mname"] = Pair { $retType, $argTypes };
217 return $methods;
220 function match_return_type(string $php, string $cpp): bool {
221 if ($php[0] == '?') {
222 $expected = 'Variant';
223 } else {
224 switch (strtolower($php)) {
225 case 'bool':
226 case 'boolean':
227 $expected = 'bool';
228 break;
229 case 'int':
230 case 'long':
231 $expected = 'int64_t';
232 break;
233 case 'float':
234 case 'double':
235 $expected = 'double';
236 break;
237 case 'void':
238 $expected = 'void';
239 break;
240 case 'string':
241 $expected = 'String';
242 break;
243 case 'array':
244 $expected = 'Array';
245 break;
246 case 'resource':
247 $expected = 'Resource';
248 break;
249 case 'mixed':
250 case 'callable':
251 $expected = 'Variant';
252 break;
253 case 'actrec':
254 $expected = 'TypedValue*';
255 break;
256 case 'object':
257 default:
258 $expected = 'Object';
259 break;
262 // Special case for ints
263 if ($cpp == 'int') {
264 $cpp = 'int64_t';
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&';
275 } else {
276 switch (strtolower(strtok($php, ' &'))) {
277 case 'bool':
278 case 'boolean':
279 $expected = 'bool';
280 break;
281 case 'int':
282 case 'long':
283 $expected = 'int64_t';
284 break;
285 case 'float':
286 case 'double':
287 $expected = 'double';
288 break;
289 case 'void':
290 // Shouldn't have void as an argument type
291 return false;
292 case 'string':
293 $expected = 'const String&';
294 break;
295 case 'array':
296 $expected = 'const Array&';
297 break;
298 case 'resource':
299 $expected = 'const Resource&';
300 break;
301 case 'mixed':
302 case 'callable':
303 $expected = 'const Variant&';
304 break;
305 case 'actrec':
306 $expected = 'ActRec*';
307 break;
308 case 'object':
309 default:
310 $expected = 'const Object&';
311 break;
314 $cpp = trim($cpp);
315 // Special case for ints
316 if ($cpp == 'int') {
317 $cpp = 'int64_t';
319 return $cpp == $expected;
322 function check_types(ConstMap<string, Pair<string, ConstVector<string>>> $php,
323 ConstMap<string, Pair<string, ConstVector<string>>> $cpp):
324 bool {
325 $errored = false;
326 foreach($php as $name => $types) {
327 if (!isset($cpp[$name])) {
328 $errored = true;
329 printf("Unimplemented native function '%s'\n", $name);
330 continue;
332 $cppTypes = $cpp[$name];
333 if (!match_return_type($types[0], $cppTypes[0])) {
334 $errored = true;
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()) {
339 $errored = true;
340 printf("Unequal number of arguments for function '%s'\n", $name);
341 continue;
343 foreach($types[1] as $idx => $t) {
344 if (!match_arg_type($t, $cppTypes[1][$idx])) {
345 $errored = true;
346 printf("Mismatched argument type for function '%s' at index '%d'."
347 . " PHP: %s C++: %s\n", $name, $idx, $t, $cppTypes[1][$idx]);
351 return $errored;
354 <<__EntryPoint>>
355 function main() {
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");
360 exit(1);
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",
374 " map to what\n";