語彙割り当て
JSDefender により、宣言、および識別子への解決済み/未解決の参照が含まれる語彙スコープの階層を反映した lexical-map.json
ファイルが、既定で作成されます。割り当てファイルの場所と名前を変更したり、割り当てファイルを無効にしたりするには、コマンド ライン オプション -m
または --mapout
、あるいは構成プロパティ mapFile
を使用します。いくつかの例を次に示します。
割り当てファイルに my-map.json
という名前を付け、それを maps
フォルダーに移動させるようにしましょう。
CLI を使用する場合:
jsdefender -m ./map/my-map.json
jsdefender --mapout ./map/my-map.json
構成ファイルを使用する場合:
{
// ...
"mapFile": "./map/my-map.json"
// ...
}
語彙割り当ての作成を無効にしてみましょう。
CLI を使用する場合:
jsdefender -m off
jsdefender --mapout off
構成ファイルを使用する場合:
{
// ...
"mapFile": "off"
// ...
}
語彙割り当ての形式
語彙割り当ては JSON ファイルです。語彙割り当ての構造を最も簡単に理解する方法は、この語彙割り当てが具体的な保護を探す方法を見ることです。
以下のファイルを保護の対象にするとします。
file1.js
:
function getGreeting() {
var name = getName();
return "Hello, " + name + ", from multifile demo!";
}
file2.js
:
function getName() {
return "Developer";
}
var element = document.getElementById("msg");
element.textContent = getGreeting();
使用する構成ファイルは次のとおりです。
jsdefender.config.json
:
{
"inputs": ["file1.js", "file2.js"],
"settings": {
"localDeclarations": {
"nameMangling": "sequential"
},
"stringLiterals": false
}
}
JSDefender 実行後の保護されたファイルは次のとおりです(識別子が名前変更されていることがわかります)。
file1.js
:
function _0x000000() {
var _0x000001 = _0x000002(); // file2.js で宣言された ID
return "Hello, " + _0x000001 + ", from multifile demo!";
}
file2.js
:
function _0x000002() {
// file1.js にはこの ID への参照があります
return "Developer";
}
var _0x000003 = document.getElementById("msg");
_0x000003.textContent = _0x000000();
そして、lexical-map.json
ファイルを次に示します。
{
"0:C:\\demos\\file1.js": {
"declarations": [
{
"globalId": 0,
"localId": 0,
"name": "getGreeting",
"line": 1,
"column": 9,
"mangledName": "_0x000000",
"type": "FunctionDeclaration",
"referenceCount": 2,
"nestedReferenceCount": 0,
"tree": 0
},
{
"globalId": 2,
"localId": 1,
"name": "getName",
"line": 1,
"column": 9,
"mangledName": "_0x000002",
"type": "FunctionDeclaration",
"referenceCount": 2,
"nestedReferenceCount": 1,
"tree": 1
},
{
"globalId": 3,
"localId": 2,
"name": "element",
"line": 5,
"column": 4,
"mangledName": "_0x000003",
"type": "Var",
"referenceCount": 2,
"nestedReferenceCount": 0,
"tree": 1
}
],
"references": [
{
"id": "getGreeting",
"globalId": 0,
"line": 1,
"column": 9
}
],
"unresolved": [],
"nested": [
{
"declarations": [
{
"globalId": 1,
"localId": 0,
"name": "name",
"line": 2,
"column": 8,
"mangledName": "_0x000001",
"type": "Var",
"referenceCount": 2,
"nestedReferenceCount": 0,
"tree": 0
}
],
"references": [
{
"id": "name",
"globalId": 1,
"line": 2,
"column": 8
},
{
"id": "getName",
"globalId": 2,
"line": 2,
"column": 15
},
{
"id": "name",
"globalId": 1,
"line": 3,
"column": 22
}
],
"unresolved": [],
"nested": []
}
]
},
"1:C:\\demos\\file2.js": {
"declarations": "inherit",
"references": [
{
"id": "getName",
"globalId": 2,
"line": 1,
"column": 9
},
{
"id": "element",
"globalId": 3,
"line": 5,
"column": 4
},
{
"id": "element",
"globalId": 3,
"line": 6,
"column": 0
},
{
"id": "getGreeting",
"globalId": 0,
"line": 6,
"column": 22
}
],
"unresolved": [
{
"id": "document",
"line": 5,
"column": 14
}
],
"nested": [
{
"declarations": [],
"references": [],
"unresolved": [],
"nested": []
}
]
}
}
このファイルは長いです。その構造を単純化したものを次に示します。
{
"0:C:\\demos\\file1.js": { // 1 番目のソース ファイル
"declarations": [ /* ... */ ], // グローバルな(最上位の)宣言
"references": [ /* ... */ ], // 解決された参照
"unresolved": [], // 未解決の参照
"nested": [ /* ... */ ] // 入れ子になった語彙スコープ
},
"1:C:\\demos\\file2.js": { // 2 番目のソース ファイル
"declarations": "inherit", // 宣言を継承します
"references": [ /* ... */ ], // 解決された参照
"unresolved": [], // 未解決の参照
"nested": [ /* ... */ ] // 入れ子になった語彙スコープ
}
}
ファイルとグローバル宣言
割り当てファイルには、すべてのソース ファイルのプロパティが含まれています。プロパティは、ソース ファイルの序数(インデックス)と絶対パスから成ります。ソース ファイルごとに以下のリストが含まれます。
declarations
:特定のファイル内のすべての宣言。references
:ソース ファイル内のすべての解決済みの参照。参照が解決済みであるとは、JSDefender が識別子を任意のファイル内、任意の語彙スコープ内にあるその宣言に関係付けることができたということです。unresolved
:ソース ファイル内のすべての未解決の参照。JSDefender がどのファイルの宣言内にも識別子を見つけられない場合、その識別子は未解決としてマークされます。console
、window
、document
、process
などのグローバル オブジェクトがこのリストに入ります。nested
:グローバル語彙スコープに入れ子になっている語彙スコープ。このノードには、declarations
、references
、unresolved
、nested
の 4 要素のタプルの再帰的な階層があります。
最上位(グローバル)の語彙スコープは、他の語彙スコープとは異なる方法で管理されます。最初のファイルの declarations
セクションには、最上位の宣言がすべて含まれます。これは、JavaScript がすべてのプログラム レベルの宣言をグローバルなスコープに巻き上げるという事実を反映したものです。それらの宣言が最初のファイルのスコープに含まれることを示すのが、後続ファイルの declarations
セクションの値 "inherit"
です。
宣言の項目
declarations
リストの各エントリには、いくつかのプロパティが含まれています。その例を次の JSON スニペットで示します。
{
"0:C:\\demos\\file1.js": {
"declarations": [
{
"globalId": 0,
"localId": 0,
"name": "getGreeting",
"line": 1,
"column": 9,
"mangledName": "_0x000000",
"type": "FunctionDeclaration",
"referenceCount": 2,
"nestedReferenceCount": 0,
"tree": 0
},
{
"globalId": 2,
"localId": 1,
"name": "getName",
"line": 1,
"column": 9,
"mangledName": "_0x000002",
"type": "FunctionDeclaration",
"referenceCount": 2,
"nestedReferenceCount": 1,
"tree": 1
},
// ...
],
// ...
}
globalId
:各宣言には、ソース コード全体で一意のグローバル識別子(ゼロから始まる連続番号)が付けられます。その値がこのフィールドに示されます。この値は名前の改変で使用されます。localId
:各宣言には、ホストするスコープ内で一意のスコープ固有識別子(ゼロから始まる連続番号)が付けられます。その値がこのフィールドに示されます。この値は名前の改変で使用されます。name
:宣言の名前変更前の元の名前。line
:ソース コードにおける宣言識別子の行番号。column
:ソース コードにおける宣言識別子の桁位置。mangledName
:宣言の名前変更が適用された場合、このプロパティに新しい名前が表示されます。type
:宣言の種類。referenceCount
:宣言への(解決済み/未解決の)参照の総数。nestedReferenceCount
:入れ子になった宣言スコープ内の宣言への(解決済み/未解決の)参照の総数。tree
:宣言を含んだソース ファイル(ツリー)の識別子。
解決済み/未解決の参照
JSDefender は、語彙割り当てを作成する際、すべての宣言を対応する識別子参照に関係付けます。すべての宣言が参照に割り当てられる際、保護エンジンが参照を解決すると共に、参照を対応する宣言にバインドしようとします。この手順が成功した場合、参照は解決済みと宣言され、グローバル ID を使って宣言にバインドされます。参照に対応する宣言が見つからない場合、参照は未解決のままとなります。
解決済みの参照には以下のプロパティが設定されます。
name
:参照された宣言の名前。globalId
:関連付けられた宣言のグローバル識別子。line
:ソース コードにおける参照識別子の行番号。column
:ソース コードにおける参照識別子の桁位置。
未解決の参照には、globalId
プロパティを除いた上記のすべてのプロパティが設定されます。
次のスニペットは file2.js
内の参照を示しています。
{
"1:C:\\demos\\file2.js": {
"declarations": "inherit",
"references": [
{
"id": "getName",
"globalId": 2,
"line": 1,
"column": 9
},
{
"id": "element",
"globalId": 3,
"line": 5,
"column": 4
},
{
"id": "element",
"globalId": 3,
"line": 6,
"column": 0
},
{
"id": "getGreeting",
"globalId": 0,
"line": 6,
"column": 22
}
],
"unresolved": [
{
"id": "document", // ドキュメントは解決できませんでした
"line": 5,
"column": 14
}
],
"nested": [ /* ... */ ]
}
}
解決済みの参照が globalId
プロパティによって宣言を参照していることがわかります。