整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          JS中的重載-如何實(shí)現(xiàn)一個(gè)類似這樣的功能,我也想玩玩

          JS中的重載-如何實(shí)現(xiàn)一個(gè)類似這樣的功能,我也想玩玩

          數(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重載的方式

          在js中主要有以下幾種實(shí)現(xiàn)重載的方式:

          1. 使用剩余參數(shù)的形式來(lái)接收可變的參數(shù),并通過(guò)一些判斷手段來(lái)處理不同情況的邏輯。
          2. 使用arguments的形式,來(lái)動(dòng)態(tài)判斷需要執(zhí)行的操作
          3. 使用proxy的形式來(lái)攔截函數(shù)的行為,以達(dá)到控制不同的邏輯分支。

          前兩種方式比較相似,思路一樣,只是實(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)的,以及它能滿足我們什么樣的需求?

          這是什么呢?

          需求假設(shè)

          我們現(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)。

          實(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è)。

          默認(rèn)處理

          從上面的代碼中我們發(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è)道理,也是支持的。

          類型擴(kuò)展

          上面的類型約定我們可能看起來(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)。

          結(jié)語(yǔ)

          目前這種方式只能支持函數(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

          1.JavaScript語(yǔ)言

          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 的版本

          2.JavaScript引擎

          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)告的類似情況相比,速度的降低是可比的。

          關(guān)鍵詞

          JavaScript;動(dòng)態(tài)分析;符號(hào)執(zhí)行測(cè)試

          一 導(dǎo)言

          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)污染分析。

          二 技術(shù)細(xì)節(jié)

          我們提供了 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)表示帶注釋的值。

          三 動(dòng)態(tài)分析

          在 Jalangi,我們進(jìn)行了以下動(dòng)態(tài)分析:

          • 符號(hào)執(zhí)行測(cè)試:符號(hào)執(zhí)行測(cè)試沿著具體的執(zhí)行路徑執(zhí)行符號(hào)執(zhí)行,生成表示輸入值約束的邏輯公式,并求解約束以生成新的測(cè)試輸入,這些新的測(cè)試輸入將沿著以前未探索的路徑執(zhí)行程序。Jalangi 中的符號(hào)執(zhí)行測(cè)試支持對(duì)整數(shù),字符串和對(duì)象類型的約束以及新穎的類型約束。我們引入類型約束來(lái)處理 JavaScript 的動(dòng)態(tài)特性,對(duì)于程序的不同可行執(zhí)行路徑,輸入變量的類型可以不同。
          • 純符號(hào)執(zhí)行:純符號(hào)執(zhí)行象征性地執(zhí)行程序,從不為了回溯而重新啟動(dòng)程序。它在執(zhí)行分支語(yǔ)句之前檢查狀態(tài),執(zhí)行一個(gè)分支,然后使用檢查點(diǎn)狀態(tài)回溯以探索另一個(gè)分支。對(duì)于小程序,純符號(hào)執(zhí)行避免了由于重復(fù)重新啟動(dòng)而造成的時(shí)間浪費(fèi)。
          • 跟蹤空值和未定義值的來(lái)源:此分析錄制空值和未定義值來(lái)源的源代碼位置,并在由于空值或未定義值而發(fā)生錯(cuò)誤時(shí)報(bào)告位置。每當(dāng)由于這些文本(例如訪問(wèn)空值的字段)而出現(xiàn)錯(cuò)誤時(shí),就會(huì)向用戶報(bào)告文本的陰影值。這樣的報(bào)告有助于程序員輕松識(shí)別空值的來(lái)源。
          • 檢測(cè)可能的類型不一致:動(dòng)態(tài)分析檢查在給定程序位置創(chuàng)建的對(duì)象是否可以采用多個(gè)不一致的類型。它計(jì)算在程序中的每個(gè)定義位置創(chuàng)建的對(duì)象和函數(shù)值的類型。如果在程序位置定義的對(duì)象或函數(shù)值在執(zhí)行期間被觀察到具有多個(gè)類型,則分析報(bào)告程序位置和觀察到的類型。有時(shí),這種類型的不一致可能會(huì)指向程序中的潛在錯(cuò)誤。我們已經(jīng)在兩個(gè) SunSpider 基準(zhǔn)測(cè)試程序中注意到了這樣的問(wèn)題。
          • 簡(jiǎn)單對(duì)象分配探查器:此動(dòng)態(tài)分析錄制在給定分配站點(diǎn)創(chuàng)建的對(duì)象數(shù)量以及訪問(wèn)對(duì)象的頻率。它報(bào)告在給定分配站點(diǎn)創(chuàng)建的對(duì)象是只讀的還是常量。它還報(bào)告對(duì)象創(chuàng)建時(shí)間和對(duì)象的最新訪問(wèn)時(shí)間之間的最大和平均差異。如果分配站點(diǎn)創(chuàng)建的常量對(duì)象太多,則可能導(dǎo)致內(nèi)存效率低下。我們?cè)诨鶞?zhǔn)測(cè)試套件中的一個(gè) web 應(yīng)用程序中發(fā)現(xiàn)了這樣一個(gè)問(wèn)題。
          • 動(dòng)態(tài)污染分析:動(dòng)態(tài)污染分析是一種信息流分析形式,它檢查信息是否可以從一組特定的內(nèi)存位置(稱為源)流向另一組內(nèi)存位置(稱為匯)。我們?cè)?Jalangi 中實(shí)現(xiàn)了一種簡(jiǎn)單的動(dòng)態(tài)污染分析形式。在分析中,我們將任何對(duì)象的任何字段的讀?。ㄒ郧拔从蓹z測(cè)源編寫(xiě))視為污染源。我們將任何可能改變程序控制流的內(nèi)存位置的讀取都視為接收器。我們附加污染信息與實(shí)際值的陰影值。

          四 實(shí)施

          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))。

          五 Jalangi 的表現(xiàn)

          我們?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)建。

          六 相關(guān)工作

          據(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í)行。

          七 結(jié)論

          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)支持!


          主站蜘蛛池模板: 日韩精品人妻av一区二区三区| 国产亚洲福利一区二区免费看| 成人午夜视频精品一区| 杨幂AV污网站在线一区二区| 美女福利视频一区二区 | 国产在线一区二区杨幂| 美女视频免费看一区二区| 一区二区三区免费视频播放器| 国产日韩AV免费无码一区二区三区 | 国产精品久久久久久一区二区三区| 人妻久久久一区二区三区| 国产在线一区二区三区| 国产主播一区二区| 韩国一区二区三区| 中文字幕精品亚洲无线码一区应用| 性无码免费一区二区三区在线 | 亚洲日韩国产一区二区三区在线| 日本一区二区三区在线观看视频| 冲田杏梨高清无一区二区| 亚洲一区影音先锋色资源| 四虎在线观看一区二区| 亚洲色婷婷一区二区三区| 亚洲高清一区二区三区电影| 国产福利电影一区二区三区,亚洲国模精品一区 | 国产乱码精品一区二区三区麻豆| 国产免费一区二区三区免费视频| 亚洲成AV人片一区二区密柚| 少妇一夜三次一区二区| 波多野结衣免费一区视频 | 国产在线一区二区三区在线| 日韩精品一区二区三区大桥未久 | 国产一区韩国女主播| 亚洲午夜福利AV一区二区无码| 亚洲乱色熟女一区二区三区蜜臀| 日韩精品电影一区亚洲| 麻豆AV天堂一区二区香蕉| 无码人妻一区二区三区在线视频 | 久久精品国产亚洲一区二区三区| 亚洲AV日韩AV天堂一区二区三区| 久久亚洲一区二区| 国产情侣一区二区|