Skip to content

Commit b765aaa

Browse files
committed
Implement GH-10024: support linting multiple files at once using php -l
This is supported in both the CLI and CGI modes. For CLI this required little changes. For CGI, the tricky part was that the options parsing happens inside the loop. This means that options passed after the -l flag were previously simply ignored. As we now re-enter the loop we would parse the options again, and if they are handled but don't set the script name, then CGI will think you want to read from standard in. To keep the same "don't parse options" behaviour I simply wrapped the options handling inside an if.
1 parent 0c0b41d commit b765aaa

File tree

4 files changed

+340
-100
lines changed

4 files changed

+340
-100
lines changed

sapi/cgi/cgi_main.c

Lines changed: 105 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2266,107 +2266,109 @@ consult the installation file that came with this distribution, or visit \n\
22662266
init_request_info(request);
22672267

22682268
if (!cgi && !fastcgi) {
2269-
while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) {
2270-
switch (c) {
2271-
2272-
case 'a': /* interactive mode */
2273-
printf("Interactive mode enabled\n\n");
2274-
fflush(stdout);
2275-
break;
2276-
2277-
case 'C': /* don't chdir to the script directory */
2278-
SG(options) |= SAPI_OPTION_NO_CHDIR;
2279-
break;
2280-
2281-
case 'e': /* enable extended info output */
2282-
CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;
2283-
break;
2284-
2285-
case 'f': /* parse file */
2286-
if (script_file) {
2287-
efree(script_file);
2288-
}
2289-
script_file = estrdup(php_optarg);
2290-
no_headers = 1;
2291-
break;
2269+
if (behavior != PHP_MODE_LINT) {
2270+
while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) {
2271+
switch (c) {
2272+
2273+
case 'a': /* interactive mode */
2274+
printf("Interactive mode enabled\n\n");
2275+
fflush(stdout);
2276+
break;
2277+
2278+
case 'C': /* don't chdir to the script directory */
2279+
SG(options) |= SAPI_OPTION_NO_CHDIR;
2280+
break;
2281+
2282+
case 'e': /* enable extended info output */
2283+
CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;
2284+
break;
2285+
2286+
case 'f': /* parse file */
2287+
if (script_file) {
2288+
efree(script_file);
2289+
}
2290+
script_file = estrdup(php_optarg);
2291+
no_headers = 1;
2292+
break;
22922293

2293-
case 'i': /* php info & quit */
2294-
if (script_file) {
2295-
efree(script_file);
2296-
}
2297-
if (php_request_startup() == FAILURE) {
2298-
SG(server_context) = NULL;
2299-
php_module_shutdown();
2300-
free(bindpath);
2301-
return FAILURE;
2302-
}
2303-
if (no_headers) {
2294+
case 'i': /* php info & quit */
2295+
if (script_file) {
2296+
efree(script_file);
2297+
}
2298+
if (php_request_startup() == FAILURE) {
2299+
SG(server_context) = NULL;
2300+
php_module_shutdown();
2301+
free(bindpath);
2302+
return FAILURE;
2303+
}
2304+
if (no_headers) {
2305+
SG(headers_sent) = 1;
2306+
SG(request_info).no_headers = 1;
2307+
}
2308+
php_print_info(0xFFFFFFFF);
2309+
php_request_shutdown((void *) 0);
2310+
fcgi_shutdown();
2311+
exit_status = 0;
2312+
goto out;
2313+
2314+
case 'l': /* syntax check mode */
2315+
no_headers = 1;
2316+
behavior = PHP_MODE_LINT;
2317+
break;
2318+
2319+
case 'm': /* list compiled in modules */
2320+
if (script_file) {
2321+
efree(script_file);
2322+
}
2323+
SG(headers_sent) = 1;
2324+
php_printf("[PHP Modules]\n");
2325+
print_modules();
2326+
php_printf("\n[Zend Modules]\n");
2327+
print_extensions();
2328+
php_printf("\n");
2329+
php_output_end_all();
2330+
fcgi_shutdown();
2331+
exit_status = 0;
2332+
goto out;
2333+
2334+
case 'q': /* do not generate HTTP headers */
2335+
no_headers = 1;
2336+
break;
2337+
2338+
case 'v': /* show php version & quit */
2339+
if (script_file) {
2340+
efree(script_file);
2341+
}
2342+
no_headers = 1;
2343+
if (php_request_startup() == FAILURE) {
2344+
SG(server_context) = NULL;
2345+
php_module_shutdown();
2346+
free(bindpath);
2347+
return FAILURE;
2348+
}
23042349
SG(headers_sent) = 1;
23052350
SG(request_info).no_headers = 1;
2306-
}
2307-
php_print_info(0xFFFFFFFF);
2308-
php_request_shutdown((void *) 0);
2309-
fcgi_shutdown();
2310-
exit_status = 0;
2311-
goto out;
2312-
2313-
case 'l': /* syntax check mode */
2314-
no_headers = 1;
2315-
behavior = PHP_MODE_LINT;
2316-
break;
2317-
2318-
case 'm': /* list compiled in modules */
2319-
if (script_file) {
2320-
efree(script_file);
2321-
}
2322-
SG(headers_sent) = 1;
2323-
php_printf("[PHP Modules]\n");
2324-
print_modules();
2325-
php_printf("\n[Zend Modules]\n");
2326-
print_extensions();
2327-
php_printf("\n");
2328-
php_output_end_all();
2329-
fcgi_shutdown();
2330-
exit_status = 0;
2331-
goto out;
2332-
2333-
case 'q': /* do not generate HTTP headers */
2334-
no_headers = 1;
2335-
break;
2336-
2337-
case 'v': /* show php version & quit */
2338-
if (script_file) {
2339-
efree(script_file);
2340-
}
2341-
no_headers = 1;
2342-
if (php_request_startup() == FAILURE) {
2343-
SG(server_context) = NULL;
2344-
php_module_shutdown();
2345-
free(bindpath);
2346-
return FAILURE;
2347-
}
2348-
SG(headers_sent) = 1;
2349-
SG(request_info).no_headers = 1;
2350-
#if ZEND_DEBUG
2351-
php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
2352-
#else
2353-
php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
2354-
#endif
2355-
php_request_shutdown((void *) 0);
2356-
fcgi_shutdown();
2357-
exit_status = 0;
2358-
goto out;
2359-
2360-
case 'w':
2361-
behavior = PHP_MODE_STRIP;
2362-
break;
2363-
2364-
case 'z': /* load extension file */
2365-
zend_load_extension(php_optarg);
2366-
break;
2367-
2368-
default:
2369-
break;
2351+
#if ZEND_DEBUG
2352+
php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
2353+
#else
2354+
php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
2355+
#endif
2356+
php_request_shutdown((void *) 0);
2357+
fcgi_shutdown();
2358+
exit_status = 0;
2359+
goto out;
2360+
2361+
case 'w':
2362+
behavior = PHP_MODE_STRIP;
2363+
break;
2364+
2365+
case 'z': /* load extension file */
2366+
zend_load_extension(php_optarg);
2367+
break;
2368+
2369+
default:
2370+
break;
2371+
}
23702372
}
23712373
}
23722374

@@ -2504,7 +2506,6 @@ consult the installation file that came with this distribution, or visit \n\
25042506
PG(during_request_startup) = 0;
25052507
if (php_lint_script(&file_handle) == SUCCESS) {
25062508
zend_printf("No syntax errors detected in %s\n", ZSTR_VAL(file_handle.filename));
2507-
exit_status = 0;
25082509
} else {
25092510
zend_printf("Errors parsing %s\n", ZSTR_VAL(file_handle.filename));
25102511
exit_status = -1;
@@ -2571,6 +2572,11 @@ consult the installation file that came with this distribution, or visit \n\
25712572
}
25722573
}
25732574
}
2575+
if (behavior == PHP_MODE_LINT && argc - 1 > php_optind) {
2576+
php_optind++;
2577+
script_file = NULL;
2578+
continue;
2579+
}
25742580
break;
25752581
}
25762582

sapi/cgi/tests/012.phpt

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
--TEST--
2+
multiple files syntax check
3+
--SKIPIF--
4+
<?php include "skipif.inc"; ?>
5+
--INI--
6+
display_errors=stdout
7+
--FILE--
8+
<?php
9+
include "include.inc";
10+
11+
function run_and_output($cmd) {
12+
if (!defined("PHP_WINDOWS_VERSION_MAJOR")) {
13+
$cmd .= " 2>/dev/null";
14+
}
15+
exec($cmd, $output, $exit_code);
16+
print_r($output);
17+
var_dump($exit_code);
18+
}
19+
20+
$php = get_cgi_path();
21+
reset_env_vars();
22+
23+
$filename_good = __DIR__."/012_good.test.php";
24+
$filename_good_escaped = escapeshellarg($filename_good);
25+
$filename_bad = __DIR__."/012_bad.test.php";
26+
$filename_bad_escaped = escapeshellarg($filename_bad);
27+
28+
$code = '
29+
<?php
30+
31+
echo "hi";
32+
';
33+
34+
file_put_contents($filename_good, $code);
35+
36+
run_and_output("$php -n -l $filename_good_escaped $filename_good_escaped");
37+
run_and_output("$php -n -l $filename_good_escaped some.unknown $filename_good_escaped");
38+
39+
$code = '
40+
<?php
41+
42+
class test
43+
private $var;
44+
}
45+
46+
?>
47+
';
48+
49+
file_put_contents($filename_bad, $code);
50+
51+
run_and_output("$php -n -l $filename_good_escaped $filename_bad_escaped $filename_good_escaped");
52+
run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped");
53+
run_and_output("$php -n -l $filename_bad_escaped some.unknown $filename_bad_escaped");
54+
run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped some.unknown");
55+
56+
echo "Done\n";
57+
?>
58+
--CLEAN--
59+
<?php
60+
@unlink($filename_good);
61+
@unlink($filename_bad);
62+
?>
63+
--EXPECTF--
64+
Array
65+
(
66+
[0] => No syntax errors detected in %s012_good.test.php
67+
[1] => No syntax errors detected in %s012_good.test.php
68+
)
69+
int(0)
70+
Array
71+
(
72+
[0] => No syntax errors detected in %s012_good.test.php
73+
[1] => No input file specified.
74+
)
75+
int(255)
76+
Array
77+
(
78+
[0] => No syntax errors detected in %s012_good.test.php
79+
[1] => <br />
80+
[2] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
81+
[3] => Errors parsing %s012_bad.test.php
82+
[4] => No syntax errors detected in %s012_good.test.php
83+
)
84+
int(255)
85+
Array
86+
(
87+
[0] => <br />
88+
[1] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
89+
[2] => Errors parsing %s012_bad.test.php
90+
[3] => <br />
91+
[4] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
92+
[5] => Errors parsing %s012_bad.test.php
93+
)
94+
int(255)
95+
Array
96+
(
97+
[0] => <br />
98+
[1] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
99+
[2] => Errors parsing %s012_bad.test.php
100+
[3] => No input file specified.
101+
)
102+
int(255)
103+
Array
104+
(
105+
[0] => <br />
106+
[1] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
107+
[2] => Errors parsing %s012_bad.test.php
108+
[3] => <br />
109+
[4] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
110+
[5] => Errors parsing %s012_bad.test.php
111+
[6] => No input file specified.
112+
)
113+
int(255)
114+
Done

sapi/cli/php_cli.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,10 @@ static int do_cli(int argc, char **argv) /* {{{ */
737737
break;
738738
}
739739
behavior=PHP_MODE_LINT;
740+
/* We want to set the error exit status if at least one lint failed.
741+
* If all were successful we set the exit status to 0.
742+
* We already set EG(exit_status) here such that only failures set the exit status. */
743+
EG(exit_status) = 0;
740744
break;
741745

742746
case 'q': /* do not generate HTTP headers */
@@ -967,7 +971,6 @@ static int do_cli(int argc, char **argv) /* {{{ */
967971
case PHP_MODE_LINT:
968972
if (php_lint_script(&file_handle) == SUCCESS) {
969973
zend_printf("No syntax errors detected in %s\n", php_self);
970-
EG(exit_status) = 0;
971974
} else {
972975
zend_printf("Errors parsing %s\n", php_self);
973976
EG(exit_status) = 255;
@@ -1137,6 +1140,12 @@ static int do_cli(int argc, char **argv) /* {{{ */
11371140
if (translated_path) {
11381141
free(translated_path);
11391142
}
1143+
if (behavior == PHP_MODE_LINT && argc > php_optind && strcmp(argv[php_optind],"--")) {
1144+
script_file = NULL;
1145+
request_started = 0;
1146+
translated_path = NULL;
1147+
goto do_repeat;
1148+
}
11401149
/* Don't repeat fork()ed processes. */
11411150
if (--num_repeats && pid == getpid()) {
11421151
fprintf(stdout, "Finished execution, repeating...\n");

0 commit comments

Comments
 (0)