亚洲免费av电影一区二区三区,日韩爱爱视频,51精品视频一区二区三区,91视频爱爱,日韩欧美在线播放视频,中文字幕少妇AV,亚洲电影中文字幕,久久久久亚洲av成人网址,久久综合视频网站,国产在线不卡免费播放

        ?

        基于JavaCC的抽象語法樹生成錯(cuò)誤處理技術(shù)研究

        2022-03-30 14:02:54王國隆金大海宮云戰(zhàn)
        計(jì)算機(jī)測量與控制 2022年2期
        關(guān)鍵詞:詞法源代碼區(qū)間

        王國隆,金大海, 宮云戰(zhàn)

        (北京郵電大學(xué) 網(wǎng)絡(luò)與交換技術(shù)國家重點(diǎn)實(shí)驗(yàn)室,北京 100876)

        0 引言

        為保障計(jì)算機(jī)軟件質(zhì)量,應(yīng)盡早進(jìn)行軟件測試,而軟件測試的重要手段之一就是靜態(tài)測試[1-5]。隨著C++語言標(biāo)準(zhǔn)11/14/17的不斷演進(jìn),新標(biāo)準(zhǔn)對(duì)C++語法進(jìn)行了諸多補(bǔ)充和優(yōu)化,同時(shí)也帶來了許多擴(kuò)充的新特性[6]。隨著C++新標(biāo)準(zhǔn)在市場上大面積的普及應(yīng)用,對(duì)支持C++新標(biāo)準(zhǔn)的靜態(tài)測試也顯得尤為重要。

        目前存在很多用于C++靜態(tài)測試的工具,如Cpplint[7],PMD[8],Cppcheck[9]等。在這些靜態(tài)測試工具中都必須對(duì)代碼解析。抽象語法樹是對(duì)代碼的一種重要的中間表示形式,也是一個(gè)對(duì)代碼進(jìn)行靜態(tài)測試的不可或缺的數(shù)據(jù)結(jié)構(gòu),在代碼測試分析領(lǐng)域有著廣泛的應(yīng)用[10]。

        目前存在的很多用于C++語言的詞法語法分析工具,如JavaCC[11],ANTLR[12],LEX/YACC[13]等不能完全支持C++新標(biāo)準(zhǔn)的某些特性。而文中采用錯(cuò)誤處理技術(shù),較好地解決了這個(gè)問題,它可以確保對(duì)抽象語法樹生成出現(xiàn)錯(cuò)誤的地方采取對(duì)應(yīng)的策略進(jìn)行錯(cuò)誤處理并完成抽象語法樹的創(chuàng)建。在本文中,提出了一個(gè)針對(duì)抽象語法樹生成的錯(cuò)誤處理框架,用以解決抽象語法樹生成錯(cuò)誤問題。

        1 相關(guān)工作

        在詞法語法分析工具方面,彭虎臣[14]以LEX作為詞法分析器,讀入字符串后根據(jù)詞法規(guī)則,將單詞或者字符轉(zhuǎn)換為token;采用YACC作為語法分析器,通過在符合BNF范式的語法規(guī)則中嵌入語法動(dòng)作,之后搭建抽象語法樹提取網(wǎng)頁中CSS部分。Liu等[15]采用用戶自定義的語法規(guī)則及詞法規(guī)則,利用ANTLR來生成相應(yīng)語法分析器及詞法分析器的代碼。之后,首先把輸入的字符流,通過詞法分析器轉(zhuǎn)變?yōu)橛蓆oken組成的流,然后利用語法分析器的輸出獲得最后的抽象語法樹進(jìn)行Scratch語言的特征提取和檢測。黃松等[10]使用純Java代碼編寫的免費(fèi)編譯工具JavaCC,經(jīng)過用戶自定義的詞法語法規(guī)則文件jjt生成抽象語法樹。肖一飛[16]提出一種基于JavaCC的通用的缺陷檢測預(yù)處理方法,通過修改jjt規(guī)則文件對(duì)不同類型的詞法異常和語法異常進(jìn)行支持解決。孟春辰[17]提出一種基于JavaCC的中間文件化簡的方式,通過保留一些類型定義信息從而避免了對(duì)頭文件中導(dǎo)致靜態(tài)分析失敗的復(fù)雜語法結(jié)構(gòu)的分析。本文鑒于JavaCC的易用性以及平臺(tái)無關(guān)性等優(yōu)勢繼續(xù)使用JavaCC作為抽象語法樹的生成工具。

        但是,JavaCC也有缺點(diǎn)。JavaCC遇到語法錯(cuò)誤或者不匹配的語法時(shí),僅僅會(huì)報(bào)告第一處錯(cuò)誤并停止分析。也就意味著,對(duì)于代碼的抽象語法樹生成錯(cuò)誤且不完整,所以需要對(duì)此進(jìn)行錯(cuò)誤處理。

        對(duì)于錯(cuò)誤處理方面,羅海麗[18]提出拋棄輸入記號(hào)直到某個(gè)定界符,JavaCC默認(rèn)的錯(cuò)誤處理就是使用了跳過字符到指定符號(hào)的方式,但是這種拋棄可能會(huì)引入更多的錯(cuò)誤;郝麗波等[19]提出受到最大重復(fù)次數(shù)約束的可重試的錯(cuò)誤處理策略,但是對(duì)于JavaCC來說不做出改動(dòng)每次生成結(jié)果都是一樣;Jia等[20]提出了可替換的對(duì)輸入做局部修改的錯(cuò)誤處理策略,但是很難猜測符合意愿的替代方式;曾祥文[21]提出了一種可以回退K個(gè)的分析器,使用兩個(gè)分析棧,一個(gè)棧負(fù)責(zé)將新單詞壓入棧,另一個(gè)棧負(fù)責(zé)維護(hù)K個(gè)單詞,相當(dāng)于對(duì)K個(gè)單詞的窗口進(jìn)行修復(fù)。由于無法預(yù)測出錯(cuò)的常見形式和替換方式,本文使用跳過符號(hào)的方式進(jìn)行錯(cuò)誤處理。

        現(xiàn)有的詞法語法分析工具對(duì)C++新標(biāo)準(zhǔn)支持的不多,一些研究人員是面向C++98標(biāo)準(zhǔn)構(gòu)造抽象語法樹并進(jìn)行分析,與他們工作不同的是,本文是面向C++新標(biāo)準(zhǔn)生成抽象語法樹并對(duì)生成錯(cuò)誤進(jìn)行處理,此方法既可應(yīng)對(duì)不支持或者不匹配的語法片段,也可應(yīng)對(duì)C++日后的語法更新。

        2 抽象語法樹生成錯(cuò)誤分析

        2.1 抽象語法樹生成錯(cuò)誤原因

        抽象語法樹(AST,abstract syntax tree)是以樹狀的形式表達(dá)源代碼語法結(jié)構(gòu)的一種表現(xiàn)形式[17],用T=表示,其中:N為樹的節(jié)點(diǎn),表示代碼中的一種語法結(jié)構(gòu);E為樹的邊,表示代碼中的語法邏輯關(guān)系。

        抽象語法樹生成過程如圖1所示。

        圖1 語法樹生成過程示意圖

        源程序經(jīng)過詞法分析和語法分析生成抽象語法樹。BNF(Backus-Naur Form)是描述編程語言的文法。詞法分析和語法分析[22]根據(jù)對(duì)應(yīng)的符合BNF范式的規(guī)則文件生成對(duì)應(yīng)的語法樹節(jié)點(diǎn)組成語法樹。詞法分析錯(cuò)誤可以通過在規(guī)則文件中添加新token來支持,語法分析則需要針對(duì)語法邏輯對(duì)規(guī)則文件進(jìn)行修改?,F(xiàn)有的規(guī)則文件可以較好地支持C++98標(biāo)準(zhǔn)和C++14標(biāo)準(zhǔn)的絕大部分語法和特性,但是針對(duì)語法邏輯修改的規(guī)則文件不能做到對(duì)不斷迭代的C++新標(biāo)準(zhǔn)的完全支持,進(jìn)而就會(huì)產(chǎn)生語法樹生成錯(cuò)誤。

        2.2 語法規(guī)則文件不能完全支持的原因分析

        語法規(guī)則文件不能完全支持C++新標(biāo)準(zhǔn)的原因主要有以下3點(diǎn):

        2.2.1 C++新標(biāo)準(zhǔn)BNF范式無法獲取

        官方的C++新標(biāo)準(zhǔn)的BNF范式文檔無法獲取,并且網(wǎng)上存在的新標(biāo)準(zhǔn)BNF范式各有出入,無法確定是否和官方一致。如果范式文檔選擇出現(xiàn)問題,會(huì)對(duì)后續(xù)分析處理產(chǎn)生重大影響。

        2.2.2 C++新標(biāo)準(zhǔn)語法結(jié)構(gòu)變化大

        C++新標(biāo)準(zhǔn)語法更改中,提出了新的語法結(jié)構(gòu),新的功能,在新關(guān)鍵字的配合之下或不需要新的關(guān)鍵字,在原有關(guān)鍵字的基礎(chǔ)之上,提出新的結(jié)構(gòu),完成新的功能。這主要目的是為了更加方便地實(shí)現(xiàn)相應(yīng)的功能。以語法樹中statement節(jié)點(diǎn)的語法結(jié)構(gòu)的變化為例,如圖2所示。

        圖2 C++新標(biāo)準(zhǔn)語法結(jié)構(gòu)變化示例

        標(biāo)準(zhǔn)更新后出現(xiàn)更多的是語法結(jié)構(gòu)的變化,有些結(jié)構(gòu)是在原有結(jié)構(gòu)的基礎(chǔ)之上進(jìn)行擴(kuò)展,這種在構(gòu)建抽象語法樹的時(shí)候相對(duì)容易進(jìn)行更改,但是很多結(jié)構(gòu)的更改是在推翻原有結(jié)構(gòu)的基礎(chǔ)上進(jìn)行重新架構(gòu)(如圖2),同時(shí)還會(huì)糅合其他新的結(jié)構(gòu)進(jìn)來,層層嵌套。這種在破壞原有結(jié)構(gòu)的基礎(chǔ)上進(jìn)行的更改在構(gòu)建抽象語法樹時(shí)相對(duì)困難。

        由于破壞原有的語法結(jié)構(gòu),所以在修改抽象語法樹規(guī)則文件時(shí),也需要對(duì)相應(yīng)結(jié)構(gòu)進(jìn)行破壞,這樣無法保證在破壞現(xiàn)有結(jié)構(gòu)后仍然可以對(duì)原有結(jié)構(gòu)進(jìn)行支撐,這是較難的問題。對(duì)于這種問題,一方面對(duì)新的語法盡量修改規(guī)則文件進(jìn)行支撐,無法支撐的語法需要通過錯(cuò)誤處理技術(shù)進(jìn)行處理。

        2.2.3 C++新標(biāo)準(zhǔn)核心庫變更大

        C++新標(biāo)準(zhǔn)的庫更樂于使用復(fù)雜的嵌套結(jié)構(gòu)和模板類來對(duì)相關(guān)代碼進(jìn)行聲明,所以結(jié)構(gòu)變得相對(duì)復(fù)雜,而之前的C++98的庫更多的是使用直接聲明的方法。例如C++98和C++11核心庫iostream的變化如圖3所示。

        圖3 C++新標(biāo)準(zhǔn)核心庫變化示例

        可以看出,新標(biāo)準(zhǔn)的庫文件結(jié)構(gòu)變得異常復(fù)雜。因此,C++98的庫文件內(nèi)容更加容易被抽象語法樹支撐。由于測試時(shí),為了獲取到更全面的分析數(shù)據(jù),會(huì)將全部的頭文件進(jìn)行展開,以獲取到更多的信息來分析,獲取分析結(jié)果。但是目前來說,在極度復(fù)雜的庫文件的情況之下,很難做到對(duì)頭文件的完全支撐。相反地,用戶所寫源文件的結(jié)構(gòu)相對(duì)簡單,不會(huì)大量使用復(fù)雜的結(jié)構(gòu),更加容易進(jìn)行語法的支撐。所以建立抽象語法樹時(shí),構(gòu)建的主體應(yīng)為源文件,要做到不丟失源碼信息。

        綜上,在對(duì)C++新標(biāo)準(zhǔn)的語法邏輯做到最大可能的支撐的基礎(chǔ)上,對(duì)于還不支持的會(huì)導(dǎo)致語法樹生成錯(cuò)誤的語法要進(jìn)行錯(cuò)誤處理。因?yàn)殄e(cuò)誤會(huì)導(dǎo)致語法樹生成中斷,錯(cuò)誤點(diǎn)之后的源代碼不能生成相關(guān)的語法樹節(jié)點(diǎn),導(dǎo)致對(duì)應(yīng)源文件的語法樹不完整,從而影響后續(xù)靜態(tài)分析。所以本文重點(diǎn)討論在語法樹生成中跳過不支持或不匹配的語法片段的錯(cuò)誤處理技術(shù)。

        3 針對(duì)抽象語法樹生成的錯(cuò)誤處理框架

        對(duì)于語法樹生成失敗的源文件,希望通過錯(cuò)誤處理技術(shù)可以繼續(xù)生成語法樹并且保證語法樹盡量完整,也由此本文提出了針對(duì)抽象語法樹生成的錯(cuò)誤處理框架,框架設(shè)計(jì)如圖4所示。

        圖4 抽象語法樹生成錯(cuò)誤處理框架

        首先,源代碼經(jīng)過預(yù)處理后生成中間文件。之后是一個(gè)迭代的過程,如果中間文件生成抽象語法樹成功,直接結(jié)束;如果抽象語法樹生成錯(cuò)誤,則需要根據(jù)報(bào)錯(cuò)行數(shù)跳過附近語法片段以此繼續(xù)生成抽象語法樹直至成功。

        3.1 預(yù)處理

        中間文件(intermediate file)是在分析C++程序時(shí)使用編譯器對(duì)源代碼進(jìn)行預(yù)處理后生成的文件,以方便進(jìn)行后續(xù)分析。

        先要對(duì)C++源文件進(jìn)行預(yù)處理,預(yù)處理是通過編譯器進(jìn)行的,包括一些宏替換,條件編譯以及頭文件展開等操作。正是因?yàn)樾枰鲜霾僮鳎圆荒苁褂?cpp文件直接進(jìn)行處理,需要使用預(yù)處理后生成的中間文件.i進(jìn)一步生成抽象語法樹。

        3.2 語法樹生成錯(cuò)誤處理

        中間文件經(jīng)過詞法和語法規(guī)則文件生成抽象語法樹,如果成功生成,當(dāng)前文件分析完成;如果生成出錯(cuò),那么接下來進(jìn)入語法樹生成錯(cuò)誤處理流程。

        α1α2…αn=w1αiw2

        (1)

        式中,w1為已經(jīng)分析并且生成語法樹節(jié)點(diǎn)的部分;αi為當(dāng)前分析的token;w2為當(dāng)前文件剩余的token流。

        假定分析到αi生成語法樹時(shí)發(fā)生錯(cuò)誤,因?yàn)榉治鍪亲陨隙碌?,那就意味著?dāng)前已經(jīng)建立了部分語法樹,并且已經(jīng)生成的語法樹已經(jīng)涵蓋了w1,但是語法樹卻無法繼續(xù)生成以涵蓋w2。此時(shí),就必須應(yīng)用錯(cuò)誤處理技術(shù)來繼續(xù)生成語法樹??刹扇〉拇胧┤缦拢?/p>

        1)刪除當(dāng)前tokenαi并繼續(xù)分析;

        2)于w1和αi之間插進(jìn)終結(jié)符號(hào)α,從而把式(1)改寫成式(2):

        α1α2…αn=w1ααiw2

        (2)

        然后再從αi開始分析;

        Lakoff提出的意象圖式(image schema)是人們理解和認(rèn)知事物的基本結(jié)構(gòu)和組織形式,也是概念范疇內(nèi)的原型結(jié)構(gòu)[4]。由于人體本身就屬于空間概念范疇,所以基本的意象圖式就是空間圖式,凡是涉及到空間結(jié)構(gòu)的概念都是以意象圖式認(rèn)知模式儲(chǔ)存在大腦中。at表示空間概念范疇是基于路徑意象圖式基礎(chǔ)的,如圖1:

        3)從w1的尾部刪去若干個(gè)token后繼續(xù)分析。

        上述措施可以單獨(dú)使用也可以結(jié)合使用,其中措施(2)直接添加終結(jié)符可能會(huì)導(dǎo)致程序不能正常編譯。這里可以結(jié)合措施(1)和(3)進(jìn)行錯(cuò)誤處理,多次使用措施1并結(jié)合措施3進(jìn)行處理相當(dāng)于在式(1)中從w1的尾部刪除若干個(gè)token并且在w2的首部刪除若干個(gè)token,即把式(1)修改為式(3):

        α1…αi-p…αi…αi+qα2…αn=w0Uw3

        (3)

        式中,

        U=αi-p…αi…αi+q

        (4)

        如果這樣的U存在,就刪除或注釋Uu后繼續(xù)分析。

        基于以上措施,對(duì)于每一個(gè)cpp文件,先不考慮源代碼中引用的頭文件,只對(duì)純?cè)创a部分生成抽象語法樹。如果在這個(gè)過程中出現(xiàn)異常,解決措施是不考慮報(bào)錯(cuò)行數(shù)附近的函數(shù)區(qū)間,繼續(xù)分析其他可以正常生成抽象語法樹的部分。

        解決辦法是,從完整的中間文件中分離出源程序部分,根據(jù)這部分用戶寫的源代碼生成抽象語法樹,如果發(fā)生異常,通過報(bào)錯(cuò)行數(shù)和函數(shù)區(qū)間標(biāo)識(shí)算法略過附近函數(shù)區(qū)間后重新生成語法樹直至成功。

        模塊流程設(shè)計(jì)如圖5所示。

        如圖5所示,新語法錯(cuò)誤處理模塊可以分為3個(gè)部分:1)源程序分離;2)文件內(nèi)函數(shù)區(qū)間劃分;3)注釋報(bào)錯(cuò)區(qū)間迭代生成抽象語法樹。

        3.2.1 源程序分離

        在完整的中間文件中,有用戶寫的源代碼部分,也有頭文件展開的部分,這里先不考慮頭文件展開部分,只對(duì)源程序生成抽象語法樹。所以,需要從完整的中間文件中得到源程序部分。

        中間文件片段以及預(yù)期分離效果如圖6所示。

        圖6 中間文件片段以及預(yù)期分離效果

        孟春辰[17]根據(jù)“# 行號(hào) 文件存放路徑”從后向前遍歷,直到遇到文件后綴.h就結(jié)束遍歷。但是對(duì)于圖6代碼,在源程序中還插入了一些擴(kuò)展進(jìn)來的頭文件部分,之前的算法不能準(zhǔn)確截取出源程序部分,所以提出了算法1所示的通用的源程序分離算法。

        算法1:源程序分離算法

        輸入:源文件名fileName,完整中間文件content;

        輸出:只包含源程序部分的中間文件result。

        index ← 0

        isSourceLine ← false

        rightPattern← "#s(d+)s”(.*)" + fileName + "”s(d+)"

        falsePattern ←"#s(d+)s”(.*)”s(d+)"

        while index < content.size() do

        if line matches rightPattern do

        index++

        isSourceLine ←true

        continue

        end if

        if line matches falsePattern do

        isSourceLine ←false

        end if

        if isSourceLine do

        result.add(line)

        end if

        index++

        end while

        return result

        算法描述:算法從上向下掃描,通過兩種正則表達(dá)式進(jìn)行識(shí)別,一種表示含有源文件后綴的文件提示信息,另一種表示普通的文件提示信息,并且通過一個(gè)布爾值標(biāo)識(shí)當(dāng)前行是否加入結(jié)果集中,最后返回源程序部分代碼。

        3.2.2 文件內(nèi)區(qū)間劃分

        對(duì)上述中間文件分離出的源程序部分生成語法樹,如果發(fā)生錯(cuò)誤,就需要根據(jù)報(bào)錯(cuò)行數(shù)注釋相應(yīng)的區(qū)間,所以接下來就需要對(duì)中間文件進(jìn)行區(qū)間劃分。

        定義1:函數(shù)區(qū)間:函數(shù)區(qū)間是文件內(nèi)用來標(biāo)識(shí)一個(gè)函數(shù)名稱和范圍的結(jié)構(gòu),該結(jié)構(gòu)由一個(gè)三元組表示,該結(jié)構(gòu)描述記為I={Name,StartLine,EndLine},其中:

        Name:表示該函數(shù)區(qū)間的函數(shù)名稱;

        StartLine:表示該函數(shù)區(qū)間的起始行數(shù);

        EndLine:表示該函數(shù)區(qū)間的終止行數(shù)。

        文件內(nèi)區(qū)間劃分分為兩部分:第一部分是函數(shù)區(qū)間的標(biāo)識(shí),第二部分是函數(shù)區(qū)間優(yōu)化。

        1)函數(shù)區(qū)間標(biāo)識(shí)算法:函數(shù)區(qū)間標(biāo)識(shí)算法通過狀態(tài)機(jī)來實(shí)現(xiàn),定義11種狀態(tài),利用互相的轉(zhuǎn)化關(guān)系求出函數(shù)范圍。狀態(tài)機(jī)轉(zhuǎn)化關(guān)系如表1所示。

        表1 函數(shù)區(qū)間標(biāo)識(shí)狀態(tài)機(jī)轉(zhuǎn)化關(guān)系

        主要算法是在函數(shù)頭結(jié)束狀態(tài)中遇到{和}的處理邏輯,具體的算法如算法2所示。

        算法2: 函數(shù)區(qū)間標(biāo)識(shí)算法

        輸入:文件路徑path;

        輸出:函數(shù)劃分后起始行結(jié)束行的集合list。

        switch state do

        ……

        case 7 do

        if 3=lastState and 6=lastState then

        startLine ←line /*lastState表示當(dāng)前狀態(tài)的上一個(gè)狀態(tài)*/

        end if

        if c ='{' then

        bracket ←bracket + 1 /*bracket表示大括號(hào)的個(gè)數(shù)*/

        end if

        if c ='}' then

        bracket← bracket - 1

        end if

        if 0 =bracket then

        endLine← line + 1

        list.add(startLine, endLine) /*list是保存起始行和結(jié)束行的集合*/

        end if

        ……

        end switch

        算法描述:查看狀態(tài)機(jī)中的狀態(tài),狀態(tài)7表示函數(shù)體開始,如果從成員變量初始化列表狀態(tài)(狀態(tài)3)或者從函數(shù)頭結(jié)束狀態(tài)(狀態(tài)6)轉(zhuǎn)換而來,需要記錄函數(shù)開始行數(shù)startLine.bracket表示括號(hào)個(gè)數(shù),當(dāng)前字符c如果是{符號(hào)需要增加括號(hào)個(gè)數(shù),如果是}需要減少括號(hào)個(gè)數(shù),如果括號(hào)個(gè)數(shù)減為0,則當(dāng)前函數(shù)結(jié)束,記錄相應(yīng)的結(jié)束行數(shù)endLine,隨后加入到函數(shù)劃分的集合中。

        2)函數(shù)區(qū)間優(yōu)化算法:對(duì)于區(qū)間劃分后的文件中,會(huì)存在部分沒有在任何區(qū)間中的代碼行需要進(jìn)一步被劃分到現(xiàn)有區(qū)間中,區(qū)間劃分后剩余部分如圖7所示。

        圖7 區(qū)間劃分后剩余部分

        可以看出,例如“public:”“