@dacely/toildefender 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -661
- package/NOTICE.md +16 -16
- package/README.md +380 -380
- package/cli.js +168 -168
- package/defendjs.js +6 -6
- package/docs/all-modes-output.demo.js +673 -673
- package/estest.js +49 -49
- package/esutils.js +107 -107
- package/logger.js +28 -28
- package/obfuscator.js +534 -534
- package/package.json +108 -108
- package/processors/deadCode.js +62 -62
- package/processors/flattener.js +808 -808
- package/processors/health.js +55 -55
- package/processors/identifiers.js +256 -256
- package/processors/literalObfuscator.js +40 -40
- package/processors/literals.js +233 -233
- package/processors/methods.js +332 -332
- package/processors/modules.js +231 -231
- package/processors/normalizer.js +490 -490
- package/processors/numericVm.js +950 -950
- package/processors/postprocessing.js +69 -69
- package/processors/preprocessing.js +248 -248
- package/processors/scopes.js +177 -177
- package/processors/uglifier.js +25 -25
- package/processors/variables.js +185 -185
- package/toildefender.js +7 -7
- package/traverser.js +115 -115
- package/utils.js +135 -135
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var assert = require("assert");
|
|
4
|
-
|
|
5
|
-
var _ = require("lodash");
|
|
6
|
-
|
|
7
|
-
var estest = require("../estest");
|
|
8
|
-
var traverser = require("../traverser");
|
|
9
|
-
var utils = require("../utils");
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Merges nested bind calls like
|
|
13
|
-
* veilmark$bind(veilmark$bind(main, 1234), 5678)
|
|
14
|
-
* to
|
|
15
|
-
* veilmark$bind(main, 1234, 5678)
|
|
16
|
-
* @param {Node} node
|
|
17
|
-
* @returns {Node}
|
|
18
|
-
*/
|
|
19
|
-
function mergeNestedBinds(node) {
|
|
20
|
-
assert.ok(estest.isNode(node));
|
|
21
|
-
|
|
22
|
-
if (isBindCall(node)) {
|
|
23
|
-
return mergeNestedBinds(node.arguments[0]).concat(node.arguments.slice(1));
|
|
24
|
-
} else {
|
|
25
|
-
return [ node ];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Checks whether node is a call to veilmark$bind.
|
|
31
|
-
* @param {Node} node
|
|
32
|
-
* @returns {boolean}
|
|
33
|
-
*/
|
|
34
|
-
function isBindCall(node) {
|
|
35
|
-
assert.ok(estest.isNode(node));
|
|
36
|
-
|
|
37
|
-
return node.type == "CallExpression"
|
|
38
|
-
&& node.callee.type == "Identifier"
|
|
39
|
-
&& node.callee.name == "veilmark$bind";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
module.exports = class Postprocessing {
|
|
43
|
-
|
|
44
|
-
constructor (logger) {
|
|
45
|
-
this.logger = logger;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Does postprocessing.
|
|
50
|
-
* @param {Node} ast Root node
|
|
51
|
-
* @return {Node} Root node
|
|
52
|
-
*/
|
|
53
|
-
do (ast) {
|
|
54
|
-
assert.ok(estest.isNode(ast));
|
|
55
|
-
|
|
56
|
-
return traverser.traverse(ast, [], (node, stack) => {
|
|
57
|
-
if (isBindCall(node)) {
|
|
58
|
-
node.arguments = mergeNestedBinds(node);
|
|
59
|
-
} else if (node.type == "BlockStatement" || node.type == "Program") {
|
|
60
|
-
node.body = node.body.filter(x => estest.isNode(x) && x.type != "EmptyStatement");
|
|
61
|
-
} else if (node.type == "SwitchCase") {
|
|
62
|
-
node.consequent = node.consequent.filter(x => estest.isNode(x) && x.type != "EmptyStatement");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return node;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var assert = require("assert");
|
|
4
|
+
|
|
5
|
+
var _ = require("lodash");
|
|
6
|
+
|
|
7
|
+
var estest = require("../estest");
|
|
8
|
+
var traverser = require("../traverser");
|
|
9
|
+
var utils = require("../utils");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Merges nested bind calls like
|
|
13
|
+
* veilmark$bind(veilmark$bind(main, 1234), 5678)
|
|
14
|
+
* to
|
|
15
|
+
* veilmark$bind(main, 1234, 5678)
|
|
16
|
+
* @param {Node} node
|
|
17
|
+
* @returns {Node}
|
|
18
|
+
*/
|
|
19
|
+
function mergeNestedBinds(node) {
|
|
20
|
+
assert.ok(estest.isNode(node));
|
|
21
|
+
|
|
22
|
+
if (isBindCall(node)) {
|
|
23
|
+
return mergeNestedBinds(node.arguments[0]).concat(node.arguments.slice(1));
|
|
24
|
+
} else {
|
|
25
|
+
return [ node ];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Checks whether node is a call to veilmark$bind.
|
|
31
|
+
* @param {Node} node
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
function isBindCall(node) {
|
|
35
|
+
assert.ok(estest.isNode(node));
|
|
36
|
+
|
|
37
|
+
return node.type == "CallExpression"
|
|
38
|
+
&& node.callee.type == "Identifier"
|
|
39
|
+
&& node.callee.name == "veilmark$bind";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = class Postprocessing {
|
|
43
|
+
|
|
44
|
+
constructor (logger) {
|
|
45
|
+
this.logger = logger;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Does postprocessing.
|
|
50
|
+
* @param {Node} ast Root node
|
|
51
|
+
* @return {Node} Root node
|
|
52
|
+
*/
|
|
53
|
+
do (ast) {
|
|
54
|
+
assert.ok(estest.isNode(ast));
|
|
55
|
+
|
|
56
|
+
return traverser.traverse(ast, [], (node, stack) => {
|
|
57
|
+
if (isBindCall(node)) {
|
|
58
|
+
node.arguments = mergeNestedBinds(node);
|
|
59
|
+
} else if (node.type == "BlockStatement" || node.type == "Program") {
|
|
60
|
+
node.body = node.body.filter(x => estest.isNode(x) && x.type != "EmptyStatement");
|
|
61
|
+
} else if (node.type == "SwitchCase") {
|
|
62
|
+
node.consequent = node.consequent.filter(x => estest.isNode(x) && x.type != "EmptyStatement");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return node;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
};
|
|
@@ -1,248 +1,248 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var _ = require("lodash");
|
|
4
|
-
|
|
5
|
-
var { Parser: Parser } = require("expr-eval");
|
|
6
|
-
|
|
7
|
-
const DEFAULT_PREPROCESSOR_VARIABLES = {
|
|
8
|
-
"true": 1,
|
|
9
|
-
"false": 0
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Generates code from an array of text nodes.
|
|
14
|
-
* @param {TextNode[]} nodes
|
|
15
|
-
* @returns {string}
|
|
16
|
-
*/
|
|
17
|
-
function codeFromNodeArray(nodes) {
|
|
18
|
-
let lines = [];
|
|
19
|
-
for (let node of nodes) {
|
|
20
|
-
lines[node.line] = node.text;
|
|
21
|
-
}
|
|
22
|
-
return lines.join("\n");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Removes shebang from beginning of code.
|
|
27
|
-
* @param {string} code
|
|
28
|
-
* @returns {string}
|
|
29
|
-
*/
|
|
30
|
-
function removeShebangs(code) {
|
|
31
|
-
if (_.startsWith(code, "#!")) {
|
|
32
|
-
code = code.split(/\r?\n/).slice(1).join("\n");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return code;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
class ArrayUtils {
|
|
39
|
-
/**
|
|
40
|
-
* Replaces all occurences of an object in an array with an other object in place.
|
|
41
|
-
* @param {Array} arr
|
|
42
|
-
* @param {Object} oldElem
|
|
43
|
-
* @param {Object} newElem
|
|
44
|
-
*/
|
|
45
|
-
static replace(arr, oldElem, newElem) {
|
|
46
|
-
for (let i = 0; i < arr.length; ++i) {
|
|
47
|
-
if (arr[i] == oldElem) {
|
|
48
|
-
arr[i] = newElem;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
class Node {
|
|
55
|
-
constructor() {
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Evaluates tree into an array of TextNodes.
|
|
60
|
-
* @param {Object.<string, string>} defines Preprocessor variables
|
|
61
|
-
* @returns {TextNode[]}
|
|
62
|
-
*/
|
|
63
|
-
eval(defines) {
|
|
64
|
-
throw new Error("Node.eval() can not be called directly");
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
class BlockNode extends Node {
|
|
69
|
-
constructor() {
|
|
70
|
-
super();
|
|
71
|
-
this.children = [];
|
|
72
|
-
}
|
|
73
|
-
eval(defines) {
|
|
74
|
-
return _.flatten(this.children.map(x => x.eval(defines)));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
class TextNode extends Node {
|
|
79
|
-
constructor(text) {
|
|
80
|
-
super();
|
|
81
|
-
this.text = text;
|
|
82
|
-
}
|
|
83
|
-
eval(defines) {
|
|
84
|
-
return [this];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
class DefineNode extends Node {
|
|
89
|
-
constructor(left, right) {
|
|
90
|
-
super();
|
|
91
|
-
this.left = left;
|
|
92
|
-
this.right = right;
|
|
93
|
-
}
|
|
94
|
-
eval(defines) {
|
|
95
|
-
defines[this.left] = this.right;
|
|
96
|
-
return [];
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
class ErrorNode extends Node {
|
|
101
|
-
constructor(message) {
|
|
102
|
-
super();
|
|
103
|
-
this.message = message;
|
|
104
|
-
}
|
|
105
|
-
eval(defines) {
|
|
106
|
-
throw new Error(this.message);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
class IfBlockNode extends BlockNode {
|
|
111
|
-
constructor(condition) {
|
|
112
|
-
super();
|
|
113
|
-
this.condition = condition;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Evaluates condition.
|
|
117
|
-
* @param {Object.<string, string>} defines Preprocessor variables
|
|
118
|
-
* @returns {boolean}
|
|
119
|
-
*/
|
|
120
|
-
evalCond(defines) {
|
|
121
|
-
let condition = this.condition;
|
|
122
|
-
condition = condition.replace(/!defined\(([\w\d]+)\)/, (match, p1) => !defines.hasOwnProperty(p1) ? "true" : "false");
|
|
123
|
-
condition = condition.replace(/defined\(([\w\d]+)\)/, (match, p1) => defines.hasOwnProperty(p1) ? "true" : "false");
|
|
124
|
-
return Parser.evaluate(condition, defines);
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Evaluates node with given condition result.
|
|
128
|
-
* @param {Object.<string, string>} defines Preprocessor variables
|
|
129
|
-
* @returns {boolean}
|
|
130
|
-
*/
|
|
131
|
-
evalWith(defines, result) {
|
|
132
|
-
if (result) {
|
|
133
|
-
return super.eval(defines);
|
|
134
|
-
} else {
|
|
135
|
-
return [];
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
eval(defines) {
|
|
139
|
-
return this.evalWith(defines, this.evalCond(defines));
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
class ElseBlockNode extends BlockNode {
|
|
144
|
-
constructor(ifNode) {
|
|
145
|
-
super();
|
|
146
|
-
this.ifNode = ifNode;
|
|
147
|
-
}
|
|
148
|
-
eval(defines) {
|
|
149
|
-
if (this.ifNode.evalCond(defines)) {
|
|
150
|
-
return this.ifNode.evalWith(defines, true);
|
|
151
|
-
} else {
|
|
152
|
-
return super.eval(defines);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
module.exports = class Preprocessing {
|
|
158
|
-
|
|
159
|
-
constructor (logger) {
|
|
160
|
-
this.logger = logger;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Processes preprocessor directives.
|
|
165
|
-
* @param {string} code
|
|
166
|
-
* @param {Object.<string, string>} preprocessorVariables
|
|
167
|
-
* @returns {string} Processed code
|
|
168
|
-
*/
|
|
169
|
-
processDirectives (code, preprocessorVariables) {
|
|
170
|
-
let lines = code.split(/\r?\n/), stack = [new BlockNode()];
|
|
171
|
-
|
|
172
|
-
let defines = {};
|
|
173
|
-
_.merge(defines, DEFAULT_PREPROCESSOR_VARIABLES);
|
|
174
|
-
_.merge(defines, preprocessorVariables);
|
|
175
|
-
|
|
176
|
-
for (let i = 0; i < lines.length; ++i) {
|
|
177
|
-
let line = lines[i];
|
|
178
|
-
let [, directive, parameters] = /^\s*\/\/\s*#(\w+)\s*(.+)?$/.exec(line) || [];
|
|
179
|
-
switch (directive) {
|
|
180
|
-
case undefined: {
|
|
181
|
-
let elem = new TextNode(line);
|
|
182
|
-
elem.line = i;
|
|
183
|
-
_.last(stack).children.push(elem);
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
case "define": {
|
|
187
|
-
let [, left, right] = /^\s*([\w\d]+)\s*(?:=\s*([\w\d]+))?\s*$/.exec(parameters);
|
|
188
|
-
let elem = new DefineNode(left, right);
|
|
189
|
-
elem.line = i;
|
|
190
|
-
_.last(stack).children.push(elem);
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
case "error": {
|
|
194
|
-
let elem = new ErrorNode(parameters);
|
|
195
|
-
elem.line = i;
|
|
196
|
-
_.last(stack).children.push(elem);
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
case "if":
|
|
200
|
-
case "ifdef":
|
|
201
|
-
case "ifndef": {
|
|
202
|
-
let elem =
|
|
203
|
-
directive == "if" ? new IfBlockNode(parameters) :
|
|
204
|
-
directive == "ifdef" ? new IfBlockNode(`defined(${parameters})`) :
|
|
205
|
-
directive == "ifndef" ? new IfBlockNode(`!defined(${parameters})`) :
|
|
206
|
-
null;
|
|
207
|
-
elem.line = i;
|
|
208
|
-
_.last(stack).children.push(elem);
|
|
209
|
-
stack.push(elem);
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
case "else": {
|
|
213
|
-
let elem = new ElseBlockNode(stack.pop());
|
|
214
|
-
elem.line = i;
|
|
215
|
-
ArrayUtils.replace(_.last(stack).children, elem.ifNode, elem);
|
|
216
|
-
stack.push(elem);
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
case "endif": {
|
|
220
|
-
let elem = stack.pop();
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
default: {
|
|
224
|
-
this.logger.warn(`Unknown preprocessor directive #${directive}`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (stack.length > 1) {
|
|
230
|
-
this.logger.warn("stack.length != 1 (preprocessor directive closing tag missing?)");
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return codeFromNodeArray(stack[0].eval(defines));
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Does preprocessing.
|
|
238
|
-
* @param {string} code
|
|
239
|
-
* @param {Object.<string, string>} preprocessorVariables
|
|
240
|
-
* @returns {string}
|
|
241
|
-
*/
|
|
242
|
-
process (code, preprocessorVariables) {
|
|
243
|
-
code = this.processDirectives(code, preprocessorVariables);
|
|
244
|
-
code = removeShebangs(code);
|
|
245
|
-
return code;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _ = require("lodash");
|
|
4
|
+
|
|
5
|
+
var { Parser: Parser } = require("expr-eval");
|
|
6
|
+
|
|
7
|
+
const DEFAULT_PREPROCESSOR_VARIABLES = {
|
|
8
|
+
"true": 1,
|
|
9
|
+
"false": 0
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generates code from an array of text nodes.
|
|
14
|
+
* @param {TextNode[]} nodes
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
function codeFromNodeArray(nodes) {
|
|
18
|
+
let lines = [];
|
|
19
|
+
for (let node of nodes) {
|
|
20
|
+
lines[node.line] = node.text;
|
|
21
|
+
}
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Removes shebang from beginning of code.
|
|
27
|
+
* @param {string} code
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
function removeShebangs(code) {
|
|
31
|
+
if (_.startsWith(code, "#!")) {
|
|
32
|
+
code = code.split(/\r?\n/).slice(1).join("\n");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return code;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class ArrayUtils {
|
|
39
|
+
/**
|
|
40
|
+
* Replaces all occurences of an object in an array with an other object in place.
|
|
41
|
+
* @param {Array} arr
|
|
42
|
+
* @param {Object} oldElem
|
|
43
|
+
* @param {Object} newElem
|
|
44
|
+
*/
|
|
45
|
+
static replace(arr, oldElem, newElem) {
|
|
46
|
+
for (let i = 0; i < arr.length; ++i) {
|
|
47
|
+
if (arr[i] == oldElem) {
|
|
48
|
+
arr[i] = newElem;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class Node {
|
|
55
|
+
constructor() {
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Evaluates tree into an array of TextNodes.
|
|
60
|
+
* @param {Object.<string, string>} defines Preprocessor variables
|
|
61
|
+
* @returns {TextNode[]}
|
|
62
|
+
*/
|
|
63
|
+
eval(defines) {
|
|
64
|
+
throw new Error("Node.eval() can not be called directly");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class BlockNode extends Node {
|
|
69
|
+
constructor() {
|
|
70
|
+
super();
|
|
71
|
+
this.children = [];
|
|
72
|
+
}
|
|
73
|
+
eval(defines) {
|
|
74
|
+
return _.flatten(this.children.map(x => x.eval(defines)));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class TextNode extends Node {
|
|
79
|
+
constructor(text) {
|
|
80
|
+
super();
|
|
81
|
+
this.text = text;
|
|
82
|
+
}
|
|
83
|
+
eval(defines) {
|
|
84
|
+
return [this];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class DefineNode extends Node {
|
|
89
|
+
constructor(left, right) {
|
|
90
|
+
super();
|
|
91
|
+
this.left = left;
|
|
92
|
+
this.right = right;
|
|
93
|
+
}
|
|
94
|
+
eval(defines) {
|
|
95
|
+
defines[this.left] = this.right;
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class ErrorNode extends Node {
|
|
101
|
+
constructor(message) {
|
|
102
|
+
super();
|
|
103
|
+
this.message = message;
|
|
104
|
+
}
|
|
105
|
+
eval(defines) {
|
|
106
|
+
throw new Error(this.message);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class IfBlockNode extends BlockNode {
|
|
111
|
+
constructor(condition) {
|
|
112
|
+
super();
|
|
113
|
+
this.condition = condition;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Evaluates condition.
|
|
117
|
+
* @param {Object.<string, string>} defines Preprocessor variables
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
evalCond(defines) {
|
|
121
|
+
let condition = this.condition;
|
|
122
|
+
condition = condition.replace(/!defined\(([\w\d]+)\)/, (match, p1) => !defines.hasOwnProperty(p1) ? "true" : "false");
|
|
123
|
+
condition = condition.replace(/defined\(([\w\d]+)\)/, (match, p1) => defines.hasOwnProperty(p1) ? "true" : "false");
|
|
124
|
+
return Parser.evaluate(condition, defines);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Evaluates node with given condition result.
|
|
128
|
+
* @param {Object.<string, string>} defines Preprocessor variables
|
|
129
|
+
* @returns {boolean}
|
|
130
|
+
*/
|
|
131
|
+
evalWith(defines, result) {
|
|
132
|
+
if (result) {
|
|
133
|
+
return super.eval(defines);
|
|
134
|
+
} else {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
eval(defines) {
|
|
139
|
+
return this.evalWith(defines, this.evalCond(defines));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class ElseBlockNode extends BlockNode {
|
|
144
|
+
constructor(ifNode) {
|
|
145
|
+
super();
|
|
146
|
+
this.ifNode = ifNode;
|
|
147
|
+
}
|
|
148
|
+
eval(defines) {
|
|
149
|
+
if (this.ifNode.evalCond(defines)) {
|
|
150
|
+
return this.ifNode.evalWith(defines, true);
|
|
151
|
+
} else {
|
|
152
|
+
return super.eval(defines);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = class Preprocessing {
|
|
158
|
+
|
|
159
|
+
constructor (logger) {
|
|
160
|
+
this.logger = logger;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Processes preprocessor directives.
|
|
165
|
+
* @param {string} code
|
|
166
|
+
* @param {Object.<string, string>} preprocessorVariables
|
|
167
|
+
* @returns {string} Processed code
|
|
168
|
+
*/
|
|
169
|
+
processDirectives (code, preprocessorVariables) {
|
|
170
|
+
let lines = code.split(/\r?\n/), stack = [new BlockNode()];
|
|
171
|
+
|
|
172
|
+
let defines = {};
|
|
173
|
+
_.merge(defines, DEFAULT_PREPROCESSOR_VARIABLES);
|
|
174
|
+
_.merge(defines, preprocessorVariables);
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < lines.length; ++i) {
|
|
177
|
+
let line = lines[i];
|
|
178
|
+
let [, directive, parameters] = /^\s*\/\/\s*#(\w+)\s*(.+)?$/.exec(line) || [];
|
|
179
|
+
switch (directive) {
|
|
180
|
+
case undefined: {
|
|
181
|
+
let elem = new TextNode(line);
|
|
182
|
+
elem.line = i;
|
|
183
|
+
_.last(stack).children.push(elem);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "define": {
|
|
187
|
+
let [, left, right] = /^\s*([\w\d]+)\s*(?:=\s*([\w\d]+))?\s*$/.exec(parameters);
|
|
188
|
+
let elem = new DefineNode(left, right);
|
|
189
|
+
elem.line = i;
|
|
190
|
+
_.last(stack).children.push(elem);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case "error": {
|
|
194
|
+
let elem = new ErrorNode(parameters);
|
|
195
|
+
elem.line = i;
|
|
196
|
+
_.last(stack).children.push(elem);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "if":
|
|
200
|
+
case "ifdef":
|
|
201
|
+
case "ifndef": {
|
|
202
|
+
let elem =
|
|
203
|
+
directive == "if" ? new IfBlockNode(parameters) :
|
|
204
|
+
directive == "ifdef" ? new IfBlockNode(`defined(${parameters})`) :
|
|
205
|
+
directive == "ifndef" ? new IfBlockNode(`!defined(${parameters})`) :
|
|
206
|
+
null;
|
|
207
|
+
elem.line = i;
|
|
208
|
+
_.last(stack).children.push(elem);
|
|
209
|
+
stack.push(elem);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case "else": {
|
|
213
|
+
let elem = new ElseBlockNode(stack.pop());
|
|
214
|
+
elem.line = i;
|
|
215
|
+
ArrayUtils.replace(_.last(stack).children, elem.ifNode, elem);
|
|
216
|
+
stack.push(elem);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case "endif": {
|
|
220
|
+
let elem = stack.pop();
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
default: {
|
|
224
|
+
this.logger.warn(`Unknown preprocessor directive #${directive}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (stack.length > 1) {
|
|
230
|
+
this.logger.warn("stack.length != 1 (preprocessor directive closing tag missing?)");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return codeFromNodeArray(stack[0].eval(defines));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Does preprocessing.
|
|
238
|
+
* @param {string} code
|
|
239
|
+
* @param {Object.<string, string>} preprocessorVariables
|
|
240
|
+
* @returns {string}
|
|
241
|
+
*/
|
|
242
|
+
process (code, preprocessorVariables) {
|
|
243
|
+
code = this.processDirectives(code, preprocessorVariables);
|
|
244
|
+
code = removeShebangs(code);
|
|
245
|
+
return code;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
};
|