亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > PHP > 正文

PHP-Zend引擎剖析之詞法分析(一)

2019-11-06 06:12:11
字體:
來源:轉載
供稿:網友

前言

閑來研究一下php底層的Zend引擎源碼,Zend引擎是PHP腳本的虛擬機。在PHP上層有SAPI接口,負責對各個接入層的抽象,例如PHP在Apache模塊里邊的實現,Fast-CGI的實現,命令行的實現。在PHP底層便是Zend虛擬機,Zend虛擬機負責解析PHP語法的文件,上層可以在虛擬機中注冊函數/變量提供給虛擬機調用,例如從Apache分發過來的HTTP請求經過PHP的Apache SAPI接口后,便會注冊一些$_COOKIE、$_GET等全局變量,而在命令行模式下便沒有這些跟HTTP相關的全局變量。Zend引擎跟其他編譯器跟解釋器一樣,會經歷詞法分析/語法分析,語法分析后會生成op code,也就是PHP的中間代碼,最終Zend虛擬機執行的是op code。第一篇貢獻給Zend引擎的理當是詞法分析的源碼剖析。PS:分析的代碼是PHP-5.5.5的源碼包,下載地址:http://windows.php.net/downloads/releases/php-5.5.5-src.zip。

詞法分析

詞法分析階段就是從輸入流里邊一個字符一個字符的掃描,識別出對應的詞素,最后把源文件轉換成為一個TOKEN序列,然后丟給語法分析器。從詞法分析階段中,詞法分析器也能檢測到源代碼里邊的一些錯誤。例如在Zend引擎的詞法分析階段就有這樣一段代碼:
          zend_error(E_COMPILE_WARNING, "Unterminated comment starting line %d", CG(zend_lineno));
當檢測到/*開頭,但是沒有*/結尾時,Zend引擎會拋出一個Waring提示,但是并不影響接下來的詞法解析,詞法分析階段一般都不會造成嚴重的解析錯誤,因為詞法分析階段的職責就是識別出Token序列而已,它并不需要知道Token跟Token之間是否具備什么聯系(那個應該是語法分析階段的職責)。在Zend引擎的詞法分析器中也會拋出致命的解析錯誤而終止詞法分析階段,如下代碼:
           zend_error_noreturn(E_COMPILE_ERROR, "Could not convert the script from the detected "                                   "encoding /"%s/" to a compatible encoding", zend_multibyte_get_encoding_name(LANG_SCNG(script_encoding)));
這個解析錯誤是因為從輸入流里邊檢測到的代碼的編碼不合法,顯然,這里是應該終止掉整個解析過程的。Zend引擎的詞法分析器re2c來生成,詞法分析的階段會涉及到各個狀態,其變量命名均為yy開頭(下文會說明)。

源碼高亮

我找了一個清晰的流程來分析怎么進入到詞法分析階段的。我們以命令行的PHP為入口來研究一下,以HelloWorld的例子來看,我們在命令行執行:php -s HelloWorld.php,結果如下:php -s是高亮源代碼的命令,所謂高亮源代碼其實就是對詞素進行一個顏色高亮,我們通過入口文件分析到在$PHPSRC/sapi/cli/php_cli.c中的do_cli函數里邊接收了命令行的參數輸入。-s的輸入對應的是高亮源碼。緊接著,便是調用了Zend引擎的代碼高亮的函數:zend_highlight。在$PHPSRC/Zend/zend_highlight.c中,我們找到了zend_highlight的定義,zend_highlight()調用的就是詞法分析器lex_scan來獲取Token,然后加入對應的顏色。到了這里,就真正進入詞法分析的流程了。

lex詞法分析器

Zend引擎的lex文件位于$PHPSRC/Zend/zend_language_scanner.l,如果你安裝了re2c,可以通過以下命令來生成c文件:
re2c -F -c -o zend_language_scanner.c zend_language_scanner.l
我們主要剖析的是zend_language_scanner.l文件。在re2c生成的詞法解析器中,我認為有兩個維度的狀態機。第一個維度是字符串的維度來維護的狀態,第二個是字符的維度來維護狀態。第二個維度的狀態機就是字符間狀態的跳轉,在這里我們忽略之。例如在Zend引擎中,當掃描到"<?php"時,Zend會將當前第一維度的狀態設置為ST_IN_SCRIPTING,表示現在我們已經進入了PHP腳本解析的狀態了。這個維度的狀態可以很方便的在lex文件中作為各種前置條件,例如在lex文件中有很多這樣的聲明:其表達的意思就是:當我們詞法解析器處于ST_IN_SCRIPTING這個狀態時,遇到"exit"這個字符串就返回一個T_EXIT的Token標志(在Zend引擎中Token的宏都是以T_開頭,其實際對應是一個數字)。你可以經常從語法錯誤提示信息中看到T_開頭的提示信息,例如在:echo "Hello" World!/n";字符串中加多了一個雙引號,運行時就會出現編譯錯誤,這里邊就有一個T_STRING的Token錯誤:
Parse error: syntax error, unexpected 'World' (T_STRING), expecting ',' or ';' in /home/raphealguo/tmp/HelloWorld.php on line 2
在詞法解析器掃描字符的過程中,需要記錄掃描過程的各個參數以及當前狀態,這些變量都是以yy開頭命名。常用到的就是:yy_state, yy_text, yyleng, yy_cursor, yy_limit各個變量的狀態掃描前后的變化示意圖。掃描echo前:

掃描echo后:

通過一個字符一個字符的掃描最終會得到一個Token序列,然后交由語法分析器去解析,接著就是剖析Zend引擎的lex文件規則是怎么寫的了。

lex文件剖析

Zend詞法解析狀態

Zend引擎在做詞法解析時會自己維護掃描過程的狀態,其實就是將yy_text等變量自己封裝一個結構體,我們可以在lex文件中看到很多SCNG的宏調用,例如:SCNG(yy_start) = YYCURSOR;定位一下#define SCNG,可以發現在lex文件的91行有這樣的宏定義:
/* Globals Macros */#define SCNG     LANG_SCNG
我們重新定位到#define LANG_SCNG在文件$PHPSRC/Zend/zend_globals_macros.h中的第56行(我們忽略52行ZTS的判斷,這是一個線程安全的宏定義):
# define LANG_SCNG(v) (language_scanner_globals.v)          //這里可以看到實際上在掃描過程中 都是調全局掃描狀態的屬性,例如SCNG(yy_start)相當于language_scanner_globals.yy_startextern ZEND_API zend_php_scanner_globals language_scanner_globals;#endif
可以看到Zend引擎維護了一個zend_php_scanner_globals的結構體(實際上在27行里邊是一個typedef的重命名,本來是叫做_zend_php_scanner_globals這個結構體),_zend_php_scanner_globals這個結構體的定義在$PHPSRC/Zend/zend_globals.h,可以看到其結構有部分跟原來lex掃描器的變量是一致的,但是它好包裝了一些堆棧,還有輸入輸出流(解析PHP文件時不一定是文件輸入流,也有可能從終端輸入的命令,所以這里包裝一個輸入輸出流是很合理的)。關鍵字Token回到lex詞法描述文件上,前邊說到詞法掃描的入口在zend_language_scanner.l的第999行int lex_scan(zval *zendlval TSRMLS_DC)里。先定義一些前置的正則匹配:對于一些無需復雜處理的關鍵字,我們掃描到對應的關鍵字,直接生成對應的Token標志即可,例如:在lex文件中可以看到很多這樣的規則聲明,<ST_IN_SCRIPTING>是指掃描到這個關鍵字的前置條件是詞法解析器要處于ST_IN_SCRIPTING這個狀態,在lex文件里邊有以下幾種方式可以設置當前的詞法解析器狀態
#define YYGETCONDITION()  SCNG(yy_state)#define YYSETCONDITION(s) SCNG(yy_state) = s#define BEGIN(state) YYSETCONDITION(STATE(state))static void _yy_push_state(int new_state TSRMLS_DC){//將當前狀態壓棧,然后重設當前狀態為新狀態zend_stack_push(&SCNG(state_stack), (void *) &YYGETCONDITION(), sizeof(int));YYSETCONDITION(new_state);}

進入PHP解析狀態

我們知道PHP是嵌入式的,只有包含在<?php ?>或者<? ?>標簽中的字符才會被執行解析,在lex文件的1732-1805行就是掃描<?php這樣起始標簽的規則聲明,源碼如下:當掃描到<?php時,在1790行設置了當前詞法解析器的狀態為ST_IN_SCRIPTING,其中HANDLE_NEWLINE是為了遞增當前的zend_lineno,這個變量是用來記錄當前解析到第幾行。最后return一個T_OPEN_TAG出去。當遇到短標簽<?=時,會先檢查全局屬性里邊的short_tags有沒有打開,沒有的話就goto到inline_char_handler去處理,inline_char_handler對應的就是掃描不在PHP標簽里邊的字符了。在1732行行定義了另外一種PHP語法打開標簽,就是:<script language="php">echo 2;</script>可以通過這個規則看出,如果在script里邊加入其他屬性就會導致這條規則失效,例如:<script language="php">echo 2;</script>就不會進行PHP語法解析了。

PHP注釋

接著我們看一下PHP里邊注釋是怎么掃描的。先找到1919行關于單行注釋的規則聲明:可以看出,PHP是支持#以及//兩種方式的單行注釋。處于ST_IN_SCRIPTING狀態下,遇到"#"|"//",變觸發了單行注釋的掃描,從當前字符開始一直掃描到流緩沖區的末尾(也即是while(YYCURSOR < YYLIMIT))。遇到/r/n以及/n時,遞增記錄當前解析的行(zend_lineno++),為了更好容錯性,PHP還兼容了//?>這樣的語法,也即是說當行注釋是不會注釋到?>的,可以從case '?'這個分支看出Zend的處理,先讓當前指針YYCURSOR--,回到?>前一個字符,然后跳出循環,這樣才不會吃掉"?>"導致后邊認不到PHP的關閉標簽。多行注釋的規則稍微復雜那么一點點:首先可以看到/**是對應PHP文檔聲明的解析(在文檔中是可以書寫PHP變量,在變量解析那里可以看到這個問題),緊接著一個while循環掃描到*/的位置,如果一直到文件結尾都沒掃到*/,那就zend_error一個Waring錯誤,但是不會影響接下去的解析。

PHP數字類型

從一開始的正則規則里邊可以知道PHP支持5中類型的數字常量聲明:其實對于代碼來說,數字其實也是字符,詞法分析器掃描到這5個規則的時候,需要把當前的zendlval對應的解析成數字存起來,同時返回一個數字類型的Token標志,看最簡單的LNUM規則處理:首先檢查一下當前的字符串是否超出C語言的long類型長度,如果不超過,直接接調用strtol把字符串轉換成long int類型。如果超出了long的范圍,Zend還是嘗試看看能不能轉,如果發生溢出(error == ERANGE)那就把當前數字轉成double類型。至于DNUM、BNUM等就不占篇幅了。

PHP變量類型

PHP的變量是以美元符$開頭,從詞法規則里邊可以看到:有三種變量的聲明調用方式,$var, $var->PRop, $var["key"]。注意到yyless調用,yyless的宏定義聲明在69行:因為詞法掃描的時候已經吃掉了"$var->",而我們只需要提取出變量名"var",因此我們需要讓YYCURSOR指針重新回到"var->"的"-"位置,因此調用了yyless(yyleng-3)。緊接著都是通過zend_copy_value拷貝變量名到zendlval里邊記錄起來供之后語法解析階段插入到符號表里邊去。這里再討論一個關于$var->prop的規則,我們留意到1193行有個奇怪的規則,為什么在ST_LOOKING_FOR_PROPERTY下還可以再有->呢,研究了一下,原來這里是為了檢驗$var->prop1->prop2這第2+個的->。

PHP字符串類型

PHP的字符串類型在詞法分析階段應該是最復雜的,PHP里邊的字符串可以由單引號跟雙引號來圍住,單引號的字符串比雙引號的字符串效率會更高,一會我們可以看到為什么。先來看一下單引號的規則:首先留意到b?['],字符串前邊能加上b聲明?但是在之后的代碼中壓根沒看出這個b的聲明對字符串有什么影響。在http://php.net/manual/zh/language.types.string.php里邊有這樣一句描述:原來這b是為了聲明一個二進制字符串用的。再留意到2022行,為什么遇到'//'要讓YYCURSOR++呢?因為在字符串中/后邊帶的是轉義字符,這里讓YYCURSOR++的目的就是為了跳過下一個字符,例如:'/'',如果不跳過第二個單引號的話,我們掃描到第二個引號就會認為字符串結束了。接下去的處理就比較簡單了,從輸入流中取出字符串的內容,返回一個T_CONSTANT_ENCAPSED_STRING的Token標志。雙引號的字符串處理就復雜一點了:雙引號里邊是支持變量的!$hello = "Hello"; $str = "${hello} World";留意到2085行,如果雙引號字符串里邊沒有變量,直接就返回一個字符串了,從這里看出,其實雙引號字符串在沒有包含$的情況下的效率跟單引號字符串是差不多的。如果遇到了變量!這個時候就要切換到ST_DOUBLE_QUOTES狀態了:現在又回到了尋找變量的規則,其他的規則就不占篇幅了,討論一個細節,我們回到1871行:注意到掃描到"$var["這種情況的時候,會壓入一個新的狀態ST_VAR_OFFSET,同時在1889這條規則里邊有前置條件ST_VAR_OFFSET的存在,這個是為了掃描到$var[$key][$key]這樣的情況,細心點還可以留意到字符串里邊的數組變量的key是不允許用->的,例如:$str = "$var[$a->s]";這樣是不符合語法的,會出現一個解析錯誤:Parse error: syntax error, unexpected '-', expecting ']' in xxx.php

PHP魔術變量

PHP魔術變量分為編譯時替換以及運行時替換,詞法規則文件里邊的1593-1722行定義了以下魔術變量:__CLASS__, __TRAIT__, __FUNCTION__, __METHOD__, __LINE__, __FILE__, __DIR__, __NAMESPACE__魔術變量的剖析留到之后再寫,留意到__contruct這類并不在詞法聲明的規則里邊出現。

PHP的容錯機制

在前邊說單行注釋的時候已經描述了一種容錯機制,在語法文件的1490行,2432行均有詞法分析階段的容錯機制。

結語

文章中還忽略了單字符的詞素(規則位于1454行)以及強制類型轉換的規則(例如:(int)$str, 規則位于1230行),Zend引擎的在詞法分析階段開始前還會檢查文件的編碼問題以及文件流的操作問題,之后再找篇文章細細研究一下這兩塊的內容。最后不由得不感嘆一下,盡管對編譯原理的熟悉程度不高,但是re2c的書寫出來的規則真心容易懂。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
一区二区三区视频免费| 国产中文字幕91| 精品久久久久久电影| 18性欧美xxxⅹ性满足| 日韩精品免费看| 日韩av网站电影| 2019中文字幕全在线观看| 欧美精品电影免费在线观看| 亚洲午夜精品久久久久久性色| 久久69精品久久久久久国产越南| 欧美午夜宅男影院在线观看| 国产精品老女人精品视频| 日韩黄色在线免费观看| 精品无码久久久久久国产| 久久久视频精品| 精品人伦一区二区三区蜜桃网站| 日韩欧美亚洲范冰冰与中字| 国产成人自拍视频在线观看| 亚洲欧美综合精品久久成人| 国产区精品在线观看| 欧美夫妻性视频| 26uuu久久噜噜噜噜| 亚洲欧美中文日韩在线| 中文字幕不卡av| 在线亚洲国产精品网| 国产成人在线亚洲欧美| 成人福利视频在线观看| 日韩精品一区二区视频| 亚洲美女在线观看| …久久精品99久久香蕉国产| 中文字幕亚洲一区二区三区| 亚洲欧美国产日韩中文字幕| 亚洲裸体xxxx| 亚洲精品99久久久久中文字幕| 久久精品视频网站| 日韩在线视频观看| 国产精品高潮呻吟久久av无限| 日韩免费在线电影| 欧美整片在线观看| 一区二区三区www| 欧美中文在线免费| 色综合91久久精品中文字幕| 久久久久久av| 日本亚洲精品在线观看| 欧美另类老女人| 国产精品麻豆va在线播放| 青青草原成人在线视频| 久久艹在线视频| 中文字幕一区电影| 欧美性xxxx在线播放| 欧美在线视频一二三| 亚洲国产精品人久久电影| 精品国产户外野外| 久久好看免费视频| 亚洲综合大片69999| 一区二区三区在线播放欧美| 亚洲男人第一av网站| 人人澡人人澡人人看欧美| 亚洲香蕉av在线一区二区三区| 国产日韩欧美黄色| 国产免费一区视频观看免费| 国内揄拍国内精品| 中文字幕欧美在线| 精品日韩中文字幕| 亚洲乱码av中文一区二区| 久久伊人精品天天| 亚洲免费av网址| 91成人免费观看网站| 久久久久久亚洲精品| 日韩av123| 4444欧美成人kkkk| 91色琪琪电影亚洲精品久久| 日韩精品丝袜在线| 国产日韩精品综合网站| 国产精品电影网站| 98午夜经典影视| 欧美视频在线观看 亚洲欧| 中文字幕精品国产| 日韩av在线天堂网| 久久久女人电视剧免费播放下载| 国产精品96久久久久久| 中文字幕av一区| 一本色道久久综合亚洲精品小说| 色中色综合影院手机版在线观看| 一本大道亚洲视频| 国产精品日韩av| 国产精品成人一区| 欧美午夜精品在线| 国产精品免费观看在线| 91大神在线播放精品| 中日韩美女免费视频网站在线观看| 亚洲欧美在线看| 亚洲精品美女久久久| 热99久久精品| 精品国产一区二区三区久久久狼| 精品动漫一区二区| 亚洲热线99精品视频| 欧美三级欧美成人高清www| 国外成人在线直播| 日韩中文字幕视频在线| 欧美激情a在线| 欧美日韩国产精品一区| 日韩欧美在线中文字幕| 精品香蕉在线观看视频一| 欧美日韩一区二区三区在线免费观看| 国产脚交av在线一区二区| 欧美黑人极品猛少妇色xxxxx| 亚洲综合精品一区二区| 狠狠色香婷婷久久亚洲精品| 欧美性高潮在线| 日韩视频一区在线| 国产精品欧美日韩| 亚洲欧美视频在线| 欧美精品久久久久久久免费观看| 国产午夜精品久久久| 国内精品模特av私拍在线观看| 久久久久免费视频| 久青草国产97香蕉在线视频| 欧美一级成年大片在线观看| 久久久久久久久久国产精品| 狠狠色狠狠色综合日日五| 91夜夜未满十八勿入爽爽影院| 亚洲在线免费观看| 精品高清美女精品国产区| 久久久中文字幕| 亚洲欧美另类中文字幕| 97国产精品视频人人做人人爱| 2025国产精品视频| 日韩中文在线中文网在线观看| 欧美性猛交xxxx免费看| 97免费视频在线| 亚洲精品国产综合久久| 久久精品91久久久久久再现| 国产精品久久久久久一区二区| 欧美日韩一区二区免费在线观看| 一区二区三区www| 亚洲精品影视在线观看| 亚洲综合最新在线| 国产精品久久久| 日韩黄色高清视频| 欧美色另类天堂2015| 日韩中文理论片| 精品亚洲男同gayvideo网站| 俺也去精品视频在线观看| 狠狠躁夜夜躁人人爽天天天天97| 久久精品中文字幕免费mv| 国产欧美精品一区二区三区-老狼| 亚洲国产日韩一区| 国产精品精品久久久| 久久色在线播放| 国产成人精品视频在线| 久久男人资源视频| 91久久久久久久久久| 成人午夜在线观看| 91av成人在线| 亚洲精品欧美极品| 国产欧美精品一区二区三区-老狼| 国产v综合ⅴ日韩v欧美大片| 欧美午夜激情小视频| 日韩精品高清在线| 欧美黑人一级爽快片淫片高清| 91精品国产综合久久久久久蜜臀| 日韩欧亚中文在线| 亚洲一区二区中文|