PHP 词法分析/语法分析
仓库代码
https://github.com/aaPanel/aaWAF/tree/main/php_engine
PHP执行流程
参考:
https://github.com/php/php-src/blob/master/Zend/zend_language_scanner.l
https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y
一、词法分析
1.1 Lexer结构体 以及实例化
// Lexer 结构体定义了PHP词法分析器的核心数据结构
type Lexer struct {
data []byte // 待分析的源代码字节数组
phpVersion *version.Version // PHP版本信息,用于兼容性处理
errHandlerFunc func(*errors.Error) // 错误处理函数
p, pe, cs int // p: 当前位置, pe: 数据结束位置, cs: 当前状态
ts, te, act int // ts: token开始位置, te: token结束位置, act: 当前动作
stack []int // 状态栈,用于处理嵌套结构
top int // 栈顶指针
heredocLabel []byte // 当前heredoc标签
tokenPool *token.Pool // token对象池,优化内存分配
positionPool *position.Pool // 位置对象池,优化内存分配
newLines NewLines // 跟踪源码中的新行位置
}
// NewLexer 创建并初始化一个新的词法分析器实例
func NewLexer(data []byte, config conf.Config) *Lexer {
lex := &Lexer{
data: data, // 设置源代码
phpVersion: config.Version, // 设置PHP版本
errHandlerFunc: config.ErrorHandlerFunc, // 设置错误处理函数
pe: len(data), // 设置数据结束位置
stack: make([]int, 0), // 初始化状态栈
// 初始化对象池和行号追踪
tokenPool: token.NewPool(position.DefaultBlockSize),
positionPool: position.NewPool(token.DefaultBlockSize),
newLines: NewLines{make([]int, 0, 128)}, // 预分配空间以提高性能
}
initLexer(lex) // 初始化词法分析器状态机
return lex
}
1.1 此法解析状态机
大概的一个状态机的图
1.2 解析一个简单的php文件
<?php phpinfo();?>
1.2.1 首先是进入到main的主状态
1.2.1 首先是进入到main的主状态
main := |*
any => {
fnext html; // 切换到html 状态
lex.ungetCnt(1) // 将刚读取的字符推回输入流
};
*|;
1.2.2 进入到html主状态
html := |*
'<?php'i ( [ \t] | newline ) => {
lex.ungetCnt(lex.te - lex.ts - 5) // 回退到空格这个地方。让下一个读取能获取到
lex.addFreeFloatingToken(tkn, token.T_OPEN_TAG, lex.ts, lex.ts+5)
fnext php;
};
进入到了php的主状态
1.2.3 空格返回
whitespace_line* => {lex.addFreeFloatingToken(tkn, token.T_WHITESPACE, lex.ts, lex.te)};
1.2.4 Phpinfo 返回
varname => { lex.setTokenPosition(tkn); tok = token.T_STRING; fbreak; };
1.2.5 ( 返回
operators => {
lex.setTokenPosition(tkn);
tok = token.ID(int(lex.data[lex.ts]));
fbreak;
};
1.2.6 ) 返回
operators => {
lex.setTokenPosition(tkn);
tok = token.ID(int(lex.data[lex.ts]));
fbreak;
};
1.2.6 ;?>
';' whitespace_line* '?>' newline? => {lex.setTokenPosition(tkn); tok = token.ID(int(';')); fnext html; fbreak;};
1.2.7 汇总
Value: T_OPEN_TAG vlue: <?php Value: T_WHITESPACE vlue: T_STRING phpinfo ID(40) ( ID(41) ) ID(59) ;?>
二、词法分析
输入的如下举例
src := []byte(` <?php phpinfo();?>`)
输入的Token为
T_INLINE_HTML vlu: T_STRING vlu: phpinfo ID(40) vlu: ( ID(41) vlu: ) ID(59) vlu: ;?> ID(0) vlu: ;?>
2.1 第一个T_INLINE_HTML
statement:
| T_INLINE_HTML
{
$$ = &ast.StmtInlineHtml{
Position: yylex.(*Parser).builder.NewTokenPosition($1),
InlineHtmlTkn: $1,
Value: $1.Value,
}
}
规约到top_statement
top_statement:
error
{
// error
$$ = nil
}
| statement
{
fmt.Println("规约到top_statement")
$$ = $1
}
规约到top_statement_list
top_statement_list:
top_statement_list top_statement
{
fmt.Println("规约到top_statement_list")
if $2 != nil {
$$ = append($1, $2)
}
}
| /* empty */
{
$$ = []ast.Vertex{}
}
;
2.2 T_STRING phpinfo
到namespace_name 节点
namespace_name:
T_STRING
{
fmt.Println("Identifier2:", $1.Value)
$$ = &ParserSeparatedList{
Items: []ast.Vertex{
&ast.NamePart{
Position: yylex.(*Parser).builder.NewTokenPosition($1),
StringTkn: $1,
Value: $1.Value,
},
},
}
}
2.3 ID(40) (
先规约后面操作
name:
namespace_name
{
fmt.Println("规约到name")
}
规约到name 之后读入(
function_call:
name argument_list
{
fmt.Println("规约到function_call")
$$ = &ast.ExprFunctionCall{
Position: yylex.(*Parser).builder.NewNodesPosition($1, $2),
Function: $1,
OpenParenthesisTkn: $2.(*ArgumentList).OpenParenthesisTkn,
Args: $2.(*ArgumentList).Arguments,
SeparatorTkns: $2.(*ArgumentList).SeparatorTkns,
CloseParenthesisTkn: $2.(*ArgumentList).CloseParenthesisTkn,
}
}
刚好符合argument_list 但是这里差了一个( 等待后续输入
argument_list:
'(' ')'
{
fmt.Println("argument_list1")
$$ = &ArgumentList{
Position: yylex.(*Parser).builder.NewTokensPosition($1, $2),
OpenParenthesisTkn: $1,
CloseParenthesisTkn: $2,
}
}
2.4 ID(41) )
这里就符合了argument_list
argument_list:
'(' ')'
{
fmt.Println("argument_list1")
$$ = &ArgumentList{
Position: yylex.(*Parser).builder.NewTokensPosition($1, $2),
OpenParenthesisTkn: $1,
CloseParenthesisTkn: $2,
}
}
规约到function_call
function_call:
name argument_list
{
xxx
}
规约到callable_variable
callable_variable:
| function_call
{
fmt.Println("function_call规约到callable_variable")
$$ = $1
}
2.5 ID(59) ;?>
先规约到variable
expr:
variable
{
$$ = $1
}
规约到expr
expr:
variable
{
fmt.Println("规约到expr")
$$ = $1
}
读入; 符合statement
statement:
| expr ';'
{
$$ = &ast.StmtExpression{
Position: yylex.(*Parser).builder.NewNodeTokenPosition($1, $2),
Expr: $1,
SemiColonTkn: $2,
}
}
规约到top_statement
top_statement:
error
{
// error
$$ = nil
}
| statement
{
fmt.Println("规约到top_statement")
$$ = $1
}
规约到top_statement_list
top_statement_list:
top_statement_list top_statement
{
fmt.Println("规约到top_statement_list")
if $2 != nil {
$$ = append($1, $2)
}
}
| /* empty */
{
$$ = []ast.Vertex{}
}
;
2.6 读入ID 0
先规约
start:
top_statement_list
{
xxx
}
这里符合start 的要求。这个语法通过



