问题摘要
在Cybrics2020的一个题目中,题目已经设置了short_open_tag = Off
,但是在代码中仍然使用了<?=....
的形式,原来的猜想是docker部署的时候由于某些原因使该配置项没有生效,从而导致短标签能继续使用,并且PHP Manual中的描述也佐证了这个猜想,其中对于short_open_tag
的描述中有如下一段:
本指令也会影响到缩写形式 <?=,它和 <? echo 等价。使用此缩写需要 short_open_tag 的值为 On。 从 PHP 5.4.0 起, <?= 总是可用的。
最初我和队友将从 PHP 5.4.0 起, <?= 总是可用的
理解为了在PHP5.4.0之后默认生效,但可以通过设置short_open_tag = Off
来进行关闭。但是随后@Smile在PHP7.4.1下的测试推翻了这个认识:
1 | // 1.php |
实际运行时却发现1.php的代码能够正常执行,2.php的内容不执行,因此这就和可以通过关闭short_open_tag来使<?=失效
这个结论冲突了。后面我在PHP5.6.31下测试,效果同PHP7.4.1,排除了PHP7和PHP5大版本差异导致的解析不一致。
PHP代码解析过程
PHP的代码在真正执行会经过一长串的处理,其中和代码解析相关的部分则是Zend引擎对代码进行词法、语法分析,编译为opcode执行。
在进行词法、语法分析时,PHP会按照已规定好的词法规则将代码切分为单独的Token,然后再通过bison进行语法处理,当语法出现错误时则会终止执行,也就是我们所看到的各种语法错误提示。
与词法、语法分析相关的规则主要存放在Zend/zend_language_parser.y
以及zend_language_scanner.l
两个文件中,我们着重关注<?=
与<?
,其中,在Zend/zend_language_parser.y
文件中,对<?=
进行了定义:
1 | %token T_OPEN_TAG_WITH_ECHO "'<?='" |
在zend_language_scanner.l
中则定义了相关的re2c规则,<?=
与<?
与<?php
一样,都可以作为PHP代码的起始符:
1 | <INITIAL>"<?=" { |
<?= 的解析
为了理解其差异,我们先看<?=
的处理流程,它的处理流程主要涉及PARSER_MODE
、RETURN_TOKEN_WITH_IDENT
、RETURN_TOKEN
三个方法,其定义分别为:
1 |
|
PARSER_MODE
会判断是否elem是否为空,elem是个union结构:zend_parser_stack_elem
,主要用于表示词法元素,其定义为:
1 | typedef union _zend_parser_stack_elem { |
因此PARSER_MODE
主要作用为判断当前是否有词法元素需要解析。RETURN_TOKEN_WITH_IDENT
与RETURN_TOKEN
的实现较为相似,都是一个goto语句,对应的代码块为:
1 | emit_token: |
SCNG宏可以对定义的scanner_global全局变量进行取值,scanner_global主要用于存放lexer的状态信息,比如当前处理的指针位置、最后一次处理的token位置等,在上面的elem->ident.offset = SCNG(yy_text) - SCNG(yy_start);
这一句中,便是计算当前位置和开始位置从而得出偏移量。
SCNG(on_event)
则是取出存放在on_event上的同名函数,on_event可以根据token类型进行对应的处理,函数参数类型为:
1 | on_event(zend_php_scanner_event event, int token, int line,const char *text, size_t length, void *context) |
对于分析<?=
做了什么这个问题来说,分析到这里基本结束了,因此可以得出结论,对于<?=
来说,在解析完了echo就会继续后续token的处理,且未看到对于短标签配置的取值判断。
<? 的解析
<?
与<?=
最关键的区别就在于CG(short_tags)
的判断上,CG宏含义为Compiler Globals,与Zend编译器相关的全局变量相关,CG(short_tags)
则是取出有关于短标签的配置,随后根据取值来决定是否继续后续的代码执行。inline_char_handler
用于扫描不在PHP标签中的内容,因此以<?
表示的PHP代码也就不会进行执行。
上述代码的分析在PHP7.4.9下进行,在PHP5.6.31版本下的分析要更为简单,对<?=
的写法更为直观:
1 | <INITIAL>"<?=" { |