Bladeren bron

Merge pull request #664 from javascript-obfuscator/numbers-to-expressions

`numbersToExpressions` option
Timofey Kachalov 4 jaren geleden
bovenliggende
commit
a0761f55fa
31 gewijzigde bestanden met toevoegingen van 1245 en 23 verwijderingen
  1. 4 0
      CHANGELOG.md
  2. 19 0
      README.md
  3. 0 0
      dist/index.browser.js
  4. 0 0
      dist/index.cli.js
  5. 0 0
      dist/index.js
  6. 4 4
      package.json
  7. 1 0
      src/JavaScriptObfuscator.ts
  8. 130 0
      src/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.ts
  9. 4 0
      src/cli/JavaScriptObfuscatorCLI.ts
  10. 1 0
      src/container/ServiceIdentifiers.ts
  11. 7 0
      src/container/modules/analyzers/AnalyzersModule.ts
  12. 7 1
      src/container/modules/node-transformers/ConvertingTransformersModule.ts
  13. 2 0
      src/custom-code-helpers/CustomCodeHelperObfuscator.ts
  14. 1 0
      src/enums/node-transformers/NodeTransformer.ts
  15. 9 0
      src/interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer.ts
  16. 1 0
      src/interfaces/options/IOptions.ts
  17. 175 0
      src/node-transformers/converting-transformers/NumberToNumericalExpressionTransformer.ts
  18. 6 0
      src/options/Options.ts
  19. 1 0
      src/options/presets/Default.ts
  20. 1 0
      src/options/presets/NoCustomNodes.ts
  21. 1 0
      src/types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData.ts
  22. 82 0
      src/utils/NumberUtils.ts
  23. 8 5
      test/dev/dev.ts
  24. 1 0
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  25. 167 0
      test/functional-tests/node-transformers/converting-transformers/numbers-to-numerical-expressions-transformer/NumbersToNumericalExpressionsTransformer.spec.ts
  26. 3 0
      test/functional-tests/node-transformers/converting-transformers/numbers-to-numerical-expressions-transformer/fixtures/non-computed-object-key.js
  27. 2 0
      test/index.spec.ts
  28. 1 0
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts
  29. 178 0
      test/unit-tests/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.spec.ts
  30. 417 1
      test/unit-tests/utils/NumberUtils.spec.ts
  31. 12 12
      yarn.lock

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 Change Log
 
 
+v1.6.0
+---
+* **New option:** `numbersToExpressions` enables numbers conversion to expressions
+
 v1.5.2
 v1.5.2
 ---
 ---
 * Prevented mutation of the name sequences of `mangled` identifier name generators
 * Prevented mutation of the name sequences of `mangled` identifier name generators

+ 19 - 0
README.md

@@ -329,6 +329,7 @@ Following options are available for the JS Obfuscator:
     identifiersPrefix: '',
     identifiersPrefix: '',
     inputFileName: '',
     inputFileName: '',
     log: false,
     log: false,
+    numbersToExpressions: false,
     renameGlobals: false,
     renameGlobals: false,
     renameProperties: false,
     renameProperties: false,
     reservedNames: [],
     reservedNames: [],
@@ -375,6 +376,7 @@ Following options are available for the JS Obfuscator:
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-prefix <string>
     --identifiers-prefix <string>
     --log <boolean>
     --log <boolean>
+    --numbers-to-expressions <boolean>
     --rename-globals <boolean>
     --rename-globals <boolean>
     --rename-properties <boolean>
     --rename-properties <boolean>
     --reserved-names '<list>' (comma separated)
     --reserved-names '<list>' (comma separated)
@@ -667,6 +669,20 @@ Type: `boolean` Default: `false`
 
 
 Enables logging of the information to the console.
 Enables logging of the information to the console.
 
 
+### `numbersToExpressions`
+Type: `boolean` Default: `false`
+
+Enables numbers conversion to expressions
+
+Example: 
+```ts
+// input
+const foo = 1234;
+
+// output
+const foo=-0xd93+-0x10b4+0x41*0x67+0x84e*0x3+-0xff8;
+```
+
 ### `renameGlobals`
 ### `renameGlobals`
 Type: `boolean` Default: `false`
 Type: `boolean` Default: `false`
 
 
@@ -985,6 +1001,7 @@ Performance will 50-100% slower than without obfuscation
     disableConsoleOutput: true,
     disableConsoleOutput: true,
     identifierNamesGenerator: 'hexadecimal',
     identifierNamesGenerator: 'hexadecimal',
     log: false,
     log: false,
+    numbersToExpressions: true,
     renameGlobals: false,
     renameGlobals: false,
     rotateStringArray: true,
     rotateStringArray: true,
     selfDefending: true,
     selfDefending: true,
@@ -1016,6 +1033,7 @@ Performance will 30-35% slower than without obfuscation
     disableConsoleOutput: true,
     disableConsoleOutput: true,
     identifierNamesGenerator: 'hexadecimal',
     identifierNamesGenerator: 'hexadecimal',
     log: false,
     log: false,
+    numbersToExpressions: false,
     renameGlobals: false,
     renameGlobals: false,
     rotateStringArray: true,
     rotateStringArray: true,
     selfDefending: true,
     selfDefending: true,
@@ -1045,6 +1063,7 @@ Performance will slightly slower than without obfuscation
     disableConsoleOutput: true,
     disableConsoleOutput: true,
     identifierNamesGenerator: 'hexadecimal',
     identifierNamesGenerator: 'hexadecimal',
     log: false,
     log: false,
+    numbersToExpressions: false,
     renameGlobals: false,
     renameGlobals: false,
     rotateStringArray: true,
     rotateStringArray: true,
     selfDefending: true,
     selfDefending: true,

File diff suppressed because it is too large
+ 0 - 0
dist/index.browser.js


File diff suppressed because it is too large
+ 0 - 0
dist/index.cli.js


File diff suppressed because it is too large
+ 0 - 0
dist/index.js


+ 4 - 4
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "javascript-obfuscator",
   "name": "javascript-obfuscator",
-  "version": "1.5.2",
+  "version": "1.6.0",
   "description": "JavaScript obfuscator",
   "description": "JavaScript obfuscator",
   "keywords": [
   "keywords": [
     "obfuscator",
     "obfuscator",
@@ -54,7 +54,7 @@
     "@types/mkdirp": "1.0.1",
     "@types/mkdirp": "1.0.1",
     "@types/mocha": "7.0.2",
     "@types/mocha": "7.0.2",
     "@types/multimatch": "4.0.0",
     "@types/multimatch": "4.0.0",
-    "@types/node": "14.0.20",
+    "@types/node": "14.0.22",
     "@types/rimraf": "3.0.0",
     "@types/rimraf": "3.0.0",
     "@types/sinon": "9.0.4",
     "@types/sinon": "9.0.4",
     "@types/string-template": "1.0.2",
     "@types/string-template": "1.0.2",
@@ -65,7 +65,7 @@
     "coveralls": "3.1.0",
     "coveralls": "3.1.0",
     "eslint": "7.4.0",
     "eslint": "7.4.0",
     "eslint-plugin-import": "2.22.0",
     "eslint-plugin-import": "2.22.0",
-    "eslint-plugin-jsdoc": "29.1.2",
+    "eslint-plugin-jsdoc": "29.1.3",
     "eslint-plugin-no-null": "1.0.2",
     "eslint-plugin-no-null": "1.0.2",
     "eslint-plugin-prefer-arrow": "1.2.1",
     "eslint-plugin-prefer-arrow": "1.2.1",
     "eslint-plugin-unicorn": "20.1.0",
     "eslint-plugin-unicorn": "20.1.0",
@@ -83,7 +83,7 @@
     "typescript": "3.9.6",
     "typescript": "3.9.6",
     "webpack": "4.43.0",
     "webpack": "4.43.0",
     "webpack-cli": "3.3.12",
     "webpack-cli": "3.3.12",
-    "webpack-node-externals": "2.3.0"
+    "webpack-node-externals": "2.5.0"
   },
   },
   "repository": {
   "repository": {
     "type": "git",
     "type": "git",

+ 1 - 0
src/JavaScriptObfuscator.ts

@@ -75,6 +75,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.MemberExpressionTransformer,
         NodeTransformer.MemberExpressionTransformer,
         NodeTransformer.MetadataTransformer,
         NodeTransformer.MetadataTransformer,
         NodeTransformer.MethodDefinitionTransformer,
         NodeTransformer.MethodDefinitionTransformer,
+        NodeTransformer.NumberToNumericalExpressionTransformer,
         NodeTransformer.ObfuscatingGuardsTransformer,
         NodeTransformer.ObfuscatingGuardsTransformer,
         NodeTransformer.ObjectExpressionKeysTransformer,
         NodeTransformer.ObjectExpressionKeysTransformer,
         NodeTransformer.ObjectExpressionTransformer,
         NodeTransformer.ObjectExpressionTransformer,

+ 130 - 0
src/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.ts

@@ -0,0 +1,130 @@
+import { injectable, inject } from 'inversify';
+
+import { TNumberNumericalExpressionData } from '../../types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData';
+
+import { INumberNumericalExpressionAnalyzer } from '../../interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import { NumberUtils } from '../../utils/NumberUtils';
+
+/**
+ * Based on https://gist.github.com/da411d/0e59f79dcf4603cdabf0024a10eeb6fe
+ */
+@injectable()
+export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpressionAnalyzer {
+    /**
+     * @type {number}
+     */
+    private static readonly additionalParts: number = 3;
+
+    /**
+     * @type {Map<number, number[]>}
+     */
+    private readonly numberFactorsMap: Map<number, number[]> = new Map();
+
+    /**
+     * @type {IRandomGenerator}
+     */
+    private readonly randomGenerator: IRandomGenerator;
+
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator
+    ) {
+        this.randomGenerator = randomGenerator;
+    }
+
+    /**
+     * @param {number} number
+     * @returns {TNumberNumericalExpressionData}
+     */
+    public analyze (number: number): TNumberNumericalExpressionData {
+        if (isNaN(number)) {
+            throw new Error('Given value is NaN');
+        }
+
+        if (NumberUtils.isUnsafeNumber(number)) {
+            return [number];
+        }
+
+        const additionParts: number[] = this.generateAdditionParts(number);
+
+        return additionParts.map((addition: number) => this.mixWithMultiplyParts(addition));
+    }
+
+    /**
+     * @param {number} number
+     * @returns {number[]}
+     */
+    private generateAdditionParts (number: number): number[] {
+        const additionParts = [];
+
+        const upperNumberLimit: number = Math.min(Math.abs(number * 2), Number.MAX_SAFE_INTEGER);
+
+        const from: number = Math.min(-10000, -upperNumberLimit);
+        const to: number = Math.max(10000, upperNumberLimit);
+
+        let temporarySum = 0;
+
+        for (let i = 0; i < NumberNumericalExpressionAnalyzer.additionalParts; i++) {
+            if (i < NumberNumericalExpressionAnalyzer.additionalParts - 1) {
+                // trailing parts
+
+                let addition: number = this.randomGenerator.getRandomInteger(from, to);
+                const isUnsafeCombination: boolean = NumberUtils.isUnsafeNumber(temporarySum + addition);
+
+                // we have to flip sign if total expression sum overflows over safe integer limits
+                if (isUnsafeCombination) {
+                    addition = -addition;
+                }
+
+                additionParts.push(addition);
+                temporarySum += addition;
+            } else {
+                const combination: number = number - temporarySum;
+                const isUnsafeCombination: boolean = NumberUtils.isUnsafeNumber(combination);
+
+                // last part
+                if (isUnsafeCombination) {
+                    additionParts.push(0 - temporarySum);
+                    additionParts.push(number);
+                } else {
+                    additionParts.push(combination);
+                }
+            }
+        }
+
+        return additionParts;
+    }
+
+    /**
+     * @param {number} number
+     * @returns {number | number[]}
+     */
+    private mixWithMultiplyParts (number: number): number | number[] {
+        const shouldMixWithMultiplyParts: boolean = this.randomGenerator.getMathRandom() > 0.5;
+
+        if (!shouldMixWithMultiplyParts || number === 0) {
+            return number;
+        }
+
+        let factors: number[] | null = this.numberFactorsMap.get(number) ?? null;
+
+        if (!factors) {
+           factors = NumberUtils.getFactors(number);
+           this.numberFactorsMap.set(number, factors);
+        }
+
+        if (!factors.length) {
+            return number;
+        }
+
+        const factor: number = factors[this.randomGenerator.getRandomInteger(0, factors.length - 1)];
+
+        return [factor, number / factor];
+    }
+}

+ 4 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -249,6 +249,10 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 '--log <boolean>', 'Enables logging of the information to the console',
                 '--log <boolean>', 'Enables logging of the information to the console',
                 BooleanSanitizer
                 BooleanSanitizer
             )
             )
+            .option(
+                '--numbers-to-expressions <boolean>', 'Enables numbers conversion to expressions',
+                BooleanSanitizer
+            )
             .option(
             .option(
                 '--reserved-names <list> (comma separated, without whitespaces)',
                 '--reserved-names <list> (comma separated, without whitespaces)',
                 'Disables obfuscation and generation of identifiers, which being matched by passed RegExp patterns (comma separated)',
                 'Disables obfuscation and generation of identifiers, which being matched by passed RegExp patterns (comma separated)',

+ 1 - 0
src/container/ServiceIdentifiers.ts

@@ -37,6 +37,7 @@ export enum ServiceIdentifiers {
     INodeGuard = 'INodeGuard',
     INodeGuard = 'INodeGuard',
     INodeTransformer = 'INodeTransformer',
     INodeTransformer = 'INodeTransformer',
     INodeTransformerNamesGroupsBuilder = 'INodeTransformerNamesGroupsBuilder',
     INodeTransformerNamesGroupsBuilder = 'INodeTransformerNamesGroupsBuilder',
+    INumberNumericalExpressionAnalyzer = 'INumberNumericalExpressionAnalyzer',
     IObfuscationEventEmitter = 'IObfuscationEventEmitter',
     IObfuscationEventEmitter = 'IObfuscationEventEmitter',
     IObfuscatedCode = 'IObfuscatedCode',
     IObfuscatedCode = 'IObfuscatedCode',
     IOptions = 'IOptions',
     IOptions = 'IOptions',

+ 7 - 0
src/container/modules/analyzers/AnalyzersModule.ts

@@ -4,6 +4,7 @@ import { ServiceIdentifiers } from '../../ServiceIdentifiers';
 
 
 import { ICalleeDataExtractor } from '../../../interfaces/analyzers/calls-graph-analyzer/ICalleeDataExtractor';
 import { ICalleeDataExtractor } from '../../../interfaces/analyzers/calls-graph-analyzer/ICalleeDataExtractor';
 import { ICallsGraphAnalyzer } from '../../../interfaces/analyzers/calls-graph-analyzer/ICallsGraphAnalyzer';
 import { ICallsGraphAnalyzer } from '../../../interfaces/analyzers/calls-graph-analyzer/ICallsGraphAnalyzer';
+import { INumberNumericalExpressionAnalyzer } from '../../../interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer';
 import { IPrevailingKindOfVariablesAnalyzer } from '../../../interfaces/analyzers/calls-graph-analyzer/IPrevailingKindOfVariablesAnalyzer';
 import { IPrevailingKindOfVariablesAnalyzer } from '../../../interfaces/analyzers/calls-graph-analyzer/IPrevailingKindOfVariablesAnalyzer';
 import { IScopeAnalyzer } from '../../../interfaces/analyzers/scope-analyzer/IScopeAnalyzer';
 import { IScopeAnalyzer } from '../../../interfaces/analyzers/scope-analyzer/IScopeAnalyzer';
 import { IStringArrayStorageAnalyzer } from '../../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
 import { IStringArrayStorageAnalyzer } from '../../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
@@ -12,6 +13,7 @@ import { CalleeDataExtractor } from '../../../enums/analyzers/calls-graph-analyz
 import { CallsGraphAnalyzer } from '../../../analyzers/calls-graph-analyzer/CallsGraphAnalyzer';
 import { CallsGraphAnalyzer } from '../../../analyzers/calls-graph-analyzer/CallsGraphAnalyzer';
 import { FunctionDeclarationCalleeDataExtractor } from '../../../analyzers/calls-graph-analyzer/callee-data-extractors/FunctionDeclarationCalleeDataExtractor';
 import { FunctionDeclarationCalleeDataExtractor } from '../../../analyzers/calls-graph-analyzer/callee-data-extractors/FunctionDeclarationCalleeDataExtractor';
 import { FunctionExpressionCalleeDataExtractor } from '../../../analyzers/calls-graph-analyzer/callee-data-extractors/FunctionExpressionCalleeDataExtractor';
 import { FunctionExpressionCalleeDataExtractor } from '../../../analyzers/calls-graph-analyzer/callee-data-extractors/FunctionExpressionCalleeDataExtractor';
+import { NumberNumericalExpressionAnalyzer } from '../../../analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer';
 import { ObjectExpressionCalleeDataExtractor } from '../../../analyzers/calls-graph-analyzer/callee-data-extractors/ObjectExpressionCalleeDataExtractor';
 import { ObjectExpressionCalleeDataExtractor } from '../../../analyzers/calls-graph-analyzer/callee-data-extractors/ObjectExpressionCalleeDataExtractor';
 import { PrevailingKindOfVariablesAnalyzer } from '../../../analyzers/prevailing-kind-of-variables-analyzer/PrevailingKindOfVariablesAnalyzer';
 import { PrevailingKindOfVariablesAnalyzer } from '../../../analyzers/prevailing-kind-of-variables-analyzer/PrevailingKindOfVariablesAnalyzer';
 import { ScopeAnalyzer } from '../../../analyzers/scope-analyzer/ScopeAnalyzer';
 import { ScopeAnalyzer } from '../../../analyzers/scope-analyzer/ScopeAnalyzer';
@@ -23,6 +25,11 @@ export const analyzersModule: interfaces.ContainerModule = new ContainerModule((
         .to(CallsGraphAnalyzer)
         .to(CallsGraphAnalyzer)
         .inSingletonScope();
         .inSingletonScope();
 
 
+    // number numerical expression analyzer
+    bind<INumberNumericalExpressionAnalyzer>(ServiceIdentifiers.INumberNumericalExpressionAnalyzer)
+        .to(NumberNumericalExpressionAnalyzer)
+        .inSingletonScope();
+
     // prevailing kind of variables analyzer
     // prevailing kind of variables analyzer
     bind<IPrevailingKindOfVariablesAnalyzer>(ServiceIdentifiers.IPrevailingKindOfVariablesAnalyzer)
     bind<IPrevailingKindOfVariablesAnalyzer>(ServiceIdentifiers.IPrevailingKindOfVariablesAnalyzer)
         .to(PrevailingKindOfVariablesAnalyzer)
         .to(PrevailingKindOfVariablesAnalyzer)

+ 7 - 1
src/container/modules/node-transformers/ConvertingTransformersModule.ts

@@ -8,14 +8,15 @@ import { IObjectExpressionExtractor } from '../../../interfaces/node-transformer
 import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';
 import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';
 import { ObjectExpressionExtractor } from '../../../enums/node-transformers/converting-transformers/properties-extractors/ObjectExpressionExtractor';
 import { ObjectExpressionExtractor } from '../../../enums/node-transformers/converting-transformers/properties-extractors/ObjectExpressionExtractor';
 
 
+import { BasePropertiesExtractor } from '../../../node-transformers/converting-transformers/object-expression-extractors/BasePropertiesExtractor';
 import { ObjectExpressionToVariableDeclarationExtractor } from '../../../node-transformers/converting-transformers/object-expression-extractors/ObjectExpressionToVariableDeclarationExtractor';
 import { ObjectExpressionToVariableDeclarationExtractor } from '../../../node-transformers/converting-transformers/object-expression-extractors/ObjectExpressionToVariableDeclarationExtractor';
 import { MemberExpressionTransformer } from '../../../node-transformers/converting-transformers/MemberExpressionTransformer';
 import { MemberExpressionTransformer } from '../../../node-transformers/converting-transformers/MemberExpressionTransformer';
 import { MethodDefinitionTransformer } from '../../../node-transformers/converting-transformers/MethodDefinitionTransformer';
 import { MethodDefinitionTransformer } from '../../../node-transformers/converting-transformers/MethodDefinitionTransformer';
+import { NumberToNumericalExpressionTransformer } from '../../../node-transformers/converting-transformers/NumberToNumericalExpressionTransformer';
 import { ObjectExpressionKeysTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionKeysTransformer';
 import { ObjectExpressionKeysTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionKeysTransformer';
 import { ObjectExpressionTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionTransformer';
 import { ObjectExpressionTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionTransformer';
 import { SplitStringTransformer } from '../../../node-transformers/converting-transformers/SplitStringTransformer';
 import { SplitStringTransformer } from '../../../node-transformers/converting-transformers/SplitStringTransformer';
 import { TemplateLiteralTransformer } from '../../../node-transformers/converting-transformers/TemplateLiteralTransformer';
 import { TemplateLiteralTransformer } from '../../../node-transformers/converting-transformers/TemplateLiteralTransformer';
-import { BasePropertiesExtractor } from '../../../node-transformers/converting-transformers/object-expression-extractors/BasePropertiesExtractor';
 
 
 export const convertingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
 export const convertingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // converting transformers
     // converting transformers
@@ -27,6 +28,11 @@ export const convertingTransformersModule: interfaces.ContainerModule = new Cont
         .to(MethodDefinitionTransformer)
         .to(MethodDefinitionTransformer)
         .whenTargetNamed(NodeTransformer.MethodDefinitionTransformer);
         .whenTargetNamed(NodeTransformer.MethodDefinitionTransformer);
 
 
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(NumberToNumericalExpressionTransformer)
+        .whenTargetNamed(NodeTransformer.NumberToNumericalExpressionTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(ObjectExpressionKeysTransformer)
         .to(ObjectExpressionKeysTransformer)
         .whenTargetNamed(NodeTransformer.ObjectExpressionKeysTransformer);
         .whenTargetNamed(NodeTransformer.ObjectExpressionKeysTransformer);

+ 2 - 0
src/custom-code-helpers/CustomCodeHelperObfuscator.ts

@@ -47,6 +47,8 @@ export class CustomCodeHelperObfuscator implements ICustomCodeHelperObfuscator {
                 ...NO_ADDITIONAL_NODES_PRESET,
                 ...NO_ADDITIONAL_NODES_PRESET,
                 identifierNamesGenerator: this.options.identifierNamesGenerator,
                 identifierNamesGenerator: this.options.identifierNamesGenerator,
                 identifiersDictionary: this.options.identifiersDictionary,
                 identifiersDictionary: this.options.identifiersDictionary,
+                numbersToExpressions: this.options.numbersToExpressions,
+                simplify: this.options.simplify,
                 seed: this.randomGenerator.getRawSeed(),
                 seed: this.randomGenerator.getRawSeed(),
                 ...additionalOptions
                 ...additionalOptions
             }
             }

+ 1 - 0
src/enums/node-transformers/NodeTransformer.ts

@@ -11,6 +11,7 @@ export enum NodeTransformer {
     MemberExpressionTransformer = 'MemberExpressionTransformer',
     MemberExpressionTransformer = 'MemberExpressionTransformer',
     MetadataTransformer = 'MetadataTransformer',
     MetadataTransformer = 'MetadataTransformer',
     MethodDefinitionTransformer = 'MethodDefinitionTransformer',
     MethodDefinitionTransformer = 'MethodDefinitionTransformer',
+    NumberToNumericalExpressionTransformer = 'NumberToNumericalExpressionTransformer',
     ObfuscatingGuardsTransformer = 'ObfuscatingGuardsTransformer',
     ObfuscatingGuardsTransformer = 'ObfuscatingGuardsTransformer',
     ObjectExpressionKeysTransformer = 'ObjectExpressionKeysTransformer',
     ObjectExpressionKeysTransformer = 'ObjectExpressionKeysTransformer',
     ObjectExpressionTransformer = 'ObjectExpressionTransformer',
     ObjectExpressionTransformer = 'ObjectExpressionTransformer',

+ 9 - 0
src/interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer.ts

@@ -0,0 +1,9 @@
+import { TNumberNumericalExpressionData } from '../../../types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData';
+
+export interface INumberNumericalExpressionAnalyzer {
+    /**
+     * @param {number} number
+     * @returns {TNumberNumericalExpressionData}
+     */
+    analyze (number: number): TNumberNumericalExpressionData;
+}

+ 1 - 0
src/interfaces/options/IOptions.ts

@@ -21,6 +21,7 @@ export interface IOptions {
     readonly identifiersPrefix: string;
     readonly identifiersPrefix: string;
     readonly inputFileName: string;
     readonly inputFileName: string;
     readonly log: boolean;
     readonly log: boolean;
+    readonly numbersToExpressions: boolean;
     readonly renameGlobals: boolean;
     readonly renameGlobals: boolean;
     readonly renameProperties: boolean;
     readonly renameProperties: boolean;
     readonly reservedNames: string[];
     readonly reservedNames: string[];

+ 175 - 0
src/node-transformers/converting-transformers/NumberToNumericalExpressionTransformer.ts

@@ -0,0 +1,175 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { TNumberNumericalExpressionData } from '../../types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData';
+
+import { INumberNumericalExpressionAnalyzer } from '../../interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
+
+import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { NodeGuards } from '../../node/NodeGuards';
+import { NodeFactory } from '../../node/NodeFactory';
+import { NumberUtils } from '../../utils/NumberUtils';
+
+/**
+ * replaces:
+ *     var number = 123;
+ *
+ * on:
+ *     var number = 50 + (100 * 2) - 127;
+ */
+@injectable()
+export class NumberToNumericalExpressionTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {INumberNumericalExpressionAnalyzer}
+     */
+    private readonly numberNumericalExpressionAnalyzer: INumberNumericalExpressionAnalyzer;
+
+    /**
+     * @param {INumberNumericalExpressionAnalyzer} numberNumericalExpressionAnalyzer
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.INumberNumericalExpressionAnalyzer)
+            numberNumericalExpressionAnalyzer: INumberNumericalExpressionAnalyzer,
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+
+        this.numberNumericalExpressionAnalyzer = numberNumericalExpressionAnalyzer;
+    }
+
+    /**
+     * @param {NodeTransformationStage} nodeTransformationStage
+     * @returns {IVisitor | null}
+     */
+    public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
+        if (!this.options.numbersToExpressions) {
+            return null;
+        }
+
+        switch (nodeTransformationStage) {
+            case NodeTransformationStage.Converting:
+                return {
+                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
+                        if (parentNode && NodeGuards.isLiteralNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {Literal} literalNode
+     * @param {NodeGuards} parentNode
+     * @returns {NodeGuards}
+     */
+    public transformNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): ESTree.Node {
+        if (typeof literalNode.value !== 'number') {
+            return literalNode;
+        }
+
+        if (NodeGuards.isPropertyNode(parentNode) && !parentNode.computed) {
+            return literalNode;
+        }
+
+        const numberNumericalExpressionData: TNumberNumericalExpressionData = this.numberNumericalExpressionAnalyzer.analyze(literalNode.value);
+
+        return this.convertNumericalExpressionDataToNode(numberNumericalExpressionData);
+    }
+
+    /**
+     * @param {TNumberNumericalExpressionData} numberNumericalExpressionData
+     * @param {ESTree.BinaryOperator} operator
+     * @returns {ESTree.BinaryExpression | ESTree.Literal | ESTree.UnaryExpression}
+     */
+    private convertNumericalExpressionDataToNode (
+        numberNumericalExpressionData: TNumberNumericalExpressionData,
+        operator: ESTree.BinaryOperator = '+'
+    ): ESTree.BinaryExpression | ESTree.Literal | ESTree.UnaryExpression {
+        const numberNumericalExpressionDataLength: number = numberNumericalExpressionData.length;
+
+        const leftParts: TNumberNumericalExpressionData = numberNumericalExpressionDataLength > 1
+            ? numberNumericalExpressionData.slice(0, numberNumericalExpressionDataLength - 1)
+            : [numberNumericalExpressionData[0]];
+        const rightParts: TNumberNumericalExpressionData = numberNumericalExpressionDataLength > 1
+            ? numberNumericalExpressionData.slice(-1)
+            : [];
+
+        // trailing iterations
+        if (rightParts.length) {
+            return this.convertPartsToBinaryExpression(operator, leftParts, rightParts);
+        }
+
+        const firstLeftPartOrNumber: number | number[] | null = leftParts[0] ?? null;
+
+        // last iteration when only single left part is left
+        return Array.isArray(firstLeftPartOrNumber)
+            ? this.convertNumericalExpressionDataToNode(firstLeftPartOrNumber, '*')
+            : this.convertPartOrNumberToLiteralNode(firstLeftPartOrNumber);
+    }
+
+    /**
+     * @param {ESTree.BinaryOperator} operator
+     * @param {TNumberNumericalExpressionData} leftParts
+     * @param {TNumberNumericalExpressionData} rightParts
+     * @returns {ESTree.BinaryExpression}
+     */
+    private convertPartsToBinaryExpression (
+        operator: ESTree.BinaryOperator,
+        leftParts: TNumberNumericalExpressionData,
+        rightParts: TNumberNumericalExpressionData
+    ): ESTree.BinaryExpression {
+        const rightPartOrNumber: number | number[] = rightParts[0];
+
+        if (Array.isArray(rightPartOrNumber)) {
+            // right part is array with multiply numbers
+            return NodeFactory.binaryExpressionNode(
+                operator,
+                this.convertNumericalExpressionDataToNode(leftParts),
+                this.convertNumericalExpressionDataToNode(rightPartOrNumber, '*')
+            );
+        } else {
+            // right part is number
+            return NodeFactory.binaryExpressionNode(
+                operator,
+                this.convertNumericalExpressionDataToNode(leftParts),
+                this.convertPartOrNumberToLiteralNode(rightPartOrNumber)
+            );
+        }
+    }
+
+    /**
+     * @param {number | number[]} partOrNumber
+     * @returns {ESTree.Literal}
+     */
+    private convertPartOrNumberToLiteralNode (
+        partOrNumber: number | number[]
+    ): ESTree.Literal | ESTree.UnaryExpression {
+        const number: number = Array.isArray(partOrNumber)
+            ? partOrNumber[0]
+            : partOrNumber;
+        const isPositiveNumber: boolean = NumberUtils.isPositive(number);
+
+        const literalNode: ESTree.Literal = NodeFactory.literalNode(Math.abs(number));
+
+        return isPositiveNumber
+            ? literalNode
+            : NodeFactory.unaryExpressionNode(
+                '-',
+                literalNode
+            );
+    }
+}

+ 6 - 0
src/options/Options.ts

@@ -151,6 +151,12 @@ export class Options implements IOptions {
     @IsBoolean()
     @IsBoolean()
     public readonly log!: boolean;
     public readonly log!: boolean;
 
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly numbersToExpressions!: boolean;
+
     /**
     /**
      * @type {boolean}
      * @type {boolean}
      */
      */

+ 1 - 0
src/options/presets/Default.ts

@@ -21,6 +21,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     identifiersDictionary: [],
     identifiersDictionary: [],
     inputFileName: '',
     inputFileName: '',
     log: false,
     log: false,
+    numbersToExpressions: false,
     renameGlobals: false,
     renameGlobals: false,
     renameProperties: false,
     renameProperties: false,
     reservedNames: [],
     reservedNames: [],

+ 1 - 0
src/options/presets/NoCustomNodes.ts

@@ -20,6 +20,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     identifiersDictionary: [],
     identifiersDictionary: [],
     inputFileName: '',
     inputFileName: '',
     log: false,
     log: false,
+    numbersToExpressions: false,
     renameGlobals: false,
     renameGlobals: false,
     renameProperties: false,
     renameProperties: false,
     reservedNames: [],
     reservedNames: [],

+ 1 - 0
src/types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData.ts

@@ -0,0 +1 @@
+export type TNumberNumericalExpressionData = (number | number[])[];

+ 82 - 0
src/utils/NumberUtils.ts

@@ -24,4 +24,86 @@ export class NumberUtils {
             ? number % 1 === 0
             ? number % 1 === 0
             : true;
             : true;
     }
     }
+
+    /**
+     * @param {number} number
+     * @returns {boolean}
+     */
+    public static isPositive (number: number): boolean {
+        if (isNaN(number)) {
+            throw new Error('Given value is NaN');
+        }
+
+        if (number > 0) {
+            return true;
+        }
+
+        if (number < 0) {
+            return false;
+        }
+
+        if (1 / number === Number.POSITIVE_INFINITY) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @param {number} number
+     * @returns {boolean}
+     */
+    public static isUnsafeNumber (number: number): boolean {
+        if (isNaN(number)) {
+            throw new Error('Given value is NaN');
+        }
+
+        return number < Number.MIN_SAFE_INTEGER || number > Number.MAX_SAFE_INTEGER;
+    }
+
+    /**
+     * Returns all factors of a number
+     * Based on https://stackoverflow.com/a/43204663
+     *
+     * @param {number} number
+     * @returns {number[]}
+     */
+    public static getFactors (number: number): number[] {
+        if (number === 0) {
+            throw new Error('Invalid number. Allowed only non-zero number');
+        }
+
+        number = Math.abs(number);
+
+        // special case for 1
+        if (number === 1) {
+            return [-number, number];
+        }
+
+        const factors: number[] = [];
+
+        const root: number = Math.sqrt(number);
+        const isEven: boolean = number % 2 === 0;
+        const incrementValue: number = isEven ? 1 : 2;
+
+        for (
+            let currentFactor = 1;
+            currentFactor <= root;
+            currentFactor += incrementValue
+        ) {
+            if (number % currentFactor !== 0) {
+                continue;
+            }
+
+            factors.push(...[-currentFactor, currentFactor]);
+
+            const compliment: number = number / currentFactor;
+
+            if (compliment !== currentFactor) {
+                factors.push(...[-compliment, compliment]);
+            }
+        }
+
+        return factors.sort((a: number, b: number) => a - b);
+    }
 }
 }

+ 8 - 5
test/dev/dev.ts

@@ -1,6 +1,5 @@
 'use strict';
 'use strict';
 
 
-import { IdentifierNamesGenerator } from '../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
 
 
 (function () {
 (function () {
@@ -8,13 +7,17 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
         `
-            function foo (arg1, arg2, arg3) {
-                console.log(arg1, arg2, arg3);
-            }
+            const foo = 0;
+            const bar = 'abc';
+            
+            console.log(foo, bar);
         `,
         `,
         {
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             ...NO_ADDITIONAL_NODES_PRESET,
-            identifierNamesGenerator: IdentifierNamesGenerator.MangledShuffledIdentifierNamesGenerator
+            compact: false,
+            numbersToExpressions: true,
+            stringArray: true,
+            stringArrayThreshold: 1
         }
         }
     ).getObfuscatedCode();
     ).getObfuscatedCode();
 
 

+ 1 - 0
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -810,6 +810,7 @@ describe('JavaScriptObfuscator', () => {
                         deadCodeInjection: true,
                         deadCodeInjection: true,
                         deadCodeInjectionThreshold: 1,
                         deadCodeInjectionThreshold: 1,
                         disableConsoleOutput: false,
                         disableConsoleOutput: false,
+                        numbersToExpressions: true,
                         simplify: true,
                         simplify: true,
                         renameProperties: true,
                         renameProperties: true,
                         rotateStringArray: true,
                         rotateStringArray: true,

+ 167 - 0
test/functional-tests/node-transformers/converting-transformers/numbers-to-numerical-expressions-transformer/NumbersToNumericalExpressionsTransformer.spec.ts

@@ -0,0 +1,167 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+describe('NumbersToNumericalExpressionsTransformer', function () {
+    this.timeout(60000);
+
+    describe('Variant #1: base', () => {
+        const initialNumber: number = -50;
+        const lastNumber: number = 50;
+
+        let areValidExpressions: boolean = true;
+
+        before(() => {
+            for (let i = initialNumber; i < lastNumber; i++) {
+                const number: number = i;
+                const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                    `${number};`,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        numbersToExpressions: true
+                    }
+                ).getObfuscatedCode();
+
+                const result: number = eval(obfuscatedCode);
+
+                if (result !== number) {
+                    areValidExpressions = false;
+                    break;
+                }
+            }
+        });
+
+        it('should correctly transform numbers to expressions', () => {
+            assert.isTrue(areValidExpressions);
+        });
+    });
+
+    describe('Variant #2: safe integers', () => {
+        describe('Variant #1: max safe integer', () => {
+            const number: number = Number.MAX_SAFE_INTEGER;
+            const samplesCount: number = 15;
+
+            let areValidExpressions: boolean = true;
+
+            before(() => {
+                for (let i = 0; i < samplesCount; i++) {
+                    const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                        `${number};`,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            numbersToExpressions: true
+                        }
+                    ).getObfuscatedCode();
+
+                    const result: number = eval(obfuscatedCode);
+
+                    if (result !== number) {
+                        areValidExpressions = false;
+                        break;
+                    }
+                }
+            });
+
+            it('should correctly transform numbers to expressions', () => {
+                assert.isTrue(areValidExpressions);
+            });
+        });
+
+        describe('Variant #2: max unsafe integer', () => {
+            const unsafeIntegerRegExp: RegExp = /0x20000000000000;/;
+            const number: number = Number.MAX_SAFE_INTEGER + 1;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    `${number};`,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        numbersToExpressions: true
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should not transform unsafe integer to expressions', () => {
+                assert.match(obfuscatedCode, unsafeIntegerRegExp);
+            });
+        });
+
+        describe('Variant #3: min safe integer', () => {
+            const number: number = Number.MIN_SAFE_INTEGER;
+            const samplesCount: number = 15;
+
+            let areValidExpressions: boolean = true;
+
+            before(() => {
+                for (let i = 0; i < samplesCount; i++) {
+                    const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                        `${number};`,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            numbersToExpressions: true
+                        }
+                    ).getObfuscatedCode();
+
+                    const result: number = eval(obfuscatedCode);
+
+                    if (result !== number) {
+                        areValidExpressions = false;
+                        break;
+                    }
+                }
+            });
+
+            it('should correctly transform numbers to expressions', () => {
+                assert.isTrue(areValidExpressions);
+            });
+        });
+
+        describe('Variant #4: min unsafe integer', () => {
+            const unsafeIntegerRegExp: RegExp = /-0x20000000000000;/;
+            const number: number = Number.MIN_SAFE_INTEGER - 1;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    `${number};`,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        numbersToExpressions: true
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should not transform unsafe integer to expressions', () => {
+                assert.match(obfuscatedCode, unsafeIntegerRegExp);
+            });
+        });
+    });
+
+    describe('Variant #3: parent node is non-computed object property', () => {
+        const regExp: RegExp = /const foo *= *{1: *'bar'};/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/non-computed-object-key.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    numbersToExpressions: true
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should not replace non-computed object property literal with expression', () => {
+            assert.match(obfuscatedCode,  regExp);
+        });
+    });
+});

+ 3 - 0
test/functional-tests/node-transformers/converting-transformers/numbers-to-numerical-expressions-transformer/fixtures/non-computed-object-key.js

@@ -0,0 +1,3 @@
+const foo = {
+    1: 'bar'
+};

+ 2 - 0
test/index.spec.ts

@@ -6,6 +6,7 @@ require('source-map-support').install();
  * Unit tests
  * Unit tests
  */
  */
 import './unit-tests/analyzers/calls-graph-analyzer/CallsGraphAnalyzer.spec';
 import './unit-tests/analyzers/calls-graph-analyzer/CallsGraphAnalyzer.spec';
+import './unit-tests/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.spec';
 import './unit-tests/analyzers/prevailing-kind-of-variables-analyzer/PrevailingKindOfVariablesAnalyzer.spec';
 import './unit-tests/analyzers/prevailing-kind-of-variables-analyzer/PrevailingKindOfVariablesAnalyzer.spec';
 import './unit-tests/analyzers/scope-analyzer/ScopeAnalyzer.spec';
 import './unit-tests/analyzers/scope-analyzer/ScopeAnalyzer.spec';
 import './unit-tests/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.spec';
 import './unit-tests/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.spec';
@@ -87,6 +88,7 @@ import './functional-tests/node-transformers/control-flow-transformers/control-f
 import './functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec';
 import './functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec';
 import './functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec';
+import './functional-tests/node-transformers/converting-transformers/numbers-to-numerical-expressions-transformer/NumbersToNumericalExpressionsTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/ObjectExpressionKeysTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/ObjectExpressionKeysTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/object-expression-transformer/ObjectExpressionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/object-expression-transformer/ObjectExpressionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/split-string-transformer/SplitStringTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/split-string-transformer/SplitStringTransformer.spec';

+ 1 - 0
test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts

@@ -25,6 +25,7 @@ describe('JavaScriptObfuscator runtime eval', function () {
         debugProtection: true,
         debugProtection: true,
         disableConsoleOutput: true,
         disableConsoleOutput: true,
         domainLock: ['obfuscator.io'],
         domainLock: ['obfuscator.io'],
+        numbersToExpressions: true,
         simplify: true,
         simplify: true,
         renameProperties: true,
         renameProperties: true,
         reservedNames: ['generate', 'sha256'],
         reservedNames: ['generate', 'sha256'],

+ 178 - 0
test/unit-tests/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.spec.ts

@@ -0,0 +1,178 @@
+import 'reflect-metadata';
+
+import { assert } from 'chai';
+
+import { ServiceIdentifiers } from '../../../../src/container/ServiceIdentifiers';
+
+import { TNumberNumericalExpressionData } from '../../../../src/types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData';
+
+import { IInversifyContainerFacade } from '../../../../src/interfaces/container/IInversifyContainerFacade';
+import { INumberNumericalExpressionAnalyzer } from '../../../../src/interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer';
+
+import { InversifyContainerFacade } from '../../../../src/container/InversifyContainerFacade';
+
+/**
+ * @param {TNumberNumericalExpressionData} data
+ * @returns {string}
+ */
+const numberNumericalExpressionDataToString = (data: TNumberNumericalExpressionData) =>
+    data
+        .map((part: number | number[]) => Array.isArray(part) ? part.join('*') : part)
+        .join('+')
+        .replace(/\+-/g, '-');
+
+describe('NumberNumericalExpressionAnalyzer', function() {
+    let numberNumericalExpressionAnalyzer: INumberNumericalExpressionAnalyzer;
+
+    this.timeout(10000);
+
+    before(() => {
+        const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
+
+        inversifyContainerFacade.load('', '', {});
+        numberNumericalExpressionAnalyzer = inversifyContainerFacade
+            .get<INumberNumericalExpressionAnalyzer>(ServiceIdentifiers.INumberNumericalExpressionAnalyzer);
+    });
+
+    describe('analyze', () => {
+        let evaluatedResult: number;
+
+        describe('Positive numbers', () => {
+            describe('Variant #1: positive number', () => {
+                const number: number = 1234;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
+            });
+
+            describe('Variant #2: positive zero number', () => {
+                const number: number = 0;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
+            });
+
+            describe('Variant #3: positive big number', () => {
+                const number: number = Number.MAX_SAFE_INTEGER;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
+            });
+
+            describe('Variant #4: positive unsafe big number', () => {
+                const number: number = Number.MAX_SAFE_INTEGER + 1;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
+            });
+        });
+
+        describe('Negative numbers', () => {
+            describe('Variant #1: negative number', () => {
+                const number: number = -1234;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
+            });
+
+            describe('Variant #2: negative zero number', () => {
+                const number: number = -0;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
+            });
+
+            describe('Variant #3: negative big number', () => {
+                const number: number = Number.MIN_SAFE_INTEGER;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
+            });
+
+            describe('Variant #4: negative unsafe number', () => {
+                const number: number = Number.MIN_SAFE_INTEGER - 1;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
+            });
+        });
+
+        describe('NaN', () => {
+            const number: number = NaN;
+
+            let testFunc: () => void;
+
+            before(() => {
+                testFunc = () => numberNumericalExpressionAnalyzer.analyze(number);
+            });
+
+            it('should throw error', () => {
+                assert.throw(testFunc, 'Given value is NaN');
+            });
+        });
+    });
+});

+ 417 - 1
test/unit-tests/utils/NumberUtils.spec.ts

@@ -2,7 +2,9 @@ import { assert } from 'chai';
 
 
 import { NumberUtils } from '../../../src/utils/NumberUtils';
 import { NumberUtils } from '../../../src/utils/NumberUtils';
 
 
-describe('NumberUtils', () => {
+describe('NumberUtils', function () {
+    this.timeout(30000);
+
     describe('toHex', () => {
     describe('toHex', () => {
         describe('Variant #1: number `0`', () => {
         describe('Variant #1: number `0`', () => {
             const number: number = 0;
             const number: number = 0;
@@ -128,4 +130,418 @@ describe('NumberUtils', () => {
             });
             });
         });
         });
     });
     });
+
+    describe('isPositive', () => {
+        describe('Variant #1: positive integer', () => {
+            const number: number = 2;
+            const expectedResult: boolean = true;
+
+            let result: boolean;
+
+            before(() => {
+                result = NumberUtils.isPositive(number);
+            });
+
+            it('should return true', () => {
+                assert.equal(result, expectedResult);
+            });
+        });
+
+        describe('Variant #2: positive zero', () => {
+            const number: number = 0;
+            const expectedResult: boolean = true;
+
+            let result: boolean;
+
+            before(() => {
+                result = NumberUtils.isPositive(number);
+            });
+
+            it('should return true', () => {
+                assert.equal(result, expectedResult);
+            });
+        });
+
+        describe('Variant #3: negative integer', () => {
+            const number: number = -2;
+            const expectedResult: boolean = false;
+
+            let result: boolean;
+
+            before(() => {
+                result = NumberUtils.isPositive(number);
+            });
+
+            it('should return false', () => {
+                assert.equal(result, expectedResult);
+            });
+        });
+
+        describe('Variant #4: negative zero', () => {
+            const number: number = -0;
+            const expectedResult: boolean = false;
+
+            let result: boolean;
+
+            before(() => {
+                result = NumberUtils.isPositive(number);
+            });
+
+            it('should return false', () => {
+                assert.equal(result, expectedResult);
+            });
+        });
+
+        describe('Variant #5: NaN', () => {
+            const number: number = NaN;
+
+            let testFunc: () => void;
+
+            before(() => {
+                testFunc = () => NumberUtils.isPositive(number);
+            });
+
+            it('should throw an error', () => {
+                assert.throw(testFunc, 'Given value is NaN');
+            });
+        });
+    });
+
+    describe('isUnsafeNumber', () => {
+        describe('Positive number', () => {
+            describe('Variant #1: positive small safe integer', () => {
+                const number: number = 100;
+                const expectedResult: boolean = false
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return false', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #2: positive big safe integer', () => {
+                const number: number = Number.MAX_SAFE_INTEGER;
+                const expectedResult: boolean = false
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return false', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #3: positive unsafe integer', () => {
+                const number: number = Number.MAX_SAFE_INTEGER + 1;
+                const expectedResult: boolean = true
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return true', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+
+        describe('Negative number', () => {
+            describe('Variant #1: negative small safe integer', () => {
+                const number: number = -100;
+                const expectedResult: boolean = false
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return false', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #2: negative big safe integer', () => {
+                const number: number = Number.MIN_SAFE_INTEGER;
+                const expectedResult: boolean = false
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return false', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #3: negative unsafe integer', () => {
+                const number: number = Number.MIN_SAFE_INTEGER - 1;
+                const expectedResult: boolean = true
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return true', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+
+        describe('NaN', () => {
+            const number: number = NaN;
+
+            let testFunc: () => void;
+
+            before(() => {
+                testFunc = () => NumberUtils.isUnsafeNumber(number);
+            });
+
+            it('should throw an error', () => {
+                assert.throw(testFunc, 'Given value is NaN');
+            });
+        });
+    });
+
+    describe('getFactors', () => {
+        describe('Positive numbers', () => {
+            describe('Variant #1: positive number `1`', () => {
+                const number: number = 1;
+                const expectedFactors: number[] = [
+                    -1,
+                    1
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #2: positive number `2`', () => {
+                const number: number = 2;
+                const expectedFactors: number[] = [
+                    -2,
+                    -1,
+                    1,
+                    2
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #3: positive number `100`', () => {
+                const number: number = 100;
+                const expectedFactors: number[] = [
+                    -100,
+                    -50,
+                    -25,
+                    -20,
+                    -10,
+                    -5,
+                    -4,
+                    -2,
+                    -1,
+                    1,
+                    2,
+                    4,
+                    5,
+                    10,
+                    20,
+                    25,
+                    50,
+                    100
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #4: positive number `9007199254740991`', () => {
+                const number: number = 9007199254740991;
+                const expectedFactors: number[] = [
+                    Number.MIN_SAFE_INTEGER,
+                    -1416003655831,
+                    -129728784761,
+                    -441650591,
+                    -20394401,
+                    -69431,
+                    -6361,
+                    -1,
+                    1,
+                    6361,
+                    69431,
+                    20394401,
+                    441650591,
+                    129728784761,
+                    1416003655831,
+                    Number.MAX_SAFE_INTEGER
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+        })
+
+        describe('Negative numbers', () => {
+            describe('Variant #1: negative number `-1`', () => {
+                const number: number = -1;
+                const expectedFactors: number[] = [
+                    -1,
+                    1
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #2: negative number `-2`', () => {
+                const number: number = -2;
+                const expectedFactors: number[] = [
+                    -2,
+                    -1,
+                    1,
+                    2
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #3: negative number `-100`', () => {
+                const number: number = -100;
+                const expectedFactors: number[] = [
+                    -100,
+                    -50,
+                    -25,
+                    -20,
+                    -10,
+                    -5,
+                    -4,
+                    -2,
+                    -1,
+                    1,
+                    2,
+                    4,
+                    5,
+                    10,
+                    20,
+                    25,
+                    50,
+                    100
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #4: negative number `-9007199254740991`', () => {
+                const number: number = -9007199254740991;
+                const expectedFactors: number[] = [
+                    Number.MIN_SAFE_INTEGER,
+                    -1416003655831,
+                    -129728784761,
+                    -441650591,
+                    -20394401,
+                    -69431,
+                    -6361,
+                    -1,
+                    1,
+                    6361,
+                    69431,
+                    20394401,
+                    441650591,
+                    129728784761,
+                    1416003655831,
+                    Number.MAX_SAFE_INTEGER
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+        })
+
+        describe('zero number', () => {
+            const number: number = 0;
+
+            let testFunc: () => void;
+
+            before(() => {
+                testFunc = () => NumberUtils.getFactors(number);
+            });
+
+            it('should throw an error', () => {
+                assert.throw(testFunc, Error);
+            });
+        })
+    });
 });
 });

+ 12 - 12
yarn.lock

@@ -369,10 +369,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.3.tgz#6356df2647de9eac569f9a52eda3480fa9e70b4d"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.3.tgz#6356df2647de9eac569f9a52eda3480fa9e70b4d"
   integrity sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA==
   integrity sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA==
 
 
-"@types/[email protected]0":
-  version "14.0.20"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.20.tgz#0da05cddbc761e1fa98af88a17244c8c1ff37231"
-  integrity sha512-MRn/NP3dee8yL5QhbSA6riuwkS+UOcsPUMOIOG3KMUQpuor/2TopdRBu8QaaB4fGU+gz/bzyDWt0FtUbeJ8H1A==
+"@types/[email protected]2":
+  version "14.0.22"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.22.tgz#23ea4d88189cec7d58f9e6b66f786b215eb61bdc"
+  integrity sha512-emeGcJvdiZ4Z3ohbmw93E/64jRzUHAItSHt8nF7M4TGgQTiWqFVGB8KNpLGFmUHmHLvjvBgFwVlqNcq+VuGv9g==
 
 
 "@types/normalize-package-data@^2.4.0":
 "@types/normalize-package-data@^2.4.0":
   version "2.4.0"
   version "2.4.0"
@@ -1923,10 +1923,10 @@ [email protected]:
     resolve "^1.17.0"
     resolve "^1.17.0"
     tsconfig-paths "^3.9.0"
     tsconfig-paths "^3.9.0"
 
 
[email protected].2:
-  version "29.1.2"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-29.1.2.tgz#542beba99946cb8d184e5261ebc632b24673f825"
-  integrity sha512-s1uJcPcjZQ4Z5i98T3zWkp/gzJ9AtHGXXg0zxd0wCnFzX8RQU6awdUokUlBFMoWZJZxdCAXDtEIQBRfr/Lrsjw==
[email protected].3:
+  version "29.1.3"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-29.1.3.tgz#288c3faf142fbf78e6abff6a838db23af8d6749f"
+  integrity sha512-HEB8jPsWBGu++LffL4K8VZ7VXz0HELI1I3Qkv/+5oSekgrAo6I0AVgl5abecLbTQQZo0OaEcmTptiIspwDOu1w==
   dependencies:
   dependencies:
     comment-parser "^0.7.5"
     comment-parser "^0.7.5"
     debug "^4.1.1"
     debug "^4.1.1"
@@ -5432,10 +5432,10 @@ [email protected]:
     v8-compile-cache "^2.1.1"
     v8-compile-cache "^2.1.1"
     yargs "^13.3.2"
     yargs "^13.3.2"
 
 
-webpack-node-externals@2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-2.3.0.tgz#c884c07103de080284e17c195fe87c68e2b9c668"
-  integrity sha512-d1scCn/L5hv73GMOlqSTO6ykLWOiUrZfn54xQYf7u0yGLlUSf5trq6HV/Gw8JIpH2NEyXS7bJec1gk9YR/Qdqw==
+webpack-node-externals@2.5.0:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-2.5.0.tgz#8d50f3289c71bc2b921a8da228e0b652acc503f1"
+  integrity sha512-g7/Z7Q/gsP8GkJkKZuJggn6RSb5PvxW1YD5vvmRZIxaSxAzkqjfL5n9CslVmNYlSqBVCyiqFgOqVS2IOObCSRg==
 
 
 webpack-sources@^1.4.0, webpack-sources@^1.4.1:
 webpack-sources@^1.4.0, webpack-sources@^1.4.1:
   version "1.4.3"
   version "1.4.3"

Some files were not shown because too many files changed in this diff