8000 Merge pull request #521 from javascript-obfuscator/split-strings-maxi… · sec-js/javascript-obfuscator@4d8b671 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4d8b671

Browse files
authored
Merge pull request javascript-obfuscator#521 from javascript-obfuscator/split-strings-maximum-call-stack-fix
Split strings maximum call stack fix
2 parents 03ab22a + 8585a78 commit 4d8b671

File tree

6 files changed

+107
-26
lines changed

6 files changed

+107
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ v0.24.0
66
* Dynamic import and `import.meta` support. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/505
77
* Now usage of some browser-related options with `target: 'node'` will cause a validation error
88
* **CLI:** a file path will be displayed on obfuscation error. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/513
9+
* Fixed `Maximum call stack size exceeded` error on large strings when `splitString` option is enabled
910
* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/516
1011
* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/512
1112
* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/496

dist/index.browser.js

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.cli.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/node-transformers/converting-transformers/SplitStringTransformer.ts

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { inject, injectable, } from 'inversify';
22
import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
33

4+
import * as estraverse from 'estraverse';
45
import * as ESTree from 'estree';
56

67
import { IOptions } from '../../interfaces/options/IOptions';
@@ -13,12 +14,19 @@ import { TransformationStage } from '../../enums/node-transformers/Transformatio
1314
import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
1415
import { NodeFactory } from '../../node/NodeFactory';
1516
import { NodeGuards } from '../../node/NodeGuards';
17+
import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
18+
import { NodeUtils } from '../../node/NodeUtils';
1619

1720
/**
1821
* Splits strings into parts
1922
*/
2023
@injectable()
2124
export class SplitStringTransformer extends AbstractNodeTransformer {
25+
/**
26+
* @type {number}
27+
*/
28+
private static readonly firstPassChunkLength: number = 1000;
29+
2230
/**
2331
* @type {NodeTransformer[]}
2432
*/
@@ -85,52 +93,104 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
8593
}
8694

8795
/**
96+
* Needs to split string on chunks of length `splitStringsChunkLength` in two pass, because of
97+
* `Maximum call stack size exceeded` error in `esrecurse` package
98+
*
8899
* @param {Literal} literalNode
89100
* @param {Node} parentNode
90101
* @returns {Node}
91102
*/
92103
public transformNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): ESTree.Node {
93-
if (typeof literalNode.value !== 'string') {
104+
if (NodeLiteralUtils.isProhibitedLiteralNode(literalNode, parentNode)) {
94105
return literalNode;
95106
}
96107

97-
if (NodeGuards.isPropertyNode(parentNode) && !parentNode.computed && parentNode.key === literalNode) {
108+
// pass #1: split string on a large chunks with length of `firstPassChunkLength`
109+
const firstPassChunksNode: ESTree.Node = this.transformLiteralNodeByChunkLength(
110+
literalNode,
111+
parentNode,
112+
SplitStringTransformer.firstPassChunkLength
113+
);
114+
115+
// pass #2: split large chunks on a chunks with length of `splitStringsChunkLength`
116+
const secondPassChunksNode: ESTree.Node = estraverse.replace(firstPassChunksNode, {
117+
/* tslint:disable:no-shadowed-variable */
118+
enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
119+
if (parentNode && NodeGuards.isLiteralNode(node)) {
120+
return this.transformLiteralNodeByChunkLength(
121+
node,
122+
parentNode,
123+
this.options.splitStringsChunkLength
124+
);
125+
}
126+
}
127+
});
128+
129+
return secondPassChunksNode;
130+
}
131+
132+
/**
133+
* @param {Literal} literalNode
134+
* @param {Node} parentNode
135+
* @param {number} chunkLength
136+
* @returns {Node}
137+
*/
138+
private transformLiteralNodeByChunkLength (
139+
literalNode: ESTree.Literal,
140+
parentNode: ESTree.Node,
141+
chunkLength: number
142+
): ESTree.Node {
143+
if (typeof literalNode.value !== 'string') {
98144
return literalNode;
99145
}
100146

101-
if (this.options.splitStringsChunkLength >= literalNode.value.length) {
147+
if (chunkLength >= literalNode.value.length) {
102148
return literalNode;
103149
}
104150

105151
const stringChunks: string[] = SplitStringTransformer.chunkString(
106152
literalNode.value,
107-
this.options.splitStringsChunkLength
153+
chunkLength
108154
);
109155

110-
return this.transformStringChunksToBinaryExpressionNode(stringChunks);
156+
const binaryExpressionNode: ESTree.BinaryExpression =
157+
this.transformStringChunksToBinaryExpressionNode(stringChunks);
158+
159+
NodeUtils.parentizeAst(binaryExpressionNode);
160+
NodeUtils.parentizeNode(binaryExpressionNode, parentNode);
161+
162+
return binaryExpressionNode;
111163
}
112164

113165
/**
114166
* @param {string[]} chunks
115167
* @returns {BinaryExpression}
116168
*/
117-
private transformStringChunksToBinaryExpressionNode (chunks: string[]): ESTree.BinaryExpression | ESTree.Literal {
118-
const lastChunk: string | undefined = chunks.pop();
169+
private transformStringChunksToBinaryExpressionNode (chunks: string[]): ESTree.BinaryExpression {
170+
const firstChunk: string | undefined = chunks.shift();
171+
const secondChunk: string | undefined = chunks.shift();
119172

120-
if (lastChunk === undefined) {
121-
throw new Error('Last chunk value should not be empty');
173+
if (!firstChunk || !secondChunk) {
174+
throw new Error('First and second chunks values should not be empty');
122175
}
123176

124-
const lastChunkLiteralNode: ESTree.Literal = NodeFactory.literalNode(lastChunk);
125-
126-
if (chunks.length === 0) {
127-
return lastChunkLiteralNode;
128-
}
129-
130-
return NodeFactory.binaryExpressionNode(
177+
const initialBinaryExpressionNode: ESTree.BinaryExpression = NodeFactory.binaryExpressionNode(
131178
'+',
132-
this.transformStringChunksToBinaryExpressionNode(chunks),
133-
lastChunkLiteralNode
179+
NodeFactory.literalNode(firstChunk),
180+
NodeFactory.literalNode(secondChunk)
181+
);
182+
183+
return chunks.reduce<ESTree.BinaryExpression>(
184+
(binaryExpressionNode: ESTree.BinaryExpression, chunk: string) => {
185+
const chunkLiteralNode: ESTree.Literal = NodeFactory.literalNode(chunk);
186+
187+
return NodeFactory.binaryExpressionNode(
188+
'+',
189+
binaryExpressionNode,
190+
chunkLiteralNode
191+
);
192+
},
193+
initialBinaryExpressionNode
134194
);
135195
}
136196
}

test/functional-tests/node-transformers/converting-transformers/split-string-transformer/SplitStringTransformer.spec.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ describe('SplitStringTransformer', () => {
189189
});
190190
});
191191

192-
describe('Variant #10: Integration with `reservedStrings` option', () => {
192+
describe('Variant #11: Integration with `reservedStrings` option', () => {
193193
it('should correctly ignore strings from `reservedStrings` option', () => {
194194
const code: string = readFileAsString(__dirname + '/fixtures/ignore-reserved-strings.js');
195195

@@ -209,4 +209,24 @@ describe('SplitStringTransformer', () => {
209209
);
210210
});
211211
});
212+
213+
describe('Variant #12: Large string', () => {
214+
it('Should does not throw `Maximum call stack size exceeded` error on a large string', () => {
215+
const code: string = `var foo = '${'a'.repeat(10000)}';`;
216+
217+
const testFunc = () => JavaScriptObfuscator.obfuscate(
218+
code,
219+
{
220+
...NO_ADDITIONAL_NODES_PRESET,
221+
splitStrings: true,
222+
splitStringsChunkLength: 2
223+
}
224+
);
225+
226+
assert.doesNotThrow(
227+
testFunc,
228+
Error
229+
);
230+
});
231+
});
212232
});

0 commit comments

Comments
 (0)
0