數(shù)重載
在其他語(yǔ)言中,我們一般都聽(tīng)說(shuō)過(guò)重載的概念,對(duì)于一個(gè)方法,如果我們期望傳入不同的參數(shù)類型,或者傳入不同的參數(shù)個(gè)數(shù),甚至傳入?yún)?shù)的順序不一樣,而去執(zhí)行不同的處理代碼,以返回相應(yīng)的結(jié)果,那么他們的方法名是可以相同的,而且它們不會(huì)產(chǎn)生沖突,這就是所謂的重載。
這是因?yàn)橄駄ava等這樣的強(qiáng)類型語(yǔ)言,他們具有類型約束和方法簽名,這樣就使得他們能夠根據(jù)調(diào)用時(shí)傳入?yún)?shù)的情況來(lái)決定使用哪一個(gè)同名方法來(lái)處理需要的操作。
是在說(shuō)我嗎?
但是js屬于弱類型語(yǔ)言,也就是說(shuō)它不會(huì)規(guī)定參數(shù)必須傳入哪種類型,定義的方法也不具有簽名的功能,而且在定義多個(gè)同名方法時(shí),類似于css里面的層疊樣式表的效果,后定義的同名方法會(huì)覆蓋前面的方法,還有一個(gè)就是函數(shù)具有提升的特性,這也就使得它無(wú)法實(shí)現(xiàn)重載的功能。
怎么辦呢?
其實(shí)js中也確實(shí)不需要重載的功能,因?yàn)闆](méi)有類型的約束,在一個(gè)方法里面就可以做很多自由的發(fā)揮,不但能滿足需要重載的需求,而且還能玩一些花樣。
不過(guò)話又說(shuō)回來(lái),沒(méi)有了約束,就容易犯錯(cuò),都用一個(gè)方法體來(lái)處理所有情況,就會(huì)容易出亂子,使得我們需要使用一堆的類似if-else的語(yǔ)句來(lái)做到這一點(diǎn)。
那么我們能不能在現(xiàn)有js運(yùn)行方式的基礎(chǔ)上,借鑒其他語(yǔ)言對(duì)于重載的運(yùn)用,來(lái)繞個(gè)彎子變量來(lái)實(shí)現(xiàn)一下屬于js自己的重載方式呢?
試一試
我們今天只討論在js中變相實(shí)現(xiàn)重載的方式,而不討論它的意義與實(shí)際應(yīng)用場(chǎng)景,我們通過(guò)一個(gè)簡(jiǎn)單的例子,來(lái)支撐這個(gè)討論,其他的交由你們來(lái)自由發(fā)揮。
讓我們開(kāi)始吧
在js中主要有以下幾種實(shí)現(xiàn)重載的方式:
前兩種方式比較相似,思路一樣,只是實(shí)現(xiàn)手段有所不同,用戶需要自己寫(xiě)判斷邏輯。第三種方式結(jié)合proxy來(lái)隱藏實(shí)現(xiàn)細(xì)節(jié),讓用戶只關(guān)注各自的分工。但它們都是在參數(shù)動(dòng)態(tài)判斷方面做文章。
前兩種方式的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):可以直接定義函數(shù)或使用表達(dá)式
缺點(diǎn):函數(shù)名不能相同,需要寫(xiě)較多的判斷分支
第三種方式的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):可以不用寫(xiě)各種參數(shù)形式的分支
缺點(diǎn):只能使用表達(dá)式定義函數(shù)
由于前兩種網(wǎng)上已經(jīng)有很多的實(shí)現(xiàn)思路與方案,因此這里不再進(jìn)行探討,其中有很多奇妙的實(shí)現(xiàn),可以做到在js中使用重載的思想。
所以在此我們只討論第三種方案,我們下面來(lái)看一下它的思路是什么,是否滿足重載的需求,它是如何實(shí)現(xiàn)的,以及它能滿足我們什么樣的需求?
這是什么呢?
我們現(xiàn)在有這樣一個(gè)場(chǎng)景和需求:
自己開(kāi)了一家服裝店,由于生意火爆,我們想要答謝新老顧客,現(xiàn)在推出了一個(gè)活動(dòng),全場(chǎng)無(wú)論任何服裝,只要買一件就直接打95折,只要買兩件就全部打9折,只要買三件就全部打85折,只要買四件及以上,就全部打8折。
如果用代碼來(lái)實(shí)現(xiàn),其實(shí)就是給方法中傳入一個(gè)兩個(gè)三個(gè)四個(gè)參數(shù)的問(wèn)題,因此我們自然而然的就想到了使用重載來(lái)實(shí)現(xiàn)這個(gè)需求。
接下來(lái)我們就試著自己實(shí)現(xiàn)一個(gè)這樣的功能,看看可不可以創(chuàng)建一個(gè)賦能方法來(lái)使某個(gè)業(yè)務(wù)處理函數(shù)具有重載的能力。
要生成這樣一個(gè)賦能方法,我們需要有對(duì)函數(shù)改造的能力,在創(chuàng)建業(yè)務(wù)處理函數(shù)的時(shí)候,最好能夠改變它的默認(rèn)行為,在執(zhí)行的時(shí)候也能夠?qū)λM(jìn)行攔截以做一些額外的操作。
那么我們很自然而然的就想到了使用Proxy,先生成一個(gè)Proxy函數(shù),然后在給它設(shè)置屬性的時(shí)候,我們進(jìn)行攔截,把賦值操作中的value緩存起來(lái),以備將來(lái)調(diào)用的時(shí)候做分支處理,根據(jù)參數(shù)的個(gè)數(shù)與類型來(lái)控制需要執(zhí)行的業(yè)務(wù)邏輯代碼。它真的能做到嗎?我們來(lái)看一下下面的一步步代碼實(shí)現(xiàn)。
function Overload(defaultCall) {
let func=defaultCall || new Function()
func.overloadCached=[]
return new Proxy(func, {
set(target, prop, value) {
if(prop==='load') {
target.overloadCached.push(value)
}
},
apply(target, thisArg, argumentsList) {
for(let i=target.overloadCached.length - 1; i > -1; i--) {
if(argumentsList.length===target.overloadCached[i].length || (argumentsList.length > target.overloadCached[i].length)) {
return target.overloadCached[i].apply(thisArg, argumentsList)
}
}
return target.apply(thisArg, argumentsList)
}
})
}
let sum=Overload()
sum.load=function (a) {
return a * 0.95;
}
sum.load=function (a, b) {
return (a + b) * 0.9;
}
sum.load=function (a, b, c) {
return (a + b + c) * 0.85;
}
sum.load=function (a, b, c, d, ...arg) {
return (arg.concat(a,b,c,d).reduce((total, cur)=> {return total + cur},0)) * 0.8;
}
console.log(sum(200));
console.log(sum(200, 300));
console.log(sum(180, 280, 190));
console.log(sum(270, 260, 310, 240));
console.log(sum(180, 220, 240, 210, 190));
//輸出:190,450,552.5,864,832
可以看到,我們實(shí)現(xiàn)了一個(gè)Overload函數(shù),用來(lái)返回一個(gè)Proxy,通過(guò)它去load不同的方法來(lái)實(shí)現(xiàn)對(duì)同名方法的重載,調(diào)用的時(shí)候只需要一個(gè)方法名即可,Proxy中我們對(duì)set(即設(shè)置該P(yáng)roxy的值的操作)和apply(即執(zhí)行該P(yáng)roxy的操作)兩種操作進(jìn)行了攔截,用到了一個(gè)叫做overloadCached的屬性來(lái)緩存我們的處理函數(shù),在調(diào)用函數(shù)的時(shí)候,我們使用從后往前遍歷的方式,來(lái)達(dá)到后定義優(yōu)先生效的原則。
通過(guò)打印結(jié)果我們知道,它已經(jīng)滿足了我們的需求假設(shè)。
從上面的代碼中我們發(fā)現(xiàn),Overload函數(shù)可以傳入一個(gè)叫做defaultCall的參數(shù),它是用來(lái)處理默認(rèn)操作的,也就是說(shuō)如果后面定義的所有方法都不能夠處理的時(shí)候,將使用該默認(rèn)函數(shù)進(jìn)行處理,如果沒(méi)有定義該函數(shù),那么調(diào)用sum時(shí)如果沒(méi)有滿足的執(zhí)行函數(shù),將會(huì)返回undefined。
現(xiàn)在我們給它傳入一個(gè)默認(rèn)的處理函數(shù),那么上面的需求將可以寫(xiě)成這樣:
function Overload(defaultCall) {
let func=defaultCall || new Function()
func.overloadCached=[]
return new Proxy(func, {
set(target, prop, value) {
if(prop==='load') {
target.overloadCached.push(value)
}
},
apply(target, thisArg, argumentsList) {
for(let i=target.overloadCached.length - 1; i > -1; i--) {
//注意這里的變化
if(argumentsList.length===target.overloadCached[i].length) {
return target.overloadCached[i].apply(thisArg, argumentsList)
}
}
return target.apply(thisArg, argumentsList)
}
})
}
let sum=Overload(function () {
return ([].__proto__.reduce.call(arguments, (total, cur)=> {return total + cur},0)) * 0.8;
})
sum.load=function (a) {
return a * 0.95;
}
sum.load=function (a, b) {
return (a + b) * 0.9;
}
sum.load=function (a, b, c) {
return (a + b + c) * 0.85;
}
console.log(sum(200));
console.log(sum(200, 300));
console.log(sum(180, 280, 190));
console.log(sum(270, 260, 310, 240));
console.log(sum(180, 220, 240, 210, 190));
//輸出:190,450,552.5,864,832
我們注意Overload函數(shù)的變化,現(xiàn)在依然滿足上面的需求。
由于我們把四個(gè)參數(shù)即以上的處理函數(shù)改為通過(guò)傳入默認(rèn)函數(shù)的方式來(lái)實(shí)現(xiàn),因此我們修改了Overload方法,這顯然是不合理的,因?yàn)檫@樣不設(shè)置默認(rèn)函數(shù)的時(shí)候會(huì)出問(wèn)題,因此我們做一個(gè)兼容處理,修改之后就變成了這樣:
function Overload(defaultCall) {
let func=defaultCall || new Function()
func.overloadCached=[]
return new Proxy(func, {
set(target, prop, value) {
if(prop==='load') {
let str=value.toString()
let m1=str.match(/\(.+?\)/)
if(m1 && m1[0].indexOf("...") !=-1) {
value.rest=true
}
target.overloadCached.push(value)
}
},
apply(target, thisArg, argumentsList) {
for(let i=target.overloadCached.length - 1; i > -1; i--) {
if((argumentsList.length===target.overloadCached[i].length) || (target.overloadCached[i].rest && argumentsList.length > target.overloadCached[i].length)) {
return target.overloadCached[i].apply(thisArg, argumentsList)
}
}
return target.apply(thisArg, argumentsList)
}
})
}
//輸出:190,450,552.5,864,832
現(xiàn)在使用這個(gè)Overload函數(shù)就已經(jīng)能夠處理上面的這兩種情況了。我們?cè)O(shè)定了一個(gè)rest屬性來(lái)給方法打上了一個(gè)標(biāo)識(shí)。
如果我們現(xiàn)在在上面的需求基礎(chǔ)上,又想要對(duì)金額做一些處理,比如希望能夠加上$、¥、等前綴,來(lái)區(qū)分不同的幣種。
這個(gè)時(shí)候我們需要增加新的重載函數(shù),而加了幣種之后的函數(shù)可能與現(xiàn)有的函數(shù)參數(shù)個(gè)數(shù)相同(比如sum('$', 220, 240)和sum(270, 260, 310)),這就造成了誤判,那么我們能不能再做一個(gè)類型區(qū)分呢?
應(yīng)該是可以的,但是我們必須約定一種格式,比如下面這種形式,我們需要在獲取Proxy屬性的時(shí)候(這里就用到了攔截獲取Proxy屬性的操作),將類型進(jìn)行緩存,以便將來(lái)時(shí)候的時(shí)候來(lái)做類型的判斷:
//我們約定了10種類型
//n→number
//s→string
//b→boolean
//o→object
//a→array
//d→date
//S→Symbol
//r→regexp
//B→bigint
//f→function
function Overload(defaultCall) {
let func=defaultCall || new Function()
func.overloadCached=[]
func.modifier=[]
return new Proxy(func, {
get(target, property, receiver) {
if(property !=='load') {
target.modifier.push(property)
}
return receiver
},
set(target, prop, value) {
if(['n','s','b','o','a','d','S','r','B','f'].includes(prop)) {
target.modifier.push(prop)
}
if(prop==='load' || target.modifier.length !==0) {
let str=value.toString()
let m1=str.match(/\(.+?\)/)
if(m1 && m1[0].indexOf("...") !=-1) {
value.rest=true
}
value.modifier=target.modifier
target.overloadCached.push(value)
target.modifier=[]
}
},
apply(target, thisArg, argumentsList) {
for(let i=target.overloadCached.length - 1; i > -1; i--) {
if((argumentsList.length===target.overloadCached[i].length) || (target.overloadCached[i].rest && argumentsList.length > target.overloadCached[i].length)) {
if(target.overloadCached[i].modifier.length !==0){
let ty={
'[object Number]': ['n'],
'[object String]': ['s'],
'[object Boolean]': ['b'],
'[object Object]': ['o'],
'[object Array]': ['a'],
'[object Date]': ['d'],
'[object Symbol]': ['S'],
'[object Regexp]': ['r'],
'[object BigInt]': ['B'],
'[object Function]': ['f'],
}
if(target.overloadCached[i].modifier.some((m, j)=> {
return !ty[({}).__proto__.toString.call(argumentsList[j])].includes(m)
})) {
continue
}
}
return target.overloadCached[i].apply(thisArg, argumentsList)
}
}
return target.apply(thisArg, argumentsList)
}
})
}
let sum=Overload()
sum.load.n=function (a) {
return a * 0.95;
}
sum.load.n.n=function (a, b) {
return (a + b) * 0.9;
}
sum.load.n.n.n=function (a, b, c) {
return (a + b + c) * 0.85;
}
sum.load.s.n.n=function (a, b, c) {
return a + (b + c) * 0.85;
}
sum.load.n.n.n.n=function (a, b, c, d, ...arg) {
return (arg.concat(a,b,c,d).reduce((total, cur)=> {return total + cur},0)) * 0.8;
}
sum.load.s.n.n.n=function (a, b, c, d, ...arg) {
return a + (arg.concat(b,c,d).reduce((total, cur)=> {return total + cur},0)) * 0.8;
}
console.log(sum(200));
console.log(sum(200, 300));
console.log(sum(260, 310, 240));
console.log(sum('', 280, 190));
console.log(sum(180, 220, 240, 210, 190));
console.log(sum('$', 220, 240, 210, 190));
//輸出:190,450,688.5,399.5,832,$688
我們現(xiàn)在已經(jīng)加上了類型判斷,通過(guò)傳入的參數(shù)類型與個(gè)數(shù)的不同,能夠相應(yīng)的去執(zhí)行對(duì)應(yīng)的函數(shù),其實(shí)參數(shù)的順序一個(gè)道理,也是支持的。
上面的類型約定我們可能看起來(lái)怪怪的,而且比較難以理解,因此我們可以擴(kuò)展一下類型約定的表示方式,改造后的Overload函數(shù)如下:
function Overload(defaultCall) {
let func=defaultCall || new Function()
func.overloadCached=[]
func.modifier=[]
return new Proxy(func, {
get(target, property, receiver) {
if(property !=='load') {
if(property.indexOf(',') !==-1) {
property.split(',').map(item=> {
target.modifier.push(item)
})
}else{
property.split('').map(item=> {
target.modifier.push(item)
})
}
}
return receiver
},
set(target, prop, value) {
let modi=null
if(prop.indexOf(',') !==-1) {
modi=prop.split(',')
}else{
modi=prop.split('')
}
if(modi.every(p=> {
return ['n','s','b','o','a','d','S','r','B','f','number','string','boolean','object','array','date','Symbol','regexp','bigint','function'].includes(p)
})) {
modi.map(item=> {
target.modifier.push(item)
})
}
if(prop==='load' || target.modifier.length !==0) {
let str=value.toString()
let m1=str.match(/\(.+?\)/)
if(m1 && m1[0].indexOf("...") !=-1) {
value.rest=true
}
value.modifier=target.modifier
target.overloadCached.push(value)
target.modifier=[]
}
},
apply(target, thisArg, argumentsList) {
for(let i=target.overloadCached.length - 1; i > -1; i--) {
if((argumentsList.length===target.overloadCached[i].length) || (target.overloadCached[i].rest && argumentsList.length > target.overloadCached[i].length)) {
if(target.overloadCached[i].modifier.length !==0){
let ty={
'[object Number]': ['n','number'],
'[object String]': ['s','string'],
'[object Boolean]': ['b','boolean'],
'[object Object]': ['o','object'],
'[object Array]': ['a','array'],
'[object Date]': ['d','date'],
'[object Symbol]': ['S','Symbol'],
'[object Regexp]': ['r','regexp'],
'[object BigInt]': ['B','bigint'],
'[object Function]': ['f','function'],
}
if(target.overloadCached[i].modifier.some((m, j)=> {
return !ty[({}).__proto__.toString.call(argumentsList[j])].includes(m)
})) {
continue
}
}
return target.overloadCached[i].apply(thisArg, argumentsList)
}
}
return target.apply(thisArg, argumentsList)
}
})
}
這樣我們就可以支持一下幾種類型約定的書(shū)寫(xiě)形式:
sum.load.s.n.n=function (a, b, c) {
return a + (b + c) * 0.85;
}
sum.load['snn']=function (a, b, c) {
return a + (b + c) * 0.85;
}
sum.load.snn=function (a, b, c) {
return a + (b + c) * 0.85;
}
//對(duì)于全稱不能夠?qū)懗?(點(diǎn))的形式
sum.load['string,number,number']=function (a, b, c) {
return a + (b + c) * 0.85;
}
//這四種形式的任意一種對(duì)于console.log(sum('$', 280, 190));
//都會(huì)輸出:$399.5
到此為止,我們已經(jīng)能夠支持參數(shù)的個(gè)數(shù)、類型、順序的不同,會(huì)執(zhí)行不同的處理函數(shù),滿足了重載的基本需求,完成了我們?cè)谧铋_(kāi)始的需求假設(shè)的實(shí)現(xiàn)。
目前這種方式只能支持函數(shù)表達(dá)式的方式來(lái)進(jìn)行重載,這里只是給大家提供一個(gè)自定義實(shí)現(xiàn)重載的方式,結(jié)合自己的業(yè)務(wù)場(chǎng)景,小伙伴們可以自由發(fā)揮,其實(shí)目前js的既有方式能滿足我們需要重載的場(chǎng)景,而不需要額外設(shè)計(jì)重載的代碼。
具體這種方式的優(yōu)劣,大家可以自行判斷,并且可以根據(jù)這種思路重新設(shè)計(jì)一下實(shí)現(xiàn)的手段。
謝謝
JavaScript是ECMAScript的實(shí)現(xiàn),由ECMA 39(歐洲計(jì)算機(jī)制造商協(xié)會(huì)39號(hào)技術(shù)委員會(huì))負(fù)責(zé)制定ECMAScript標(biāo)準(zhǔn)。
ECMAScript發(fā)展史:
時(shí)間 | 版本 | 說(shuō)明 |
1997年7月 | ES1.0 發(fā)布 | 當(dāng)年7月,ECMA262 標(biāo)準(zhǔn)出臺(tái) |
1998年6月 | ES2.0 發(fā)布 | 該版本修改完全符合ISO/IEC 16262國(guó)際標(biāo)準(zhǔn)。 |
1999年12月 | ES3.0 發(fā)布 | 成為 JavaScript 的通行標(biāo)準(zhǔn),得到了廣泛支持 |
2007年10月 | ES4.0草案發(fā)布 | 各大廠商意見(jiàn)分歧,該方案未通過(guò) |
2008年7月 | 發(fā)布ES3.1,并改名為ECMAScript 5 | 廢除ECMAScript 4.0,所以4.0版本不存在 |
2009年12月 | ESt 5.0 正式發(fā)布 | |
2011年6月 | ES5.1 發(fā)布 | 該版本成為了 ISO 國(guó)際標(biāo)準(zhǔn)(ISO/IEC 16262:2011) |
2013年12月 | ES6 草案發(fā)布 | |
2015年6月 | ES6 正式發(fā)布,并且更名為“ECMAScript 2015” | TC39委員會(huì)決定每年發(fā)布一個(gè)ECMAScript 的版本 |
JavaScript引擎是指用于處理以及執(zhí)行JavaScript腳本的虛擬機(jī)。
常見(jiàn)的JavaScript引擎:
??
我們描述了一個(gè)名為 Jalangi 的工具框架,用于 JavaScript 程序的動(dòng)態(tài)分析和符號(hào)執(zhí)行測(cè)試。該框架是用 JavaScript 編寫(xiě)的,允許對(duì) JavaScript 進(jìn)行各種重載動(dòng)態(tài)分析。Jalangi 包含兩個(gè)關(guān)鍵技術(shù):1)選擇性錄制回放,這是一種能夠錄制并準(zhǔn)確地回放用戶選擇的程序部分的技術(shù);2)陰影值和陰影執(zhí)行,可以輕松實(shí)現(xiàn)重量級(jí)動(dòng)態(tài)分析,例如,condicolic 測(cè)試和污點(diǎn)跟蹤。Jalangi 通過(guò)對(duì)源代碼注入進(jìn)行檢測(cè),這使得它可以實(shí)現(xiàn)跨平臺(tái)移植。根據(jù) Apache 2.0 許可,可以從https://github.com/SRA-SiliconValley/jalangi獲得Jalangi。我們?cè)赟unSpider基準(zhǔn)套件和五個(gè)Web應(yīng)用程序上對(duì)Jalangi的評(píng)估表明,Jalangi在錄制過(guò)程中的平均速度降低了26倍,在重放和分析過(guò)程中的速度平均降低了30倍。與類似工具(如PIN和針對(duì)x86二進(jìn)制文件的Valgrind)報(bào)告的類似情況相比,速度的降低是可比的。
JavaScript;動(dòng)態(tài)分析;符號(hào)執(zhí)行測(cè)試
JavaScript 是編寫(xiě)客戶端 web 應(yīng)用程序的首選語(yǔ)言,并且在編寫(xiě)移動(dòng)應(yīng)用程序(例如用于 Tizen OS 和 iOS)、桌面應(yīng)用程序(如 Windows 8 和 Gnome 桌面應(yīng)用程序)和服務(wù)器端應(yīng)用程序(如 node.js)時(shí)越來(lái)越流行。但是,針對(duì) JavaScript 應(yīng)用程序的分析、測(cè)試和調(diào)試的工具較少。我們已經(jīng)開(kāi)發(fā)了一個(gè)簡(jiǎn)單而強(qiáng)大的框架,稱為 Jalangi,用于為 JavaScript 編寫(xiě)重載動(dòng)態(tài)分析。本文簡(jiǎn)要介紹了該框架及其使用場(chǎng)景。該框架提供了一些有用的抽象和 API,大大簡(jiǎn)化了 JavaScript 動(dòng)態(tài)分析的實(shí)現(xiàn)。關(guān)于 Jalangi 背后的技術(shù)的詳細(xì)描述可以在[6]中找到。
Jalangi 可以在任何瀏覽器或 node.js 上工作。我們通過(guò)選擇性的源代碼檢測(cè)來(lái)實(shí)現(xiàn)瀏覽器的獨(dú)立性。即使某些源文件沒(méi)有檢測(cè),Jalangi 也可以運(yùn)行。對(duì) Jalangi 的分析分兩個(gè)階段進(jìn)行。在第一階段中,在用戶選擇的平臺(tái)(如 Android 上運(yùn)行的 mobile chrome)上執(zhí)行并錄制一個(gè)插入指令的 JavaScript 應(yīng)用程序。在第二階段中,錄制的數(shù)據(jù)用于在桌面環(huán)境中執(zhí)行用戶指定的動(dòng)態(tài)分析。
Jalangi 允許通過(guò)支持陰影值和陰影執(zhí)行輕松實(shí)現(xiàn)動(dòng)態(tài)分析。陰影值使我們能夠?qū)㈥幱爸蹬c程序中使用的任何值相關(guān)聯(lián)。在 Jalangi 中,我們使用陰影值和執(zhí)行實(shí)現(xiàn)了幾個(gè)動(dòng)態(tài)分析:1)共同語(yǔ)言測(cè)試,2)純符號(hào)執(zhí)行,3)跟蹤 null 和 undefined 的來(lái)源,4)檢測(cè)可能的類型不一致,5)簡(jiǎn)單的對(duì)象分配分析器,6)簡(jiǎn)單的動(dòng)態(tài)污染分析。
我們提供了 Jalangi 的技術(shù)細(xì)節(jié)概要。有關(guān)更詳細(xì)的技術(shù)討論,請(qǐng)參閱[6]。用戶標(biāo)識(shí)由 Jalangi 檢測(cè)的 web 應(yīng)用程序的一個(gè)子集,以便錄制和回放。在錄制階段,將在用戶選擇的平臺(tái)上執(zhí)行生成的檢測(cè)代碼。即使使用了用戶代碼的子集,整個(gè)應(yīng)用程序也會(huì)在錄制階段執(zhí)行,包括已插入和未插入 JavaScript 代碼以及本機(jī)代碼。但是,在回放階段,Jalangi 僅回放已檢測(cè)的代碼。Jalangi 具有在用戶平臺(tái)上完整執(zhí)行 JavaScript 應(yīng)用程序的功能,使錄制的執(zhí)行可以在開(kāi)發(fā)筆記本電腦/臺(tái)式機(jī) JavaScript 引擎上進(jìn)行調(diào)試,以進(jìn)行調(diào)試,包括移動(dòng)瀏覽器和基于 node.js 的系統(tǒng),或具有嵌入式 JavaScript 引擎的集成開(kāi)發(fā)系統(tǒng)。這種方法還支持使用支持陰影執(zhí)行的底層陰影值實(shí)現(xiàn)動(dòng)態(tài)分析。
通過(guò)錄制執(zhí)行期間從內(nèi)存加載的每個(gè)值,并在回放期間在相關(guān)內(nèi)存加載期間重新使用它們,可以有效地提供錄制和回放。這種方法雖然聽(tīng)起來(lái)不錯(cuò),但也存在一些挑戰(zhàn),例如:(1)如何有效地錄制函數(shù)和對(duì)象?(2) 如何在未檢測(cè)本機(jī)函數(shù)(例如,JavaScript 事件 dispather)調(diào)用檢測(cè)函數(shù)時(shí)提供回放?通過(guò)提供間接記錄(其中唯一的數(shù)字標(biāo)識(shí)符與每個(gè)對(duì)象和功能相關(guān)聯(lián))以及記錄這些標(biāo)識(shí)符的值來(lái)解決第一個(gè)問(wèn)題。第二個(gè)問(wèn)題通過(guò)顯式錄制和調(diào)用已檢測(cè)功能來(lái)解決,這些功能又從未插入代碼中調(diào)用,或者由于 JavaScript 事件處理程序調(diào)度而執(zhí)行。
此外,我們還觀察到,回放期間的內(nèi)存負(fù)載值可以通過(guò)執(zhí)行檢測(cè)代碼來(lái)計(jì)算,而無(wú)需錄制所有內(nèi)存負(fù)載的值。通過(guò)僅記錄必要的內(nèi)存負(fù)載,這用于提高 Jalangi 的效率。為了確定是否必須記錄內(nèi)存負(fù)載的值,Jalangi 在記錄階段跟蹤影子內(nèi)存,該影子內(nèi)存將在執(zhí)行實(shí)際代碼時(shí)隨實(shí)際內(nèi)存一起更新。 執(zhí)行本機(jī)和未執(zhí)行的代碼不會(huì)對(duì)影子內(nèi)存進(jìn)行更新。為了確保在重放階段可以使用正確的值,只有在記錄階段在內(nèi)存位置存儲(chǔ)的值存在差異時(shí)(例如,如果在內(nèi)存位置的值之間存在差異),Jalangi 才會(huì)存儲(chǔ)內(nèi)存負(fù)載的值。 實(shí)際加載的內(nèi)存及其關(guān)聯(lián)值存儲(chǔ)在影子內(nèi)存中)。
在 Jalangi 中,在回放階段執(zhí)行中使用的任何值都可以替換為帶注釋的值,該值可以為實(shí)際使用的值提供附加信息。例如,支持污染分析所需的額外污染信息,或者可能是與符號(hào)執(zhí)行中的實(shí)際值相關(guān)的信息,這些信息可以以符號(hào)表達(dá)式的形式提供。Jalangi 使用 concolvalue 類型的對(duì)象來(lái)表示帶注釋的值。
在 Jalangi,我們進(jìn)行了以下動(dòng)態(tài)分析:
Jalangi 可在https://github.com/SRA SiliconValley/Jalangi 上獲得。我們已經(jīng)用 JavaScript 實(shí)現(xiàn)了 Jalangi。
Jalangi 通過(guò)檢測(cè) JavaScript 代碼進(jìn)行操作。表 3 顯示了在表 1 中插入代碼后獲得的代碼。在檢測(cè)期間,Jalangi 從 Jalangi 庫(kù)插入各種回調(diào)函數(shù)?;卣{(diào)函數(shù)列在表 2 中。這些函數(shù)在 JavaScript 中包裝了各種操作。Jalangi 的選擇性錄制重放引擎是通過(guò)定義這些回調(diào)函數(shù)來(lái)實(shí)現(xiàn)的。
Jalangi 將工具庫(kù)公開(kāi)為函數(shù)工具代碼。這也使我們能夠動(dòng)態(tài)地測(cè)試在運(yùn)行時(shí)創(chuàng)建和計(jì)算的任何代碼。例如,我們將對(duì) eval(s)的任何調(diào)用修改為 eval(instrumentCode(s))。
我們?cè)?JavaScript-SunSpider(http://www.webkit.org/perf/SunSpider/SunSpider.html)基準(zhǔn)套件中的26個(gè)程序和使用HTML5/JavaScript為T(mén)izen操作系統(tǒng)編寫(xiě)的5個(gè)web應(yīng)用程序上運(yùn)行了Jalangi的錄制回放。(https://developer.tizen.org/下載/示例web應(yīng)用程序)。表4顯示了與錄制階段和三個(gè)動(dòng)態(tài)分析相關(guān)的開(kāi)銷:無(wú)分析(用null表示)、跟蹤空和未定義的來(lái)源(用track表示)和污染分析(用taint表示)。我們還報(bào)告為每個(gè)基準(zhǔn)程序錄制的值的數(shù)量。實(shí)驗(yàn)是在配備2.3 GHz Intel Core i7 和 8 GB RAM 的筆記本電腦上進(jìn)行的,并在 Chrome 25 上運(yùn)行了網(wǎng)絡(luò)應(yīng)用,并在 node.js 0.8.14 上執(zhí)行了重放。
我們沒(méi)有衡量 web 應(yīng)用程序的增長(zhǎng)速度,因?yàn)樗鼈兇蠖嗍墙换ナ綉?yīng)用程序。對(duì)于 SunSpider 基準(zhǔn)套件,在記錄階段,我們觀察到平均速度降低了 26 倍,最低為 1.5 倍,最高為 93 倍。 在重播階段進(jìn)行空分析時(shí),我們觀察到平均速度降低了 30 倍,最小值為 1.5 倍,最大值為 93 倍。 跟蹤分析顯示,平均速度降低了 32.75 倍,最小值為 1.5 倍,最大值為 96 倍。
5.1 JALANGI 動(dòng)態(tài)分析檢測(cè)到的問(wèn)題
Jalangi 可能的類型不一致檢查器發(fā)現(xiàn),SunSpider 基準(zhǔn)測(cè)試套件的 3d-cube.js 中的 CreateP 函數(shù)主要用作構(gòu)造函數(shù),但在某個(gè)位置它被稱為函數(shù)。作為函數(shù)調(diào)用的結(jié)果,程序在全局對(duì)象中創(chuàng)建一個(gè)不必要的 V 字段。我們認(rèn)為此調(diào)用可能是編程錯(cuò)誤。
Jalangi 的對(duì)象分配分析器注意到附件游戲 webapp 中的 getValue(place,_board)方法創(chuàng)建了一個(gè)常量對(duì)象數(shù)千次。我們相信,通過(guò)在方法之外提升常數(shù)對(duì)象,可以避免這種不必要的常數(shù)對(duì)象的創(chuàng)建。
據(jù)我們所知,Jalangi 是第一個(gè) JavaScript 動(dòng)態(tài)分析框架。很少有工具可以執(zhí)行 JavaScript 程序的錄制回放。JSBench 使用錄制回放機(jī)制創(chuàng)建 JavaScript 基準(zhǔn)。Mugshot 捕獲所有事件,以確定地重放 web 應(yīng)用程序的執(zhí)行。Ripley 在服務(wù)器端副本上復(fù)制客戶端 JavaScript 程序的執(zhí)行。
Jalangi 已經(jīng)處理了 JavaScript 的各種具有挑戰(zhàn)性的細(xì)節(jié)。由于可以處理 JavaScript 的所有令人擔(dān)憂的問(wèn)題,因此可以輕松地在 Jalangi 框架中實(shí)施動(dòng)態(tài)分析。我們期望 Jalangi 將有助于未來(lái) JavaScript 動(dòng)態(tài)分析的研究。
本文由南京大學(xué)軟件學(xué)院 2020 級(jí)碩士李彤宇轉(zhuǎn)述翻譯 感謝國(guó)家重點(diǎn)研發(fā)計(jì)劃(2018YFB1003900)和國(guó)家自然科學(xué)基金(61832009,61932012)支持!
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。