Browse Source

Added additional self defending mechanisms. Updated tests

sanex 3 years ago
parent
commit
c4abec8c2a
31 changed files with 794 additions and 329 deletions
  1. 1 0
      package.json
  2. 6 3
      src/custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper.ts
  3. 13 7
      src/custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper.ts
  4. 11 3
      src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/AtobTemplate.ts
  5. 2 1
      test/dev/dev.ts
  6. 3 2
      test/functional-tests/code-transformers/preparing-transformers/hashbang-operator-transformer/HashbangOperatorTransformer.spec.ts
  7. 4 3
      test/functional-tests/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.spec.ts
  8. 2 1
      test/functional-tests/custom-code-helpers/string-array/StringArrayCodeHelper.spec.ts
  9. 2 2
      test/functional-tests/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.spec.ts
  10. 36 18
      test/functional-tests/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.spec.ts
  11. 468 198
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec.ts
  12. 4 3
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-template/StringArrayTemplate.spec.ts
  13. 74 23
      test/functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/DictionaryIdentifierNamesGenerator.spec.ts
  14. 0 1
      test/functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/fixtures/string-array-storage-name-conflict-2.js
  15. 25 6
      test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/MangledIdentifierNamesGenerator.spec.ts
  16. 20 4
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  17. 5 4
      test/functional-tests/node-transformers/converting-transformers/class-field-transformer/ClassFieldTransformer.spec.ts
  18. 3 2
      test/functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec.ts
  19. 11 4
      test/functional-tests/node-transformers/finalizing-transformers/directive-placement-transformer/DirectivePlacementTransformer.spec.ts
  20. 2 1
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/EscapeSequenceTransformer.spec.ts
  21. 5 1
      test/functional-tests/node-transformers/initializing-transformers/comments-transformer/CommentsTransformer.spec.ts
  22. 4 3
      test/functional-tests/node-transformers/preparing-transformers/eval-call-expression-transformer/EvalCallExpressionTransformer.spec.ts
  23. 2 1
      test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/black-list-obfuscating-guard/BlackListObfuscatingGuard.spec.ts
  24. 8 8
      test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/conditional-comment-obfuscating-guard/ConditionalCommentObfuscatingGuard.spec.ts
  25. 9 2
      test/functional-tests/node-transformers/preparing-transformers/variable-preserve-transformer/VariablePreserveTransformer.spec.ts
  26. 0 11
      test/functional-tests/node-transformers/string-array-transformers/string-array-scope-calls-wrapper-transformer/StringArrayScopeCallsWrapperTransformer.spec.ts
  27. 13 7
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts
  28. 11 10
      test/functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec.ts
  29. 28 0
      test/helpers/get-string-array-regexp.ts
  30. 13 0
      test/helpers/minimizeCode.ts
  31. 9 0
      yarn.lock

+ 1 - 0
package.json

@@ -84,6 +84,7 @@
     "rimraf": "3.0.2",
     "sinon": "11.1.1",
     "source-map-resolve": "0.6.0",
+    "terser": "^5.7.1",
     "threads": "1.6.5",
     "ts-loader": "9.2.3",
     "ts-node": "10.1.0",

+ 6 - 3
src/custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper.ts

@@ -13,9 +13,12 @@ export class StringArrayCallsWrapperBase64CodeHelper extends StringArrayCallsWra
     protected override getDecodeStringArrayTemplate (): string {
         const atobFunctionName: string = this.randomGenerator.getRandomString(6);
 
-        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(AtobTemplate(), {
-            atobFunctionName: atobFunctionName
-        });
+        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(
+            AtobTemplate(this.options.selfDefending),
+            {
+                atobFunctionName: atobFunctionName
+            }
+        );
 
         const selfDefendingCode: string = this.getSelfDefendingTemplate();
 

+ 13 - 7
src/custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper.ts

@@ -15,13 +15,19 @@ export class StringArrayCallsWrapperRc4CodeHelper extends StringArrayCallsWrappe
         const atobFunctionName: string = this.randomGenerator.getRandomString(6);
         const rc4FunctionName: string = this.randomGenerator.getRandomString(6);
 
-        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(AtobTemplate(), {
-            atobFunctionName
-        });
-        const rc4Polyfill: string = this.customCodeHelperFormatter.formatTemplate(Rc4Template(), {
-            atobFunctionName,
-            rc4FunctionName
-        });
+        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(
+            AtobTemplate(this.options.selfDefending),
+            {
+                atobFunctionName
+            }
+        );
+        const rc4Polyfill: string = this.customCodeHelperFormatter.formatTemplate(
+            Rc4Template(),
+            {
+                atobFunctionName,
+                rc4FunctionName
+            }
+        );
 
         const selfDefendingCode: string = this.getSelfDefendingTemplate();
 

+ 11 - 3
src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/AtobTemplate.ts

@@ -5,19 +5,27 @@ import { base64alphabetSwapped } from '../../../../constants/Base64AlphabetSwapp
  *
  * @returns {string}
  */
-export function AtobTemplate (): string {
+export function AtobTemplate (selfDefending: boolean): string {
     return `
         var {atobFunctionName} = function (input) {
             const chars = '${base64alphabetSwapped}';
 
             let output = '';
             let tempEncodedString = '';
+            ${selfDefending ? 'let func = output + {atobFunctionName};' : ''}
             
             for (
                 let bc = 0, bs, buffer, idx = 0;
                 buffer = input.charAt(idx++);
-                ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
-                    bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
+                ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) 
+                    ? output += ${((): string => {
+                        const basePart: string = 'String.fromCharCode(255 & bs >> (-2 * bc & 6))';
+                        
+                        return selfDefending
+                            ? `(func.charCodeAt(idx + 10) - 10 ? ${basePart} : bc)`
+                            : basePart;
+                    })()}
+                    : 0
             ) {
                 buffer = chars.indexOf(buffer);
             }

+ 2 - 1
test/dev/dev.ts

@@ -37,7 +37,8 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
             stringArrayEncoding: ['rc4', 'base64'],
             stringArrayWrappersCount: 5,
             rotateStringArray: true,
-            identifierNamesGenerator: 'mangled'
+            identifierNamesGenerator: 'mangled',
+            selfDefending: true
         }
     ).getObfuscatedCode();
 

+ 3 - 2
test/functional-tests/code-transformers/preparing-transformers/hashbang-operator-transformer/HashbangOperatorTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -59,8 +60,8 @@ describe('HashbangOperatorTransformer', () => {
 
     describe('Variant #3: `stringArray` option enabled', () => {
         const regExp: RegExp = new RegExp(
-            `^#!\/usr\/bin\/env node${lineSeparator}` +
-            `var _0x(\\w){4} *= *\\['abc'];`
+            `^#!\/usr\/bin\/env node${lineSeparator}.*` +
+            `${getStringArrayRegExp(['abc']).source}`
         );
 
         let obfuscatedCode: string;

+ 4 - 3
test/functional-tests/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.spec.ts

@@ -56,9 +56,10 @@ describe('StringArrayCallsWrapperCodeHelper', () => {
     describe('Preserve string array name', () => {
         const callsWrapperRegExp: RegExp = new RegExp(`` +
             `function *b *\\(c, *d\\) *{ *` +
-                `b *= *function *\\(e, *f\\) *{` +
-                    `e *= *e *- *0x0; *` +
-                    `var g *= *a\\[e]; *` +
+                `var e *= *a\\(\\); *` +
+                `b *= *function *\\(f, *g\\) *{` +
+                    `f *= *f *- *0x0; *` +
+                    `var h *= *e\\[f]; *` +
         ``);
 
         let obfuscatedCode: string;

+ 2 - 1
test/functional-tests/custom-code-helpers/string-array/StringArrayCodeHelper.spec.ts

@@ -2,12 +2,13 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade';
 
 describe('StringArrayCodeHelper', () => {
-    const regExp: RegExp = /^var _0x([a-f0-9]){4} *= *\[/;
+    const regExp: RegExp = getStringArrayRegExp(['test']);
 
     describe('`stringArray` option is set', () => {
         let obfuscatedCode: string;

+ 2 - 2
test/functional-tests/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.spec.ts

@@ -84,8 +84,8 @@ describe('StringArrayRotateFunctionCodeHelper', () => {
     });
 
     describe('Preserve string array name', () => {
-        const arrayRotateRegExp: RegExp = /c\['push']\(c\['shift']\(\)\);/;
-        const comparisonRegExp: RegExp = /if *\(e *=== *d\) *{/;
+        const arrayRotateRegExp: RegExp = /e\['push']\(e\['shift']\(\)\);/;
+        const comparisonRegExp: RegExp = /if *\(f *=== *d\) *{/;
 
         let obfuscatedCode: string;
 

+ 36 - 18
test/functional-tests/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.spec.ts

@@ -5,41 +5,59 @@ import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/id
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 
 describe('StringArrayCodeHelperGroup', () => {
     const regExp: RegExp = new RegExp(
-        'function *b *\\(\\w, *\\w\\) *{.*return \\w;}.*' +
-        'function *c *\\(\\w, *\\w\\) *{.*return \\w;}.*' +
-        'function *d *\\(\\w, *\\w\\) *{.*return \\w;}'
+        'function *\\w *\\(\\w, *\\w\\) *{.*return \\w;}.*' +
+        'function *\\w *\\(\\w, *\\w\\) *{.*return \\w;}.*' +
+        'function *\\w *\\(\\w, *\\w\\) *{.*return \\w;}'
+    );
+    const stringArrayCallsWrapperRegExp: RegExp = new RegExp(
+        `function *(\\w) *\\(\\w, *\\w\\) *{.*return \\w;}.*`
     );
 
     describe('StringArrayCallsWrapper code helper names', () => {
+        const stringArrayCallsWrapperNames: Set<string> = new Set();
+        const samplesCount: number = 30;
         let obfuscatedCode: string;
 
+        const expectedUniqStringArrayCallsWrapperNamesCount: number = 3;
+
         before(() => {
             const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
 
-            obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                code,
-                {
-                    ...NO_ADDITIONAL_NODES_PRESET,
-                    identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
-                    stringArray: true,
-                    stringArrayThreshold: 1,
-                    stringArrayEncoding: [
-                        StringArrayEncoding.None,
-                        StringArrayEncoding.Base64,
-                        StringArrayEncoding.Rc4
-                    ]
-                }
-            ).getObfuscatedCode();
+            for (let i = 0; i < samplesCount; i++) {
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        stringArrayEncoding: [
+                            StringArrayEncoding.None,
+                            StringArrayEncoding.Base64,
+                            StringArrayEncoding.Rc4
+                        ]
+                    }
+                ).getObfuscatedCode();
+
+                const callsWrapperName: string = getRegExpMatch(obfuscatedCode, stringArrayCallsWrapperRegExp);
+
+                stringArrayCallsWrapperNames.add(callsWrapperName);
+            }
         });
 
-        it('should place multiple StringArrayCallsWrapper code helper names in the correct order', () => {
+        it('should correct place all StringArrayCallsWrapper code helpers', () => {
             assert.match(obfuscatedCode, regExp);
         });
+
+        it('should place multiple StringArrayCallsWrapper code helper names in the random order', () => {
+            assert.equal(stringArrayCallsWrapperNames.size, expectedUniqStringArrayCallsWrapperNamesCount);
+        });
     });
 });

+ 468 - 198
test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec.ts

@@ -16,16 +16,19 @@ import { Rc4Template } from '../../../../../../src/custom-code-helpers/string-ar
 import { StringArrayBase64DecodeTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayBase64DecodeTemplate';
 import { StringArrayCallsWrapperTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate';
 import { StringArrayRC4DecodeTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayRC4DecodeTemplate';
+import { StringArrayTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array/StringArrayTemplate';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
 import { InversifyContainerFacade } from '../../../../../../src/container/InversifyContainerFacade';
 import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscatorFacade';
+import { minimizeCode } from '../../../../../helpers/minimizeCode';
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 import { swapLettersCase } from '../../../../../helpers/swapLettersCase';
 
 describe('StringArrayCallsWrapperTemplate', () => {
     const stringArrayName: string = 'stringArrayName';
+    const stringArrayFunctionName: string = 'stringArrayFunctionName';
     const stringArrayCallsWrapperName: string = 'stringArrayCallsWrapperName';
     const stringArrayCacheName: string = 'stringArrayCache';
     const atobFunctionName: string = 'atob';
@@ -45,243 +48,510 @@ describe('StringArrayCallsWrapperTemplate', () => {
     });
 
     describe('Variant #1: `base64` encoding', () => {
-        describe('Variant #1: index shift amount is `0`', () => {
-            const index: string = '0x0';
-
-            const indexShiftAmount: number = 0;
-
-            const expectedDecodedValue: string = 'test1';
-
-            let decodedValue: string;
-
-            before(() => {
-                const atobPolyfill = format(AtobTemplate(), {
-                    atobFunctionName
-                });
-                const atobDecodeTemplate: string = format(
-                    StringArrayBase64DecodeTemplate(randomGenerator),
-                    {
-                        atobPolyfill,
-                        atobFunctionName,
-                        selfDefendingCode: '',
+        describe('Variant #1: `selfDefending` option is disabled', () => {
+            const selfDefendingEnabled: boolean = false;
+
+            describe('Variant #1: index shift amount is `0`', () => {
+                const index: string = '0x0';
+
+                const indexShiftAmount: number = 0;
+
+                const expectedDecodedValue: string = 'test1';
+
+                let decodedValue: string;
+
+                before(() => {
+                    const stringArrayTemplate = format(StringArrayTemplate(), {
+                        stringArrayName,
+                        stringArrayFunctionName,
+                        stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa('test1')}'`
+                    });
+                    const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                        atobFunctionName
+                    });
+                    const atobDecodeTemplate: string = format(
+                        StringArrayBase64DecodeTemplate(randomGenerator),
+                        {
+                            atobPolyfill,
+                            atobFunctionName,
+                            selfDefendingCode: '',
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName
+                        }
+                    );
+                    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                        decodeCodeHelperTemplate: atobDecodeTemplate,
+                        indexShiftAmount,
                         stringArrayCacheName,
-                        stringArrayCallsWrapperName
-                    }
-                );
-                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                    decodeCodeHelperTemplate: atobDecodeTemplate,
-                    indexShiftAmount,
-                    stringArrayCacheName,
-                    stringArrayCallsWrapperName,
-                    stringArrayName
+                        stringArrayCallsWrapperName,
+                        stringArrayFunctionName
+                    });
+
+                    decodedValue = Function(`
+                        ${stringArrayTemplate}
+                    
+                        ${stringArrayCallsWrapperTemplate}
+                        
+                        return ${stringArrayCallsWrapperName}(${index});
+                    `)();
                 });
 
-                decodedValue = Function(`
-                var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa('test1')}'];
-            
-                ${stringArrayCallsWrapperTemplate}
-                
-                return ${stringArrayCallsWrapperName}(${index});
-            `)();
-            });
-
-            it('should correctly return decoded value', () => {
-                assert.deepEqual(decodedValue, expectedDecodedValue);
+                it('should correctly return decoded value', () => {
+                    assert.deepEqual(decodedValue, expectedDecodedValue);
+                });
             });
-        });
-
-        describe('Variant #2: index shift amount is `5`', () => {
-            const index: string = '0x5';
-
-            const indexShiftAmount: number = 5;
-
-            const expectedDecodedValue: string = 'test1';
 
-            let decodedValue: string;
-
-            before(() => {
-                const atobPolyfill = format(AtobTemplate(), {
-                    atobFunctionName
-                });
-                const atobDecodeTemplate: string = format(
-                    StringArrayBase64DecodeTemplate(randomGenerator),
-                    {
-                        atobPolyfill,
-                        atobFunctionName,
-                        selfDefendingCode: '',
+            describe('Variant #2: index shift amount is `5`', () => {
+                const index: string = '0x5';
+
+                const indexShiftAmount: number = 5;
+
+                const expectedDecodedValue: string = 'test1';
+
+                let decodedValue: string;
+
+                before(() => {
+                    const stringArrayTemplate = format(StringArrayTemplate(), {
+                        stringArrayName,
+                        stringArrayFunctionName,
+                        stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa('test1')}'`
+                    });
+                    const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                        atobFunctionName
+                    });
+                    const atobDecodeTemplate: string = format(
+                        StringArrayBase64DecodeTemplate(randomGenerator),
+                        {
+                            atobPolyfill,
+                            atobFunctionName,
+                            selfDefendingCode: '',
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName
+                        }
+                    );
+                    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                        decodeCodeHelperTemplate: atobDecodeTemplate,
+                        indexShiftAmount,
                         stringArrayCacheName,
-                        stringArrayCallsWrapperName
-                    }
-                );
-                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                    decodeCodeHelperTemplate: atobDecodeTemplate,
-                    indexShiftAmount,
-                    stringArrayCacheName,
-                    stringArrayCallsWrapperName,
-                    stringArrayName
+                        stringArrayCallsWrapperName,
+                        stringArrayFunctionName
+                    });
+
+                    decodedValue = Function(`
+                        ${stringArrayTemplate}
+                    
+                        ${stringArrayCallsWrapperTemplate}
+                        
+                        return ${stringArrayCallsWrapperName}(${index});
+                    `)();
                 });
 
-                decodedValue = Function(`
-                var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa('test1')}'];
-            
-                ${stringArrayCallsWrapperTemplate}
-                
-                return ${stringArrayCallsWrapperName}(${index});
-            `)();
+                it('should correctly return decoded value', () => {
+                    assert.deepEqual(decodedValue, expectedDecodedValue);
+                });
             });
 
-            it('should correctly return decoded value', () => {
-                assert.deepEqual(decodedValue, expectedDecodedValue);
+            describe('Variant #3: no regexp inside atob template', () => {
+                const indexShiftAmount: number = 0;
+
+                const expectedRegExpTestValue: string = '12345';
+
+                let decodedValue: string;
+
+                before(() => {
+                    const stringArrayTemplate = format(StringArrayTemplate(), {
+                        stringArrayName,
+                        stringArrayFunctionName,
+                        stringArrayStorageItems: `'${swapLettersCase('c3RyaQ==')}'`
+                    });
+                    const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                        atobFunctionName
+                    });
+                    const atobDecodeTemplate: string = format(
+                        StringArrayBase64DecodeTemplate(randomGenerator),
+                        {
+                            atobPolyfill,
+                            atobFunctionName,
+                            selfDefendingCode: '',
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName
+                        }
+                    );
+                    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                        decodeCodeHelperTemplate: atobDecodeTemplate,
+                        indexShiftAmount,
+                        stringArrayCacheName,
+                        stringArrayCallsWrapperName,
+                        stringArrayFunctionName
+                    });
+
+                    decodedValue = Function(`
+                        ${stringArrayTemplate}
+                    
+                        ${stringArrayCallsWrapperTemplate}
+                        
+                        /(.+)/.test("12345");
+                        ${stringArrayCallsWrapperName}(0x0);
+                                        
+                        return RegExp.$1;
+                    `)();
+                });
+
+                it('should correctly return RegExp.$1 match without mutation by atob template', () => {
+                    assert.deepEqual(decodedValue, expectedRegExpTestValue);
+                });
             });
         });
 
-        describe('Variant #3: no regexp inside atob template', () => {
-            const indexShiftAmount: number = 0;
-
-            const expectedRegExpTestValue: string = '12345';
-
-            let decodedValue: string;
-
-            before(() => {
-                const atobPolyfill = format(AtobTemplate(), {
-                    atobFunctionName
+        describe('Variant #2: `selfDefending` option is enabled', () => {
+            const selfDefendingEnabled: boolean = true;
+
+            describe('Variant #1: correct code evaluation for single-line code', () => {
+                describe('Variant #1: index shift amount is `0`', () => {
+                    const index: string = '0x0';
+
+                    const indexShiftAmount: number = 0;
+
+                    const expectedDecodedValue: string = 'test1test1';
+
+                    let decodedValue: string;
+
+                    before(async() => {
+                        const stringArrayTemplate = format(StringArrayTemplate(), {
+                            stringArrayName,
+                            stringArrayFunctionName,
+                            stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa('test1test1')}'`
+                        });
+                        const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                            atobFunctionName
+                        });
+                        const atobDecodeTemplate: string = format(
+                            StringArrayBase64DecodeTemplate(randomGenerator),
+                            {
+                                atobPolyfill,
+                                atobFunctionName,
+                                selfDefendingCode: '',
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName
+                            }
+                        );
+                        const stringArrayCallsWrapperTemplate: string = await minimizeCode(
+                            format(StringArrayCallsWrapperTemplate(), {
+                                decodeCodeHelperTemplate: atobDecodeTemplate,
+                                indexShiftAmount,
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName,
+                                stringArrayFunctionName
+                            })
+                        );
+
+                        decodedValue = Function(`
+                            ${stringArrayTemplate}
+                        
+                            ${stringArrayCallsWrapperTemplate}
+                            
+                            return ${stringArrayCallsWrapperName}(${index});
+                        `)();
+                    });
+
+                    it('should correctly return decoded value', () => {
+                        assert.deepEqual(decodedValue, expectedDecodedValue);
+                    });
                 });
-                const atobDecodeTemplate: string = format(
-                    StringArrayBase64DecodeTemplate(randomGenerator),
-                    {
-                        atobPolyfill,
-                        atobFunctionName,
-                        selfDefendingCode: '',
-                        stringArrayCacheName,
-                        stringArrayCallsWrapperName
-                    }
-                );
-                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                    decodeCodeHelperTemplate: atobDecodeTemplate,
-                    indexShiftAmount,
-                    stringArrayCacheName,
-                    stringArrayCallsWrapperName,
-                    stringArrayName
-                });
-
-                decodedValue = Function(`
-                var ${stringArrayName} = ['${swapLettersCase('c3RyaQ==')}'];
-            
-                ${stringArrayCallsWrapperTemplate}
-                
-                /(.+)/.test("12345");
-                ${stringArrayCallsWrapperName}(0x0);
-                                
-                return RegExp.$1;
-            `)();
             });
 
-            it('should correctly return RegExp.$1 match without mutation by atob template', () => {
-                assert.deepEqual(decodedValue, expectedRegExpTestValue);
+            describe('Variant #2: invalid code evaluation for multi-line code', () => {
+                describe('Variant #1: index shift amount is `0`', () => {
+                    const index: string = '0x0';
+
+                    const indexShiftAmount: number = 0;
+
+                    const expectedDecodedValue: string = 'test18est1';
+
+                    let decodedValue: string;
+
+                    before(() => {
+                        const stringArrayTemplate = format(StringArrayTemplate(), {
+                            stringArrayName,
+                            stringArrayFunctionName,
+                            stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa('test1test1')}'`
+                        });
+                        const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                            atobFunctionName
+                        });
+                        const atobDecodeTemplate: string = format(
+                            StringArrayBase64DecodeTemplate(randomGenerator),
+                            {
+                                atobPolyfill,
+                                atobFunctionName,
+                                selfDefendingCode: '',
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName
+                            }
+                        );
+                        const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                            decodeCodeHelperTemplate: atobDecodeTemplate,
+                            indexShiftAmount,
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName,
+                            stringArrayFunctionName
+                        });
+
+                        decodedValue = Function(`
+                            ${stringArrayTemplate}
+                        
+                            ${stringArrayCallsWrapperTemplate}
+                            
+                            return ${stringArrayCallsWrapperName}(${index});
+                        `)();
+                    });
+
+                    it('should correctly return decoded value', () => {
+                        assert.deepEqual(decodedValue, expectedDecodedValue);
+                    });
+                });
             });
         });
     });
 
     describe('Variant #2: `rc4` encoding', () => {
-        describe('Variant #1: index shift amount is `0`', () => {
-            const index: string = '0x0';
-            const key: string = 'key';
+        describe('Variant #1: `selfDefending` option is disabled', () => {
+            const selfDefendingEnabled: boolean = false;
 
-            const indexShiftAmount: number = 0;
+            describe('Variant #1: index shift amount is `0`', () => {
+                const index: string = '0x0';
+                const key: string = 'key';
 
-            const expectedDecodedValue: string = 'test1';
+                const indexShiftAmount: number = 0;
 
-            let decodedValue: string;
+                const expectedDecodedValue: string = 'test1';
 
-            before(() => {
-                const atobPolyfill = format(AtobTemplate(), {
-                    atobFunctionName
-                });
-                const rc4Polyfill = format(Rc4Template(), {
-                    atobFunctionName,
-                    rc4FunctionName
-                });
-                const rc4decodeCodeHelperTemplate: string = format(
-                    StringArrayRC4DecodeTemplate(randomGenerator),
-                    {
-                        atobPolyfill,
-                        rc4Polyfill,
-                        rc4FunctionName,
-                        selfDefendingCode: '',
+                let decodedValue: string;
+
+                before(() => {
+                    const stringArrayTemplate = format(StringArrayTemplate(), {
+                        stringArrayName,
+                        stringArrayFunctionName,
+                        stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'`
+                    });
+                    const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                        atobFunctionName
+                    });
+                    const rc4Polyfill = format(Rc4Template(), {
+                        atobFunctionName,
+                        rc4FunctionName
+                    });
+                    const rc4decodeCodeHelperTemplate: string = format(
+                        StringArrayRC4DecodeTemplate(randomGenerator),
+                        {
+                            atobPolyfill,
+                            rc4Polyfill,
+                            rc4FunctionName,
+                            selfDefendingCode: '',
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName
+                        }
+                    );
+                    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                        decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
+                        indexShiftAmount,
                         stringArrayCacheName,
-                        stringArrayCallsWrapperName
-                    }
-                );
-                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                    decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
-                    indexShiftAmount,
-                    stringArrayCacheName,
-                    stringArrayCallsWrapperName,
-                    stringArrayName
+                        stringArrayCallsWrapperName,
+                        stringArrayFunctionName
+                    });
+
+                    decodedValue = Function(`
+                        ${stringArrayTemplate}
+                    
+                        ${stringArrayCallsWrapperTemplate}
+                        
+                        return ${stringArrayCallsWrapperName}('${index}', '${key}');
+                    `)();
                 });
 
-                decodedValue = Function(`
-                var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'];
-            
-                ${stringArrayCallsWrapperTemplate}
-                
-                return ${stringArrayCallsWrapperName}('${index}', '${key}');
-            `)();
-            });
-
-            it('should correctly return decoded value', () => {
-                assert.deepEqual(decodedValue, expectedDecodedValue);
+                it('should correctly return decoded value', () => {
+                    assert.deepEqual(decodedValue, expectedDecodedValue);
+                });
             });
-        });
 
-        describe('Variant #2: index shift amount is `5`', () => {
-            const index: string = '0x5';
-            const key: string = 'key';
+            describe('Variant #2: index shift amount is `5`', () => {
+                const index: string = '0x5';
+                const key: string = 'key';
 
-            const indexShiftAmount: number = 5;
+                const indexShiftAmount: number = 5;
 
-            const expectedDecodedValue: string = 'test1';
+                const expectedDecodedValue: string = 'test1';
 
-            let decodedValue: string;
+                let decodedValue: string;
 
-            before(() => {
-                const atobPolyfill = format(AtobTemplate(), {
-                    atobFunctionName
-                });
-                const rc4Polyfill = format(Rc4Template(), {
-                    atobFunctionName,
-                    rc4FunctionName
-                });
-                const rc4decodeCodeHelperTemplate: string = format(
-                    StringArrayRC4DecodeTemplate(randomGenerator),
-                    {
-                        atobPolyfill,
-                        rc4Polyfill,
-                        rc4FunctionName,
-                        selfDefendingCode: '',
+                before(() => {
+                    const stringArrayTemplate = format(StringArrayTemplate(), {
+                        stringArrayName,
+                        stringArrayFunctionName,
+                        stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'`
+                    });
+                    const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                        atobFunctionName
+                    });
+                    const rc4Polyfill = format(Rc4Template(), {
+                        atobFunctionName,
+                        rc4FunctionName
+                    });
+                    const rc4decodeCodeHelperTemplate: string = format(
+                        StringArrayRC4DecodeTemplate(randomGenerator),
+                        {
+                            atobPolyfill,
+                            rc4Polyfill,
+                            rc4FunctionName,
+                            selfDefendingCode: '',
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName
+                        }
+                    );
+                    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                        decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
+                        indexShiftAmount,
                         stringArrayCacheName,
-                        stringArrayCallsWrapperName
-                    }
-                );
-                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                    decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
-                    indexShiftAmount,
-                    stringArrayCacheName,
-                    stringArrayCallsWrapperName,
-                    stringArrayName
+                        stringArrayCallsWrapperName,
+                        stringArrayFunctionName
+                    });
+
+                    decodedValue = Function(`
+                        ${stringArrayTemplate}
+                    
+                        ${stringArrayCallsWrapperTemplate}
+                        
+                        return ${stringArrayCallsWrapperName}('${index}', '${key}');
+                    `)();
                 });
 
-                decodedValue = Function(`
-                var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'];
-            
-                ${stringArrayCallsWrapperTemplate}
-                
-                return ${stringArrayCallsWrapperName}('${index}', '${key}');
-            `)();
+                it('should correctly return decoded value', () => {
+                    assert.deepEqual(decodedValue, expectedDecodedValue);
+                });
             });
+        });
 
-            it('should correctly return decoded value', () => {
-                assert.deepEqual(decodedValue, expectedDecodedValue);
+        describe('Variant #2: `selfDefending` option is enabled', () => {
+            const selfDefendingEnabled: boolean = true;
+
+            describe('Variant #1: correct code evaluation for single-line code', () => {
+                describe('Variant #1: index shift amount is `0`', () => {
+                    const index: string = '0x0';
+                    const key: string = 'key';
+
+                    const indexShiftAmount: number = 0;
+
+                    const expectedDecodedValue: string = 'test1';
+
+                    let decodedValue: string;
+
+                    before(async() => {
+                        const stringArrayTemplate = format(StringArrayTemplate(), {
+                            stringArrayName,
+                            stringArrayFunctionName,
+                            stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'`
+                        });
+                        const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                            atobFunctionName
+                        });
+                        const rc4Polyfill = format(Rc4Template(), {
+                            atobFunctionName,
+                            rc4FunctionName
+                        });
+                        const rc4decodeCodeHelperTemplate: string = format(
+                            StringArrayRC4DecodeTemplate(randomGenerator),
+                            {
+                                atobPolyfill,
+                                rc4Polyfill,
+                                rc4FunctionName,
+                                selfDefendingCode: '',
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName
+                            }
+                        );
+                        const stringArrayCallsWrapperTemplate: string = await minimizeCode(
+                            format(StringArrayCallsWrapperTemplate(), {
+                                decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
+                                indexShiftAmount,
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName,
+                                stringArrayFunctionName
+                            })
+                        );
+
+                        console.log(stringArrayCallsWrapperTemplate);
+
+                        decodedValue = Function(`
+                            ${stringArrayTemplate}
+                        
+                            ${stringArrayCallsWrapperTemplate}
+                            
+                            return ${stringArrayCallsWrapperName}('${index}', '${key}');
+                        `)();
+                    });
+
+                    it('should correctly return decoded value', () => {
+                        assert.deepEqual(decodedValue, expectedDecodedValue);
+                    });
+                });
+            });
+
+            describe('Variant #2: invalid code evaluation for multi-line code', () => {
+                describe('Variant #1: index shift amount is `0`', () => {
+                    const index: string = '0x0';
+                    const key: string = 'key';
+
+                    const indexShiftAmount: number = 0;
+
+                    const expectedDecodedValue: string = 'test\u001c';
+
+                    let decodedValue: string;
+
+                    before(() => {
+                        const stringArrayTemplate = format(StringArrayTemplate(), {
+                            stringArrayName,
+                            stringArrayFunctionName,
+                            stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'`
+                        });
+                        const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                            atobFunctionName
+                        });
+                        const rc4Polyfill = format(Rc4Template(), {
+                            atobFunctionName,
+                            rc4FunctionName
+                        });
+                        const rc4decodeCodeHelperTemplate: string = format(
+                            StringArrayRC4DecodeTemplate(randomGenerator),
+                            {
+                                atobPolyfill,
+                                rc4Polyfill,
+                                rc4FunctionName,
+                                selfDefendingCode: '',
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName
+                            }
+                        );
+                        const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                            decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
+                            indexShiftAmount,
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName,
+                            stringArrayFunctionName
+                        });
+
+                        decodedValue = Function(`
+                            ${stringArrayTemplate}
+                        
+                            ${stringArrayCallsWrapperTemplate}
+                            
+                            return ${stringArrayCallsWrapperName}('${index}', '${key}');
+                        `)();
+                    });
+
+                    it('should correctly return decoded value', () => {
+                        assert.deepEqual(decodedValue, expectedDecodedValue);
+                    });
+                });
             });
         });
     });

+ 4 - 3
test/functional-tests/custom-code-helpers/string-array/templates/string-array-template/StringArrayTemplate.spec.ts

@@ -7,13 +7,14 @@ import { IObfuscationResult } from '../../../../../../src/interfaces/source-code
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
 import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscatorFacade';
+import { getStringArrayRegExp } from '../../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
 describe('StringArrayTemplate', () => {
     describe('Prevailing kind of variables', () => {
         describe('`var` kind', () => {
             let obfuscatedCode: string,
-                stringArrayRegExp: RegExp = /var (_0x(\w){4}) *= *\['.*'];/;
+                stringArrayRegExp: RegExp = getStringArrayRegExp(['foo'], {kind: 'var'});
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
@@ -40,7 +41,7 @@ describe('StringArrayTemplate', () => {
 
         describe('`const` kind', () => {
             let obfuscatedCode: string,
-                stringArrayRegExp: RegExp = /const (_0x(\w){4}) *= *\['.*'];/;
+                stringArrayRegExp: RegExp = getStringArrayRegExp(['foo'], {kind: 'const'});
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
@@ -67,7 +68,7 @@ describe('StringArrayTemplate', () => {
 
         describe('`let` kind', () => {
             let obfuscatedCode: string,
-                stringArrayRegExp: RegExp = /const (_0x(\w){4}) *= *\['.*'];/;
+                stringArrayRegExp: RegExp = getStringArrayRegExp(['foo'], {kind: 'const'});
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');

+ 74 - 23
test/functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/DictionaryIdentifierNamesGenerator.spec.ts

@@ -4,8 +4,9 @@ import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/id
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
-import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
+import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 
@@ -13,10 +14,19 @@ describe('DictionaryIdentifierNamesGenerator', () => {
     describe('generateWithPrefix', () => {
         describe('Variant #1: should not generate same name for string array as existing name in code', () => {
             describe('Variant #1: `renameGlobals` option is disabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const a[cABC] *= *\['_aa', *'_ab'];/;
-                const variableDeclarationIdentifierNameRegExp1: RegExp = /const aa *= *a[cABC]\(0x0\);/;
-                const variableDeclarationIdentifierNameRegExp2: RegExp = /const ab *= *a[cABC]\(0x1\);/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(
+                    ['_aa', '_ab'],
+                    {
+                        name: '\\w*',
+                        kind: 'const'
+                    }
+                );
+                const variableDeclarationIdentifierNameRegExp1: RegExp = /const (\w*) *= *\w*\(0x0\);/;
+                const variableDeclarationIdentifierNameRegExp2: RegExp = /const (\w*) *= *\w*\(0x1\);/;
 
+                let stringArrayName: string = '';
+                let variableDeclarationIdentifierName1: string = '';
+                let variableDeclarationIdentifierName2: string = '';
                 let obfuscatedCode: string;
 
                 before(() => {
@@ -34,26 +44,45 @@ describe('DictionaryIdentifierNamesGenerator', () => {
                             stringArrayThreshold: 1
                         }
                     ).getObfuscatedCode();
+
+                    stringArrayName = getRegExpMatch(obfuscatedCode, stringArrayStorageRegExp);
+                    variableDeclarationIdentifierName1 = getRegExpMatch(
+                        obfuscatedCode,
+                        variableDeclarationIdentifierNameRegExp1
+                    );
+                    variableDeclarationIdentifierName2 = getRegExpMatch(
+                        obfuscatedCode,
+                        variableDeclarationIdentifierNameRegExp2
+                    );
                 });
 
-                it('Match #1: should generate correct identifier for string array', () => {
+                it('Should generate correct identifier for string array', () => {
                     assert.match(obfuscatedCode, stringArrayStorageRegExp);
                 });
 
-                it('Match #2: should keep identifier name for existing variable declaration', () => {
-                    assert.match(obfuscatedCode, variableDeclarationIdentifierNameRegExp1);
+                it('Should keep identifier name for existing variable declaration #1', () => {
+                    assert.notEqual(stringArrayName, variableDeclarationIdentifierName1);
                 });
 
-                it('Match #3: should keep identifier name for existing variable declaration', () => {
-                    assert.match(obfuscatedCode, variableDeclarationIdentifierNameRegExp2);
+                it('Should keep identifier name for existing variable declaration #2', () => {
+                    assert.notEqual(stringArrayName, variableDeclarationIdentifierName2);
                 });
             });
 
             describe('Variant #2: `renameGlobals` option is enabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const a[cABC] *= *\['_aa', *'_ab'];/;
-                const lastVariableDeclarationIdentifierNameRegExp1: RegExp = /const a[cABC] *= *a[cABC]\(0x0\);/;
-                const lastVariableDeclarationIdentifierNameRegExp2: RegExp = /const a[cABC] *= *a[cABC]\(0x1\);/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(
+                    ['_aa', '_ab'],
+                    {
+                        name: '\\w*',
+                        kind: 'const'
+                    }
+                );
+                const variableDeclarationIdentifierNameRegExp1: RegExp = /const (\w*) *= *\w*\(0x0\);/;
+                const variableDeclarationIdentifierNameRegExp2: RegExp = /const (\w*) *= *\w*\(0x1\);/;
 
+                let stringArrayName: string = '';
+                let variableDeclarationIdentifierName1: string = '';
+                let variableDeclarationIdentifierName2: string = '';
                 let obfuscatedCode: string;
 
                 before(() => {
@@ -64,7 +93,7 @@ describe('DictionaryIdentifierNamesGenerator', () => {
                         {
                             ...NO_ADDITIONAL_NODES_PRESET,
                             identifierNamesGenerator: IdentifierNamesGenerator.DictionaryIdentifierNamesGenerator,
-                            identifiersDictionary: ['a', 'b', 'c'],
+                            identifiersDictionary: ['a', 'b', 'c', 'd'],
                             identifiersPrefix: 'a',
                             renameGlobals: true,
                             transformObjectKeys: true,
@@ -72,18 +101,28 @@ describe('DictionaryIdentifierNamesGenerator', () => {
                             stringArrayThreshold: 1
                         }
                     ).getObfuscatedCode();
+
+                    stringArrayName = getRegExpMatch(obfuscatedCode, stringArrayStorageRegExp);
+                    variableDeclarationIdentifierName1 = getRegExpMatch(
+                        obfuscatedCode,
+                        variableDeclarationIdentifierNameRegExp1
+                    );
+                    variableDeclarationIdentifierName2 = getRegExpMatch(
+                        obfuscatedCode,
+                        variableDeclarationIdentifierNameRegExp2
+                    );
                 });
 
-                it('Match #1: should generate correct identifier for string array', () => {
+                it('Should generate correct identifier for string array', () => {
                     assert.match(obfuscatedCode, stringArrayStorageRegExp);
                 });
 
-                it('Match #2: should keep identifier name for existing variable declaration', () => {
-                    assert.match(obfuscatedCode, lastVariableDeclarationIdentifierNameRegExp1);
+                it('Should keep identifier name for existing variable declaration #1', () => {
+                    assert.notEqual(stringArrayName, variableDeclarationIdentifierName1);
                 });
 
-                it('Match #3: should keep identifier name for existing variable declaration', () => {
-                    assert.match(obfuscatedCode, lastVariableDeclarationIdentifierNameRegExp2);
+                it('Should keep identifier name for existing variable declaration #2', () => {
+                    assert.notEqual(stringArrayName, variableDeclarationIdentifierName2);
                 });
             });
         });
@@ -91,11 +130,17 @@ describe('DictionaryIdentifierNamesGenerator', () => {
         describe('Variant #2: should not generate same prefixed name for identifier in code as prefixed name of string array', function () {
             this.timeout(10000);
 
-            const samplesCount: number = 20;
+            const samplesCount: number = 30;
 
             describe('Variant #1: `renameGlobals` option is disabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const ([abAB]{1,3}) *= *\['first', *'abc'];/;
-                const variableDeclarationIdentifierNameRegExp: RegExp = /const ([abAB]{1,3}){1,2} *= *[abAB]{1,3}\(0x0\);/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(
+                    ['first', 'abc'],
+                    {
+                        name: '\\w*',
+                        kind: 'const'
+                    }
+                );
+                const variableDeclarationIdentifierNameRegExp: RegExp = /const (\w*) *= *\w*\(0x0\);/;
 
                 let isIdentifiersAreConflicted: boolean = false;
 
@@ -133,8 +178,14 @@ describe('DictionaryIdentifierNamesGenerator', () => {
             });
 
             describe('Variant #2: `renameGlobals` option is enabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const ([abAB]{1,3}) *= *\['first', *'abc'];/;
-                const variableDeclarationIdentifierNameRegExp: RegExp = /const ([abAB]{1,3}){1,2} *= *[abAB]{1,3}\(0x0\);/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(
+                    ['first', 'abc'],
+                    {
+                        name: '\\w*',
+                        kind: 'const'
+                    }
+                );
+                const variableDeclarationIdentifierNameRegExp: RegExp = /const (\w*) *= *\w*\(0x0\);/;
 
                 let isIdentifiersAreConflicted: boolean = false;
 

+ 0 - 1
test/functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/fixtures/string-array-storage-name-conflict-2.js

@@ -1,5 +1,4 @@
 function foo () {
     const testA = 'first';
     const testB = 'abc';
-    const testC = 'abc';
 }

+ 25 - 6
test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/MangledIdentifierNamesGenerator.spec.ts

@@ -4,6 +4,7 @@ import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/id
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -12,7 +13,10 @@ describe('MangledIdentifierNamesGenerator', () => {
     describe('generateWithPrefix', () => {
         describe('Variant #1: should not generate same name for string array as existing name in code', () => {
             describe('Variant #1: `renameGlobals` option is disabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const ab *= *\['abc'];/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                    kind: 'const',
+                    name: 'ab'
+                });
                 const lastVariableDeclarationIdentifierNameRegExp: RegExp = /const aa *= *ac\(0x0\);/;
 
                 let obfuscatedCode: string;
@@ -43,7 +47,10 @@ describe('MangledIdentifierNamesGenerator', () => {
             });
 
             describe('Variant #2: `renameGlobals` option is enabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const ab *= *\['abc'];/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                    kind: 'const',
+                    name: 'ab'
+                });
                 const lastVariableDeclarationIdentifierNameRegExp: RegExp = /const aB *= *ac\(0x0\);/;
 
                 let obfuscatedCode: string;
@@ -77,7 +84,10 @@ describe('MangledIdentifierNamesGenerator', () => {
 
         describe('Variant #2: should not generate same prefixed name for identifier in code as prefixed name of string array', () => {
             describe('Variant #1: `renameGlobals` option is disabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const aa *= *\['abc', *'last'];/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(['abc', 'last'], {
+                    kind: 'const',
+                    name: 'aa'
+                });
                 const functionDeclarationIdentifierNameRegExp: RegExp = /function foo *\(\) *{/;
                 const lastVariableDeclarationIdentifierNameRegExp: RegExp = /const ac *= *ab\(0x1\);/;
 
@@ -113,7 +123,10 @@ describe('MangledIdentifierNamesGenerator', () => {
             });
 
             describe('Variant #2: `renameGlobals` option is enabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const aa *= *\['abc', *'last'];/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(['abc', 'last'], {
+                    kind: 'const',
+                    name: 'aa'
+                });
                 const functionDeclarationIdentifierNameRegExp: RegExp = /function ac *\(\) *{/;
                 const lastVariableDeclarationIdentifierNameRegExp: RegExp = /const ad *= *ab\(0x1\);/;
 
@@ -221,7 +234,10 @@ describe('MangledIdentifierNamesGenerator', () => {
 
         describe('Variant #2: Should generate different names set for different lexical scopes when string array is enabled', () => {
             describe('Variant #1: `renameGlobals` option is disabled', () => {
-                const stringArrayIdentifierRegExp: RegExp = /var a *= *\['abc'];/;
+                const stringArrayIdentifierRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                    kind: 'var',
+                    name: 'a'
+                });
                 const variableIdentifierRegExp: RegExp = /var foo *= *b\(0x0\);/;
                 const functionDeclarationRegExp1: RegExp = /function bar *\(c, *d\) *{}/;
                 const functionDeclarationRegExp2: RegExp = /function baz *\(c, *d\) *{}/;
@@ -260,7 +276,10 @@ describe('MangledIdentifierNamesGenerator', () => {
             });
 
             describe('Variant #2: `renameGlobals` option is enabled', () => {
-                const stringArrayIdentifierRegExp: RegExp = /var a *= *\['abc'];/;
+                const stringArrayIdentifierRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                    kind: 'var',
+                    name: 'a'
+                });
                 const variableIdentifierRegExp: RegExp = /var c *= *b\(0x0\);/;
                 const functionDeclarationRegExp1: RegExp = /function d *\(f, *g\) *{}/;
                 const functionDeclarationRegExp2: RegExp = /function e *\(f, *g\) *{}/;

+ 20 - 4
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -27,6 +27,7 @@ import { OptionsPreset } from '../../../src/enums/options/presets/OptionsPreset'
 
 import { buildLargeCode } from '../../helpers/buildLargeCode';
 import { getRegExpMatch } from '../../helpers/getRegExpMatch';
+import { getStringArrayRegExp } from '../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../helpers/readFileAsString';
 
 describe('JavaScriptObfuscator', () => {
@@ -444,7 +445,12 @@ describe('JavaScriptObfuscator', () => {
             });
 
             describe('Variant #4: with `stringArray`, `renameGlobals` and `identifiersPrefix` options', () => {
-                const stringArrayRegExp: RegExp = /^var foo_0x(\w){4} *= *\['abc'\];/;
+                const stringArrayRegExp: RegExp = new RegExp(
+                    'function foo_0x([a-f0-9]){4} *\\(\\) *{' +
+                        'var _0x([a-f0-9]){4,6} *= *\\[\'abc\'];.*' +
+                        'return foo_0x([a-f0-9]){4}\\(\\); *' +
+                    '}'
+                );
                 const stringArrayCallRegExp: RegExp = /var foo_0x(\w){4,6} *= *foo_0x(\w){4}\(0x0\);$/;
 
                 let obfuscatedCode: string;
@@ -536,7 +542,7 @@ describe('JavaScriptObfuscator', () => {
         });
 
         describe('latin literal variable value', () => {
-            const stringArrayLatinRegExp: RegExp = /^var _0x(\w){4} *= *\['abc'\];/;
+            const stringArrayLatinRegExp: RegExp = getStringArrayRegExp(['abc']);
             const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\(0x0\);$/;
 
             let obfuscatedCode: string;
@@ -564,7 +570,12 @@ describe('JavaScriptObfuscator', () => {
         });
 
         describe('cyrillic literal variable value', () => {
-            const stringArrayCyrillicRegExp: RegExp = /^var _0x(\w){4} *= *\['абц'\];/;
+            const stringArrayCyrillicRegExp: RegExp = new RegExp(
+                'function _0x(\\w){4} *\\(\\) *{' +
+                    'var _0x([a-f0-9]){4,6} *= *\\[\'абц\'];.*' +
+                    'return _0x(\\w){4}\\(\\); *' +
+                '}'
+            );
             const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\(0x0\);$/;
 
             let obfuscatedCode: string;
@@ -689,7 +700,12 @@ describe('JavaScriptObfuscator', () => {
                 const code1: string = readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js');
                 const code2: string = readFileAsString(__dirname + '/fixtures/simple-input-2.js');
 
-                const regExp: RegExp = /var (_0x(\w){4}) *= *\['.*'\];/;
+                const regExp: RegExp = new RegExp(
+                    'function _0x(\\w){4} *\\(\\) *{' +
+                        'var _0x([a-f0-9]){4,6} *= *\\[\'.*\'];.*' +
+                        'return _0x(\\w){4}\\(\\); *' +
+                    '}'
+                );
 
                 let match1: string,
                     match2: string;

+ 5 - 4
test/functional-tests/node-transformers/converting-transformers/class-field-transformer/ClassFieldTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -31,7 +32,7 @@ describe('ClassFieldTransformer', () => {
             });
 
             describe('Variant #2: `stringArray` option is enabled', () => {
-                const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['property', *'bar'\];/;
+                const stringArrayRegExp: RegExp = getStringArrayRegExp(['property', 'bar']);
                 const stringArrayCallRegExp: RegExp = /\[_0x([a-f0-9]){4}\(0x1\)\]\(\)\{\}/;
 
                 let obfuscatedCode: string;
@@ -103,7 +104,7 @@ describe('ClassFieldTransformer', () => {
             });
 
             describe('Variant #2: `stringArray` option is enabled', () => {
-                const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['property', *'constructor', *'bar'];/;
+                const stringArrayRegExp: RegExp = getStringArrayRegExp(['property', 'constructor', 'bar']);
                 const stringArrayCallRegExp: RegExp = /\[_0x([a-f0-9]){4}\(0x2\)\]\(\)\{\}/;
 
                 let obfuscatedCode: string;
@@ -203,7 +204,7 @@ describe('ClassFieldTransformer', () => {
             });
 
             describe('Variant #2: `stringArray` option is enabled', () => {
-                const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['property', *'bar'\];/;
+                const stringArrayRegExp: RegExp = getStringArrayRegExp(['property', 'bar']);
                 const stringArrayCallRegExp: RegExp = /\[_0x([a-f0-9]){4}\(0x0\)\] *= *0x1;/;
 
                 let obfuscatedCode: string;
@@ -254,7 +255,7 @@ describe('ClassFieldTransformer', () => {
             });
 
             describe('Variant #2: `stringArray` option is enabled', () => {
-                const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['property', *'constructor', *'bar'];/;
+                const stringArrayRegExp: RegExp = getStringArrayRegExp(['property', 'constructor', 'bar']);
                 const stringArrayCallRegExp: RegExp = /\[_0x([a-f0-9]){4}\(0x0\)\] *= *0x1;/;
 
                 let obfuscatedCode: string;

+ 3 - 2
test/functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -30,7 +31,7 @@ describe('MemberExpressionTransformer', () => {
         });
 
         describe('`stringArray` option is enabled', () => {
-            const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['log'\];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['log']);
             const stringArrayCallRegExp: RegExp = /var test *= *console\[_0x([a-f0-9]){4}\(0x0\)\];/;
 
             let obfuscatedCode: string;
@@ -60,7 +61,7 @@ describe('MemberExpressionTransformer', () => {
 
     describe('transformation of member expression node with square brackets', () => {
         describe('Variant #1: square brackets literal ', () => {
-            const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['log'\];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['log']);
             const stringArrayCallRegExp: RegExp = /var test *= *console\[_0x([a-f0-9]){4}\(0x0\)\];/;
 
             let obfuscatedCode: string;

+ 11 - 4
test/functional-tests/node-transformers/finalizing-transformers/directive-placement-transformer/DirectivePlacementTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -11,7 +12,10 @@ describe('DirectivePlacementTransformer', function () {
 
     describe('Variant #1: program scope', () => {
         describe('Variant #1: directive at the top of program scope', () => {
-            const directiveRegExp: RegExp = /^'use strict'; *var _0x([a-f0-9]){4} *= *\['test'];/;
+            const directiveRegExp: RegExp = new RegExp(
+                '^\'use strict\';.*' +
+                getStringArrayRegExp(['test']).source
+            );
 
             let obfuscatedCode: string;
 
@@ -34,9 +38,8 @@ describe('DirectivePlacementTransformer', function () {
         });
 
         describe('Variant #2: directive-like string literal at the middle of program scope', () => {
+            const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(['test', 'use\\\\x20strict']);
             const directiveRegExp: RegExp = new RegExp(
-                '^var _0x([a-f0-9]){4} *= *\\[\'test\', *\'use\\\\x20strict\']; *' +
-                '.*?' +
                 'var test *= *_0x([a-f0-9]){4}\\(0x0\\);.*' +
                 '_0x([a-f0-9]){4}\\(0x1\\);'
             );
@@ -56,7 +59,11 @@ describe('DirectivePlacementTransformer', function () {
                 ).getObfuscatedCode();
             });
 
-            it('should keep directive-like string literal at the middle of program scope', () => {
+            it('should add directive-like string literal to the string array', () => {
+                assert.match(obfuscatedCode, stringArrayStorageRegExp);
+            });
+
+            it('should add call to the directive-like string literal from the string array', () => {
                 assert.match(obfuscatedCode, directiveRegExp);
             });
         });

+ 2 - 1
test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/EscapeSequenceTransformer.spec.ts

@@ -5,6 +5,7 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/N
 import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 import { StringArrayIndexesType } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -57,7 +58,7 @@ describe('EscapeSequenceTransformer', function () {
     });
 
     describe('Variant #3: `unicodeEscapeSequence` and `stringArray` options are enabled', () => {
-        const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['\\x74\\x65\\x73\\x74'\];/;
+        const stringArrayRegExp: RegExp = getStringArrayRegExp(['\\\\x74\\\\x65\\\\x73\\\\x74']);
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('\\x30\\x78\\x30'\);/;
 
         let obfuscatedCode: string;

+ 5 - 1
test/functional-tests/node-transformers/initializing-transformers/comments-transformer/CommentsTransformer.spec.ts

@@ -5,6 +5,7 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/N
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 
 describe('CommentsTransformer', () => {
     const lineSeparatorEscaped: string = '\\r?\\n';
@@ -142,7 +143,10 @@ describe('CommentsTransformer', () => {
 
     describe('Variant #7: simple comment with preserved words and additional code helper is inserted', () => {
         describe('Variant #1: `stringArray` code helper', () => {
-            const regExp: RegExp = /^\/\/ *@license *test *comment *\n*var _0x([a-f0-9]){4} *= *\['abc'];/;
+            const regExp: RegExp = new RegExp(
+                '^\\/\\/ *@license *test *comment *\\n*.*' +
+                getStringArrayRegExp(['abc']).source
+            );
 
             let obfuscatedCode: string;
 

+ 4 - 3
test/functional-tests/node-transformers/preparing-transformers/eval-call-expression-transformer/EvalCallExpressionTransformer.spec.ts

@@ -4,10 +4,11 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/N
 
 import { StringArrayIndexesType } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 
+import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
-import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
 
 describe('EvalCallExpressionTransformer', () => {
     describe('Variant #1: identifier reference', () => {
@@ -95,7 +96,7 @@ describe('EvalCallExpressionTransformer', () => {
 
     describe('Variant #4: string array calls wrapper call', () => {
         describe('Variant #1: hexadecimal number indexes type', () => {
-            const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['log', *'bar'];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['log', 'bar']);
             const stringArrayCallsWrapperRegExp: RegExp = /eval *\('console\[_0x([a-f0-9]){4,6}\(0\)]\(_0x([a-f0-9]){4,6}\(1\)\);'\);/;
 
             let obfuscatedCode: string;
@@ -126,7 +127,7 @@ describe('EvalCallExpressionTransformer', () => {
         });
 
         describe('Variant #1: hexadecimal numeric string indexes type', () => {
-            const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['log', *'bar'];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['log', 'bar']);
             const stringArrayCallsWrapperRegExp: RegExp = /eval *\('console\[_0x([a-f0-9]){4,6}\(\\'0x0\\'\)]\(_0x([a-f0-9]){4,6}\(\\'0x1\\'\)\);'\);/;
 
             let obfuscatedCode: string;

+ 2 - 1
test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/black-list-obfuscating-guard/BlackListObfuscatingGuard.spec.ts

@@ -4,13 +4,14 @@ import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscator
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
 describe('BlackListObfuscatingGuard', () => {
     describe('check', () => {
         describe('`\'use strict\';` operator', () => {
             const useStrictOperatorRegExp: RegExp = /'use *strict';/;
-            const stringArrayLatinRegExp: RegExp = /var _0x(\w){4} *= *\['abc'\];/;
+            const stringArrayLatinRegExp: RegExp = getStringArrayRegExp(['abc']);
             const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\(0x0\);$/;
 
             let obfuscatedCode: string;

+ 8 - 8
test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/conditional-comment-obfuscating-guard/ConditionalCommentObfuscatingGuard.spec.ts

@@ -10,9 +10,9 @@ describe('ConditionalCommentObfuscatingGuard', () => {
     describe('check', () => {
         describe('Variant #1: `disable` conditional comment', () => {
             const disableConditionalCommentRegExp: RegExp = /\/\/ *javascript-obfuscator:disable/;
-            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *0x1;/;
+            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *0x1;/;
             const ignoredVariableDeclarationRegExp: RegExp = /var bar *= *2;/;
-            const consoleLogRegExp: RegExp = /console.log\(_0x([a-f0-9]){4,6}\);/;
+            const consoleLogRegExp: RegExp = /console.log\(_0x([a-f0-9]){5,6}\);/;
 
             let obfuscatedCode: string;
 
@@ -47,8 +47,8 @@ describe('ConditionalCommentObfuscatingGuard', () => {
         describe('Variant #2: `disable` and `enable` conditional comments #1', () => {
             const disableConditionalCommentRegExp: RegExp = /\/\/ *javascript-obfuscator:disable/;
             const enableConditionalCommentRegExp: RegExp = /\/\/ *javascript-obfuscator:enable/;
-            const obfuscatedVariableDeclaration1RegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *0x1;/;
-            const obfuscatedVariableDeclaration2RegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *0x3;/;
+            const obfuscatedVariableDeclaration1RegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *0x1;/;
+            const obfuscatedVariableDeclaration2RegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *0x3;/;
             const ignoredVariableDeclarationRegExp: RegExp = /var bar *= *2;/;
 
             let obfuscatedCode: string;
@@ -87,7 +87,7 @@ describe('ConditionalCommentObfuscatingGuard', () => {
 
         describe('Variant #3: `disable` and `enable` conditional comments #2', () => {
             const ignoredVariableDeclarationRegExp: RegExp = /var foo *= *1;/;
-            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *0x2;/;
+            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *0x2;/;
 
             let obfuscatedCode: string;
 
@@ -139,13 +139,13 @@ describe('ConditionalCommentObfuscatingGuard', () => {
         });
 
         describe('Variant #5: `disable` and `enable` conditional comments with dead code injection', () => {
-            const obfuscatedFunctionExpressionRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *function *\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}\) *{/g;
+            const obfuscatedFunctionExpressionRegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *function *\(_0x([a-f0-9]){5,6}, *_0x([a-f0-9]){5,6}, *_0x([a-f0-9]){5,6}\) *{/g;
             const expectedObfuscatedFunctionExpressionLength: number = 3;
 
             const ignoredFunctionExpression1RegExp: RegExp = /var bar *= *function *\(a, *b, *c\) *{/;
             const ignoredFunctionExpression2RegExp: RegExp = /var baz *= *function *\(a, *b, *c\) *{/;
 
-            const obfuscatedFunctionCallRegExp: RegExp = /_0x([a-f0-9]){4,6}\( *\);/g;
+            const obfuscatedFunctionCallRegExp: RegExp = /_0x([a-f0-9]){5,6}\( *\);/g;
             const expectedObfuscatedFunctionCallsLength: number = 3;
 
             const ignoredFunctionCall1RegExp: RegExp = /bar\( *\);/;
@@ -209,7 +209,7 @@ describe('ConditionalCommentObfuscatingGuard', () => {
         });
 
         describe('Variant #6: `disable` and `enable` conditional comments with control flow flattening', () => {
-            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['[a-zA-Z0-9]{1,5}'];/;
+            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *_0x([a-f0-9]){5,6}\['[a-zA-Z0-9]{1,5}'];/;
             const ignoredVariableDeclarationRegExp: RegExp = /var bar *= *'bar';/;
 
             let obfuscatedCode: string;

+ 9 - 2
test/functional-tests/node-transformers/preparing-transformers/variable-preserve-transformer/VariablePreserveTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { stubNodeTransformers } from '../../../../helpers/stubNodeTransformers';
 
@@ -11,7 +12,10 @@ import { ObjectPatternPropertiesTransformer } from '../../../../../src/node-tran
 describe('VariablePreserveTransformer', () => {
     describe('Variant #1: string array storage name conflicts with identifier name', () => {
         describe('Variant #1: `renameGlobals` option is disabled', () => {
-            const stringArrayStorageNameRegExp: RegExp = /const b *= *\['abc'];/;
+            const stringArrayStorageNameRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                kind: 'const',
+                name: 'b'
+            });
             const identifierNameRegExp: RegExp = /const a *= *c\(0x0\);/;
 
             let obfuscatedCode: string;
@@ -40,7 +44,10 @@ describe('VariablePreserveTransformer', () => {
         });
 
         describe('Variant #2: `renameGlobals` option is enabled', () => {
-            const stringArrayStorageNameRegExp: RegExp = /const b *= *\['abc'];/;
+            const stringArrayStorageNameRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                kind: 'const',
+                name: 'b'
+            });
             const identifierNameRegExp: RegExp = /const d *= *c\(0x0\);/;
 
             let obfuscatedCode: string;

+ 0 - 11
test/functional-tests/node-transformers/string-array-transformers/string-array-scope-calls-wrapper-transformer/StringArrayScopeCallsWrapperTransformer.spec.ts

@@ -651,11 +651,9 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                         '}';
 
                     const stringArrayScopeCallsWrapperRegExp1: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         getStringArrayCallsWrapperMatch('f')
                     );
                     const stringArrayScopeCallsWrapperRegExp2: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function test *\\( *\\) *{.*' +
                             `${getStringArrayCallsWrapperMatch('g')}.*?` +
                         '}'
@@ -723,11 +721,9 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                         '}';
 
                     const stringArrayScopeCallsWrapperRegExp1: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         getStringArrayCallsWrapperMatch('f')
                     );
                     const stringArrayScopeCallsWrapperRegExp2: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function test *\\( *\\) *{.*' +
                             `${getStringArrayCallsWrapperMatch('g')}.*?` +
                         '}'
@@ -791,13 +787,11 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
 
             describe('Variant #2: correct chained calls', () => {
                 const stringArrayScopeCallsWrapperRegExp1: RegExp = new RegExp(
-                    'const a *= *\\[.*?];.*?' +
                     'function *f *\\(c, *d\\) *{' +
                         `return b\\([cd] *-(?: -)?${hexadecimalIndexMatch}, *[cd]\\);` +
                     '}.*'
                 );
                 const stringArrayScopeCallsWrapperRegExp2: RegExp = new RegExp(
-                    'const a *= *\\[.*?];.*?' +
                     'function test *\\( *\\) *{.*' +
                         'function *g *\\(c, *d\\) *{' +
                             `return f\\(` +
@@ -863,7 +857,6 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
 
             describe('Variant #3: no wrappers on a root scope', () => {
                 const stringArrayScopeCallsWrapperRegExp: RegExp = new RegExp(
-                    'const a *= *\\[.*?];.*' +
                     'function test *\\( *\\) *{.*' +
                         'function *f*\\(c, *d\\) *{' +
                             `return b\\([cd] *-(?: -)?${hexadecimalIndexMatch}, *[cd]\\);` +
@@ -1088,13 +1081,11 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                         .join(', *');
 
                     const stringArrayScopeCallsWrapperRegExp1: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function *f *\\(c, *d, *e, *h, *i\\) *{' +
                             `return b\\([cdehi] *-(?: -)?${hexadecimalIndexMatch}, *[cdehi]\\);` +
                         '}.*'
                     );
                     const stringArrayScopeCallsWrapperRegExp2: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function test *\\( *\\) *{.*' +
                             'function *g *\\(c, *d, *e, *h, *i\\) *{' +
                                 `return f\\(` +
@@ -1169,13 +1160,11 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                         .join(', *');
 
                     const stringArrayScopeCallsWrapperRegExp1: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function *f *\\(c, *d, *e, *h, *i\\) *{' +
                             `return b\\([cdehi] *-(?: -)?${hexadecimalIndexMatch}, *[cdehi]\\);` +
                         '}.*'
                     );
                     const stringArrayScopeCallsWrapperRegExp2: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function test *\\( *\\) *{.*' +
                             'function *g *\\(c, *d, *e, *h, *i\\) *{' +
                                 `return f\\(` +

+ 13 - 7
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts

@@ -7,6 +7,7 @@ import { StringArrayWrappersType } from '../../../../../src/enums/node-transform
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
 import { swapLettersCase } from '../../../../helpers/swapLettersCase';
@@ -17,7 +18,7 @@ describe('StringArrayTransformer', function () {
     this.timeout(120000);
 
     describe('Variant #1: default behaviour', () => {
-        const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
+        const stringArrayRegExp: RegExp = getStringArrayRegExp(['test']);
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x0\);/;
 
         let obfuscatedCode: string;
@@ -355,7 +356,7 @@ describe('StringArrayTransformer', function () {
     });
 
     describe('Variant #5: same literal node values', () => {
-        const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
+        const stringArrayRegExp: RegExp = getStringArrayRegExp(['test']);
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x0\);/;
 
         let obfuscatedCode: string;
@@ -406,7 +407,12 @@ describe('StringArrayTransformer', function () {
     });
 
     describe('Variant #7: base64 encoding', () => {
-        const stringArrayRegExp: RegExp = new RegExp(`^var _0x([a-f0-9]){4} *= *\\['${swapLettersCase('dGVzdA')}'];`);
+        const stringArrayRegExp: RegExp = new RegExp(
+            'function _0x([a-f0-9]){4} *\\(\\) *{' +
+                `var _0x([a-f0-9]){4,6} *= *\\[\'${swapLettersCase('dGVzdA')}\'];.*` +
+                'return _0x([a-f0-9]){4}\\(\\); *' +
+            '}'
+        );
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x0\);/;
 
         let obfuscatedCode: string;
@@ -506,8 +512,8 @@ describe('StringArrayTransformer', function () {
             const expectedMatchesChance: number = 0.5;
             const expectedMatchesDelta: number = 0.15;
 
-            const noneEncodingRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
-            const base64EncodingRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['DgvZDa'\];/;
+            const noneEncodingRegExp: RegExp = getStringArrayRegExp(['test']);
+            const base64EncodingRegExp: RegExp = getStringArrayRegExp(['DgvZDa']);
 
             let noneEncodingMatchesCount: number = 0;
             let base64EncodingMatchesCount: number = 0;
@@ -1080,7 +1086,7 @@ describe('StringArrayTransformer', function () {
 
     describe('Variant #16: object expression key literal', () => {
         describe('Variant #1: base key literal', () => {
-            const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['bar'];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['bar'])
             const objectExpressionRegExp: RegExp = /var test *= *{'foo' *: *_0x([a-f0-9]){4}\(0x0\)};/;
 
             let obfuscatedCode: string;
@@ -1108,7 +1114,7 @@ describe('StringArrayTransformer', function () {
         });
 
         describe('Variant #2: computed key literal', () => {
-            const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['foo', *'bar'];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['foo', 'bar'])
             const objectExpressionRegExp: RegExp = /var test *= *{\[_0x([a-f0-9]){4}\(0x0\)] *: *_0x([a-f0-9]){4}\(0x1\)};/;
 
             let obfuscatedCode: string;

+ 11 - 10
test/functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -15,7 +16,7 @@ describe('StringArrayStorage', () => {
             const delta: number = 0.1;
             const expectedVariantProbability: number = 1;
 
-            const stringArrayVariantRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\[(?:'.*?', *)?'test'(?:, *'.*?')?];/g;
+            const stringArrayVariantRegExp: RegExp = /var.*= *\[(?:'.*?', *)?'test'(?:, *'.*?')?];.*/;
             const literalNodeVariantRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x.\);/g;
 
             let stringArrayVariantProbability: number,
@@ -74,8 +75,8 @@ describe('StringArrayStorage', () => {
             const literalNodeVariantsCount: number = 1;
 
             const stringArrayVariantRegExps: RegExp[] = [
-                /var _0x([a-f0-9]){4} *= *\['foo', *'bar', *'baz'(?:, *'.*?')+];/g,
-                /var _0x([a-f0-9]){4} *= *\[(?:'.*?', *)+'foo', *'bar', *'baz'];/g
+                /var.*= *\['foo', *'bar', *'baz'(?:, *'.*?')+];.*/,
+                /var.*= *\[(?:'.*?', *)+'foo', *'bar', *'baz'];.*/,
             ];
             const literalNodeVariantRegExps: RegExp[] = [
                 new RegExp(
@@ -155,7 +156,7 @@ describe('StringArrayStorage', () => {
             const delta: number = 0.1;
             const expectedVariantProbability: number = 1;
 
-            const stringArrayVariantRegExp1: RegExp = /var _0x([a-f0-9]){4} *= *\['test'];/g;
+            const stringArrayVariantRegExp1: RegExp = getStringArrayRegExp(['test']);
             const literalNodeVariant1RegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x0\);/g;
 
             let stringArrayVariant1Probability: number,
@@ -212,12 +213,12 @@ describe('StringArrayStorage', () => {
             const variantsCount: number = 6;
 
             const stringArrayVariantRegExps: RegExp[] = [
-                /var _0x([a-f0-9]){4} *= *\['foo', *'bar', *'baz'];/g,
-                /var _0x([a-f0-9]){4} *= *\['foo', *'baz', *'bar'];/g,
-                /var _0x([a-f0-9]){4} *= *\['bar', *'foo', *'baz'];/g,
-                /var _0x([a-f0-9]){4} *= *\['bar', *'baz', *'foo'];/g,
-                /var _0x([a-f0-9]){4} *= *\['baz', *'foo', *'bar'];/g,
-                /var _0x([a-f0-9]){4} *= *\['baz', *'bar', *'foo'];/g
+                getStringArrayRegExp(['foo', 'bar', 'baz']),
+                getStringArrayRegExp(['foo', 'baz', 'bar']),
+                getStringArrayRegExp(['bar', 'foo', 'baz']),
+                getStringArrayRegExp(['bar', 'baz', 'foo']),
+                getStringArrayRegExp(['baz', 'foo', 'bar']),
+                getStringArrayRegExp(['baz', 'bar', 'foo'])
             ];
 
             const literalNodeVariantRegExps: RegExp[] = [

+ 28 - 0
test/helpers/get-string-array-regexp.ts

@@ -0,0 +1,28 @@
+const defaultOptions = {
+    name: '_0x([a-f0-9]){4}',
+    kind: 'var'
+}
+
+/**
+ * Returns string array RegExp
+ *
+ * @returns {RegExp}
+ */
+export function getStringArrayRegExp(
+    stringArrayItems: string[],
+    options: Partial<typeof defaultOptions> = {}
+): RegExp {
+    const mergedOptions = {
+        ...defaultOptions,
+        ...options
+    };
+
+    const {name, kind} = mergedOptions;
+
+    return new RegExp(
+        `function (${name}) *\\(\\) *{` +
+            `${kind}.*= *\\[${stringArrayItems.map((item: string) => `\'${item}\'`).join(',')}];.*` +
+            `return ${name}\\(\\); *` +
+        '}'
+    );
+}

+ 13 - 0
test/helpers/minimizeCode.ts

@@ -0,0 +1,13 @@
+import {minify} from 'terser';
+
+/**
+ * Minimizes code
+ *
+ * @param {string} code
+ * @returns {string}
+ */
+export async function minimizeCode (code: string): Promise<string> {
+    const result = await minify(code);
+
+    return result.code ?? '';
+}

+ 9 - 0
yarn.lock

@@ -4458,6 +4458,15 @@ terser@^5.7.0:
     source-map "~0.7.2"
     source-map-support "~0.5.19"
 
+terser@^5.7.1:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.1.tgz#2dc7a61009b66bb638305cb2a824763b116bf784"
+  integrity sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg==
+  dependencies:
+    commander "^2.20.0"
+    source-map "~0.7.2"
+    source-map-support "~0.5.19"
+
 test-exclude@^6.0.0:
   version "6.0.0"
   resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz"