Parcourir la source

stringLiteralReplacer refactoring + fix of incorrect cache of value with `stringArrayThreshold` chance

sanex3339 il y a 8 ans
Parent
commit
8f73014d28

+ 59 - 49
dist/index.js

@@ -198,8 +198,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 
 var escodegen = __webpack_require__(25);
 var esprima = __webpack_require__(48);
-var estraverse = __webpack_require__(15);
-var NodeType_1 = __webpack_require__(16);
+var estraverse = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 var Node_1 = __webpack_require__(12);
 
 var NodeUtils = function () {
@@ -530,7 +530,7 @@ var _createClass3 = _interopRequireDefault(_createClass2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 
 var Node = function () {
     function Node() {
@@ -895,12 +895,6 @@ exports.AbstractCustomNode = AbstractCustomNode;
 
 /***/ },
 /* 15 */
-/***/ function(module, exports) {
-
-module.exports = require("estraverse");
-
-/***/ },
-/* 16 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -942,6 +936,12 @@ exports.NodeType = Utils_1.Utils.strEnumify({
     WhileStatement: 'WhileStatement'
 });
 
+/***/ },
+/* 16 */
+/***/ function(module, exports) {
+
+module.exports = require("estraverse");
+
 /***/ },
 /* 17 */
 /***/ function(module, exports, __webpack_require__) {
@@ -1382,7 +1382,7 @@ var _createClass3 = _interopRequireDefault(_createClass2);
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 var escodegen = __webpack_require__(25);
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 
 var Nodes = function () {
     function Nodes() {
@@ -2176,9 +2176,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
 var ServiceIdentifiers_1 = __webpack_require__(4);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var NodeTransformers_1 = __webpack_require__(40);
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 var ObfuscationEvents_1 = __webpack_require__(22);
 var VisitorDirection_1 = __webpack_require__(81);
 var Node_1 = __webpack_require__(12);
@@ -4490,9 +4490,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
 var ServiceIdentifiers_1 = __webpack_require__(4);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var CustomNodes_1 = __webpack_require__(20);
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 var AbstractNodeTransformer_1 = __webpack_require__(17);
 var Node_1 = __webpack_require__(12);
 var NodeAppender_1 = __webpack_require__(24);
@@ -4740,9 +4740,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
 var ServiceIdentifiers_1 = __webpack_require__(4);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var NodeObfuscatorsReplacers_1 = __webpack_require__(19);
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 var AbstractNodeTransformer_1 = __webpack_require__(17);
 var Node_1 = __webpack_require__(12);
 var NodeUtils_1 = __webpack_require__(8);
@@ -4836,9 +4836,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
 var ServiceIdentifiers_1 = __webpack_require__(4);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var NodeObfuscatorsReplacers_1 = __webpack_require__(19);
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 var AbstractNodeTransformer_1 = __webpack_require__(17);
 var Node_1 = __webpack_require__(12);
 var NodeUtils_1 = __webpack_require__(8);
@@ -4966,9 +4966,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
 var ServiceIdentifiers_1 = __webpack_require__(4);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var NodeObfuscatorsReplacers_1 = __webpack_require__(19);
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 var AbstractNodeTransformer_1 = __webpack_require__(17);
 var Node_1 = __webpack_require__(12);
 var NodeUtils_1 = __webpack_require__(8);
@@ -5064,9 +5064,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
 var ServiceIdentifiers_1 = __webpack_require__(4);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var NodeObfuscatorsReplacers_1 = __webpack_require__(19);
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 var AbstractNodeTransformer_1 = __webpack_require__(17);
 var Node_1 = __webpack_require__(12);
 var NodeUtils_1 = __webpack_require__(8);
@@ -5238,7 +5238,7 @@ var inversify_1 = __webpack_require__(2);
 var ServiceIdentifiers_1 = __webpack_require__(4);
 var escodegen = __webpack_require__(25);
 var NodeObfuscatorsReplacers_1 = __webpack_require__(19);
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 var AbstractNodeTransformer_1 = __webpack_require__(17);
 var Node_1 = __webpack_require__(12);
 var MemberExpressionObfuscator = function (_AbstractNodeTransfor) {
@@ -5400,7 +5400,7 @@ var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
 var ServiceIdentifiers_1 = __webpack_require__(4);
 var escodegen = __webpack_require__(25);
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 var AbstractNodeTransformer_1 = __webpack_require__(17);
 var Node_1 = __webpack_require__(12);
 var Utils_1 = __webpack_require__(13);
@@ -5498,9 +5498,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
 var ServiceIdentifiers_1 = __webpack_require__(4);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var NodeObfuscatorsReplacers_1 = __webpack_require__(19);
-var NodeType_1 = __webpack_require__(16);
+var NodeType_1 = __webpack_require__(15);
 var AbstractNodeTransformer_1 = __webpack_require__(17);
 var Node_1 = __webpack_require__(12);
 var NodeUtils_1 = __webpack_require__(8);
@@ -5861,20 +5861,18 @@ var StringLiteralReplacer = StringLiteralReplacer_1 = function (_AbstractReplace
     (0, _createClass3.default)(StringLiteralReplacer, [{
         key: "replace",
         value: function replace(nodeValue) {
-            var replaceWithStringArrayFlag = nodeValue.length >= StringLiteralReplacer_1.minimumLengthForStringArray && RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold;
+            var usingStringArray = this.options.stringArray && nodeValue.length >= StringLiteralReplacer_1.minimumLengthForStringArray && RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold;
+            var cacheKey = nodeValue + "-" + String(usingStringArray);
+            if (this.stringLiteralCache.has(cacheKey) && this.options.stringArrayEncoding !== StringArrayEncoding_1.StringArrayEncoding.rc4) {
+                return this.stringLiteralCache.get(cacheKey);
+            }
             var result = void 0;
-            if (this.options.stringArray && replaceWithStringArrayFlag) {
-                if (this.stringLiteralCache.has(nodeValue) && this.options.stringArrayEncoding !== StringArrayEncoding_1.StringArrayEncoding.rc4) {
-                    return this.stringLiteralCache.get(nodeValue);
-                }
+            if (usingStringArray) {
                 result = this.replaceStringLiteralWithStringArrayCall(nodeValue);
             } else {
-                if (this.stringLiteralCache.has(nodeValue)) {
-                    return this.stringLiteralCache.get(nodeValue);
-                }
                 result = "'" + Utils_1.Utils.stringToUnicodeEscapeSequence(nodeValue, !this.options.unicodeEscapeSequence) + "'";
             }
-            this.stringLiteralCache.set(nodeValue, result);
+            this.stringLiteralCache.set(cacheKey, result);
             return result;
         }
     }, {
@@ -5896,24 +5894,36 @@ var StringLiteralReplacer = StringLiteralReplacer_1 = function (_AbstractReplace
             return hexadecimalIndex;
         }
     }, {
-        key: "replaceStringLiteralWithStringArrayCall",
-        value: function replaceStringLiteralWithStringArrayCall(value) {
-            var rc4Key = '';
+        key: "getEncodedValue",
+        value: function getEncodedValue(value) {
+            var encodedValue = void 0,
+                key = void 0;
             switch (this.options.stringArrayEncoding) {
                 case StringArrayEncoding_1.StringArrayEncoding.rc4:
-                    rc4Key = RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomGenerator().pickone(StringLiteralReplacer_1.rc4Keys);
-                    value = CryptUtils_1.CryptUtils.btoa(CryptUtils_1.CryptUtils.rc4(value, rc4Key));
+                    key = RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomGenerator().pickone(StringLiteralReplacer_1.rc4Keys);
+                    encodedValue = CryptUtils_1.CryptUtils.btoa(CryptUtils_1.CryptUtils.rc4(value, key));
                     break;
                 case StringArrayEncoding_1.StringArrayEncoding.base64:
-                    value = CryptUtils_1.CryptUtils.btoa(value);
+                    encodedValue = CryptUtils_1.CryptUtils.btoa(value);
                     break;
+                default:
+                    encodedValue = value;
             }
-            value = Utils_1.Utils.stringToUnicodeEscapeSequence(value, !this.options.unicodeEscapeSequence);
-            var hexadecimalIndex = this.getArrayHexadecimalIndex(value);
+            encodedValue = Utils_1.Utils.stringToUnicodeEscapeSequence(encodedValue, !this.options.unicodeEscapeSequence);
+            return { encodedValue: encodedValue, key: key };
+        }
+    }, {
+        key: "replaceStringLiteralWithStringArrayCall",
+        value: function replaceStringLiteralWithStringArrayCall(value) {
+            var _getEncodedValue = this.getEncodedValue(value),
+                encodedValue = _getEncodedValue.encodedValue,
+                key = _getEncodedValue.key;
+
+            var hexadecimalIndex = this.getArrayHexadecimalIndex(encodedValue);
             var rotatedStringArrayStorageId = Utils_1.Utils.stringRotate(this.stringArrayStorage.getStorageId(), 1);
             var stringArrayStorageCallsWrapperName = "_" + Utils_1.Utils.hexadecimalPrefix + rotatedStringArrayStorageId;
-            if (this.options.stringArrayEncoding === StringArrayEncoding_1.StringArrayEncoding.rc4) {
-                return stringArrayStorageCallsWrapperName + "('" + hexadecimalIndex + "', '" + Utils_1.Utils.stringToUnicodeEscapeSequence(rc4Key, !this.options.unicodeEscapeSequence) + "')";
+            if (key) {
+                return stringArrayStorageCallsWrapperName + "('" + hexadecimalIndex + "', '" + Utils_1.Utils.stringToUnicodeEscapeSequence(key, !this.options.unicodeEscapeSequence) + "')";
             }
             return stringArrayStorageCallsWrapperName + "('" + hexadecimalIndex + "')";
         }
@@ -6279,7 +6289,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
 var ServiceIdentifiers_1 = __webpack_require__(4);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var CalleeDataExtractors_1 = __webpack_require__(37);
 var Node_1 = __webpack_require__(12);
 var NodeUtils_1 = __webpack_require__(8);
@@ -6397,7 +6407,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var AbstractCalleeDataExtractor_1 = __webpack_require__(32);
 var Node_1 = __webpack_require__(12);
 var NodeUtils_1 = __webpack_require__(8);
@@ -6475,7 +6485,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var AbstractCalleeDataExtractor_1 = __webpack_require__(32);
 var Node_1 = __webpack_require__(12);
 var NodeUtils_1 = __webpack_require__(8);
@@ -6560,7 +6570,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 
 var tslib_1 = __webpack_require__(3);
 var inversify_1 = __webpack_require__(2);
-var estraverse = __webpack_require__(15);
+var estraverse = __webpack_require__(16);
 var Node_1 = __webpack_require__(12);
 var NodeUtils_1 = __webpack_require__(8);
 var AbstractCalleeDataExtractor_1 = __webpack_require__(32);

+ 2 - 2
package.json

@@ -29,10 +29,10 @@
     "esprima": "3.1.3",
     "estraverse": "4.2.0",
     "inversify": "^3.0.0-rc.2",
-    "lodash": "^4.17.2",
+    "lodash": "^4.17.3",
     "mkdirp": "0.5.1",
     "reflect-metadata": "^0.1.9",
-    "source-map-support": "0.4.7",
+    "source-map-support": "0.4.8",
     "string-template": "^1.0.0",
     "tslib": "^1.4.0"
   },

+ 4 - 0
src/interfaces/node-transformers/IEncodedValue.d.ts

@@ -0,0 +1,4 @@
+export interface IEncodedValue {
+    encodedValue: string;
+    key?: string;
+}

+ 35 - 27
src/node-transformers/node-obfuscators/replacers/StringLiteralReplacer.ts

@@ -2,6 +2,7 @@ import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
 import { ICustomNodeGroup } from '../../../interfaces/custom-nodes/ICustomNodeGroup';
+import { IEncodedValue } from '../../../interfaces/node-transformers/IEncodedValue';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IStorage } from '../../../interfaces/storages/IStorage';
 
@@ -66,32 +67,26 @@ export class StringLiteralReplacer extends AbstractReplacer {
      * @returns {string}
      */
     public replace (nodeValue: string): string {
-        const replaceWithStringArrayFlag: boolean = (
-            nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray
-            && RandomGeneratorUtils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold
+        const usingStringArray: boolean = (
+            this.options.stringArray &&
+            nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray &&
+            RandomGeneratorUtils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold
         );
+        const cacheKey: string = `${nodeValue}-${String(usingStringArray)}`;
 
-        let result: string;
+        if (this.stringLiteralCache.has(cacheKey) && this.options.stringArrayEncoding !== StringArrayEncoding.rc4) {
+            return <string>this.stringLiteralCache.get(cacheKey);
+        }
 
-        if (this.options.stringArray && replaceWithStringArrayFlag) {
-            /**
-             * we can't use values from cache with `stringArrayEncoding: rc4`
-             * because rc4 key will be the same for same values
-             */
-            if (this.stringLiteralCache.has(nodeValue) && this.options.stringArrayEncoding !== StringArrayEncoding.rc4) {
-                return <string>this.stringLiteralCache.get(nodeValue);
-            }
+        let result: string;
 
+        if (usingStringArray) {
             result = this.replaceStringLiteralWithStringArrayCall(nodeValue);
         } else {
-            if (this.stringLiteralCache.has(nodeValue)) {
-                return <string>this.stringLiteralCache.get(nodeValue);
-            }
-
             result = `'${Utils.stringToUnicodeEscapeSequence(nodeValue, !this.options.unicodeEscapeSequence)}'`;
         }
 
-        this.stringLiteralCache.set(nodeValue, result);
+        this.stringLiteralCache.set(cacheKey, result);
 
         return result;
     }
@@ -125,32 +120,45 @@ export class StringLiteralReplacer extends AbstractReplacer {
 
     /**
      * @param value
-     * @returns {string}
+     * @returns {IEncodedValue}
      */
-    private replaceStringLiteralWithStringArrayCall (value: string): string {
-        let rc4Key: string = '';
+    private getEncodedValue (value: string): IEncodedValue {
+        let encodedValue: string,
+            key: string | undefined;
 
         switch (this.options.stringArrayEncoding) {
             case StringArrayEncoding.rc4:
-                rc4Key = RandomGeneratorUtils.getRandomGenerator().pickone(StringLiteralReplacer.rc4Keys);
-                value = CryptUtils.btoa(CryptUtils.rc4(value, rc4Key));
+                key = RandomGeneratorUtils.getRandomGenerator().pickone(StringLiteralReplacer.rc4Keys);
+                encodedValue = CryptUtils.btoa(CryptUtils.rc4(value, key));
 
                 break;
 
             case StringArrayEncoding.base64:
-                value = CryptUtils.btoa(value);
+                encodedValue = CryptUtils.btoa(value);
 
                 break;
+
+            default:
+                encodedValue = value;
         }
 
-        value = Utils.stringToUnicodeEscapeSequence(value, !this.options.unicodeEscapeSequence);
+        encodedValue = Utils.stringToUnicodeEscapeSequence(encodedValue, !this.options.unicodeEscapeSequence);
 
-        const hexadecimalIndex: string = this.getArrayHexadecimalIndex(value);
+        return { encodedValue, key };
+    }
+
+    /**
+     * @param value
+     * @returns {string}
+     */
+    private replaceStringLiteralWithStringArrayCall (value: string): string {
+        const { encodedValue, key }: IEncodedValue = this.getEncodedValue(value);
+        const hexadecimalIndex: string = this.getArrayHexadecimalIndex(encodedValue);
         const rotatedStringArrayStorageId: string = Utils.stringRotate(this.stringArrayStorage.getStorageId(), 1);
         const stringArrayStorageCallsWrapperName: string = `_${Utils.hexadecimalPrefix}${rotatedStringArrayStorageId}`;
 
-        if (this.options.stringArrayEncoding === StringArrayEncoding.rc4) {
-            return `${stringArrayStorageCallsWrapperName}('${hexadecimalIndex}', '${Utils.stringToUnicodeEscapeSequence(rc4Key, !this.options.unicodeEscapeSequence)}')`;
+        if (key) {
+            return `${stringArrayStorageCallsWrapperName}('${hexadecimalIndex}', '${Utils.stringToUnicodeEscapeSequence(key, !this.options.unicodeEscapeSequence)}')`;
         }
 
         return `${stringArrayStorageCallsWrapperName}('${hexadecimalIndex}')`;

+ 28 - 6
test/functional-tests/node-transformers/node-obfuscators/LiteralObfuscator.spec.ts

@@ -21,7 +21,7 @@ describe('LiteralObfuscator', () => {
             assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *'\\x74\\x65\\x73\\x74';$/);
         });
 
-        it('should replace literal node value with unicode escape sequence from unicode array', () => {
+        it('should replace literal node value with unicode escape sequence from string array', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 `var test = 'test';`,
                 {
@@ -38,7 +38,7 @@ describe('LiteralObfuscator', () => {
             assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-z0-9]){4}\('0x0'\);/);
         });
 
-        it('should replace literal node value with raw value from unicode array if `unicodeEscapeSequence` is disabled', () => {
+        it('should replace literal node value with raw value from string array if `unicodeEscapeSequence` is disabled', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 `var test = 'test';`,
                 {
@@ -56,7 +56,7 @@ describe('LiteralObfuscator', () => {
             assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-z0-9]){4}\('0x0'\);/);
         });
 
-        it('should replace literal node value with raw value from unicode array if `unicodeEscapeSequence` and `stringArray` are disabled', () => {
+        it('should replace literal node value with raw value from string array if `unicodeEscapeSequence` and `stringArray` are disabled', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 `var test = 'test';`,
                 {
@@ -83,7 +83,7 @@ describe('LiteralObfuscator', () => {
             ));
         });
 
-        it('shouldn\'t replace short literal node value with unicode array value', () => {
+        it('shouldn\'t replace short literal node value with value from string array', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 `var test = 'te';`,
                 {
@@ -96,7 +96,7 @@ describe('LiteralObfuscator', () => {
             assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *'\\x74\\x65';/);
         });
 
-        it('should replace literal node value with unicode array value encoded using base64', () => {
+        it('should replace literal node value with value from string array encoded using base64', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 `var test = 'test';`,
                 {
@@ -114,7 +114,7 @@ describe('LiteralObfuscator', () => {
             assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-z0-9]){4}\('0x0'\);/);
         });
 
-        it('should replace literal node value with unicode array value encoded using rc4', () => {
+        it('should replace literal node value with value from string array encoded using rc4', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 `var test = 'test';`,
                 {
@@ -130,6 +130,28 @@ describe('LiteralObfuscator', () => {
                 /var *test *= *_0x([a-z0-9]){4}\('0x0', '(\\x[a-f0-9]*){4}'\);/
             );
         });
+
+        it('should replace literal node value with value from string array with `stringArrayThreshold` chance', () => {
+            const samples: number = 1000;
+            const stringArrayThreshold: number = 0.5;
+            const delta: number = 0.1;
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                `var test = 'test';\n`.repeat(samples),
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    stringArray: true,
+                    stringArrayThreshold: stringArrayThreshold
+                }
+            );
+
+            const regExp1: RegExp = /var *test *= *_0x([a-z0-9]){4}\('0x0'\);/g;
+            const regExp2: RegExp = /var *test *= *'\\x74\\x65\\x73\\x74';/g;
+            const stringArrayMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp1)!.length;
+            const noStringArrayMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp2)!.length;
+
+            assert.closeTo(stringArrayMatchesLength / samples, stringArrayThreshold, delta);
+            assert.closeTo(noStringArrayMatchesLength / samples, stringArrayThreshold, delta);
+        });
     });
 
     it('should obfuscate literal node with boolean value', () => {