js-confuser 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +215 -170
  3. package/dist/constants.js +6 -2
  4. package/dist/obfuscator.js +0 -6
  5. package/dist/options.js +4 -4
  6. package/dist/presets.js +6 -7
  7. package/dist/templates/crash.js +2 -2
  8. package/dist/templates/functionLength.js +16 -0
  9. package/dist/transforms/dispatcher.js +4 -1
  10. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +89 -58
  11. package/dist/transforms/flatten.js +224 -147
  12. package/dist/transforms/identifier/movedDeclarations.js +38 -85
  13. package/dist/transforms/identifier/renameVariables.js +94 -41
  14. package/dist/transforms/lock/lock.js +0 -37
  15. package/dist/transforms/minify.js +2 -2
  16. package/dist/transforms/rgf.js +139 -246
  17. package/dist/transforms/stack.js +42 -1
  18. package/dist/transforms/transform.js +1 -1
  19. package/dist/util/gen.js +2 -1
  20. package/dist/util/identifiers.js +37 -3
  21. package/dist/util/insert.js +24 -3
  22. package/docs/ControlFlowFlattening.md +595 -0
  23. package/{Countermeasures.md → docs/Countermeasures.md} +1 -15
  24. package/{Integrity.md → docs/Integrity.md} +2 -2
  25. package/docs/RGF.md +419 -0
  26. package/package.json +1 -1
  27. package/src/constants.ts +3 -0
  28. package/src/obfuscator.ts +0 -4
  29. package/src/options.ts +9 -86
  30. package/src/presets.ts +6 -7
  31. package/src/templates/crash.ts +10 -10
  32. package/src/templates/functionLength.ts +14 -0
  33. package/src/transforms/dispatcher.ts +5 -1
  34. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +130 -129
  35. package/src/transforms/flatten.ts +357 -290
  36. package/src/transforms/identifier/movedDeclarations.ts +50 -96
  37. package/src/transforms/identifier/renameVariables.ts +120 -56
  38. package/src/transforms/lock/lock.ts +1 -42
  39. package/src/transforms/minify.ts +11 -2
  40. package/src/transforms/rgf.ts +214 -404
  41. package/src/transforms/stack.ts +62 -0
  42. package/src/transforms/transform.ts +6 -2
  43. package/src/util/gen.ts +7 -2
  44. package/src/util/identifiers.ts +43 -2
  45. package/src/util/insert.ts +26 -2
  46. package/test/code/ES6.src.js +24 -0
  47. package/test/transforms/flatten.test.ts +352 -88
  48. package/test/transforms/identifier/movedDeclarations.test.ts +37 -9
  49. package/test/transforms/identifier/renameVariables.test.ts +37 -0
  50. package/test/transforms/lock/lock.test.ts +1 -48
  51. package/test/transforms/minify.test.ts +19 -0
  52. package/test/transforms/rgf.test.ts +262 -353
  53. package/test/transforms/stack.test.ts +52 -0
  54. package/test/util/identifiers.test.ts +113 -1
  55. package/test/util/insert.test.ts +57 -3
  56. package/src/transforms/eval.ts +0 -89
  57. package/src/transforms/identifier/nameRecycling.ts +0 -280
  58. package/test/transforms/eval.test.ts +0 -131
  59. package/test/transforms/identifier/nameRecycling.test.ts +0 -205
@@ -519,3 +519,55 @@ test("Variant #16: Function with 'this'", async () => {
519
519
 
520
520
  expect(TEST_OUTPUT).toStrictEqual(true);
521
521
  });
522
+
523
+ // https://github.com/MichaelXF/js-confuser/issues/88
524
+ test("Variant #17: Syncing arguments parameter", async () => {
525
+ var output = await JsConfuser(
526
+ `
527
+ var TEST_OUTPUT;
528
+
529
+ function syncingArguments(parameter) {
530
+ arguments[0] = "Correct Value";
531
+ TEST_OUTPUT = parameter;
532
+ }
533
+
534
+ syncingArguments("Incorrect Value");
535
+ `,
536
+ { target: "node", stack: true }
537
+ );
538
+
539
+ function evalNoStrictMode(evalCode) {
540
+ var fn = new Function(evalCode + ";return TEST_OUTPUT");
541
+ return fn();
542
+ }
543
+
544
+ var TEST_OUTPUT = evalNoStrictMode(output);
545
+
546
+ expect(TEST_OUTPUT).toStrictEqual("Correct Value");
547
+ });
548
+
549
+ test("Variant #18: Preserve function.length property", async () => {
550
+ var output = await JsConfuser(
551
+ `
552
+ function oneParameter(a){
553
+ var _ = 1;
554
+ };
555
+ var twoParameters = function(a,b){
556
+ var _ = 1;
557
+ };
558
+ var myObject = {
559
+ threeParameters(a,b,c){
560
+ var _ = 1;
561
+ }
562
+ }
563
+
564
+ TEST_OUTPUT = oneParameter.length + twoParameters.length + myObject.threeParameters.length;
565
+ `,
566
+ { target: "node", stack: true }
567
+ );
568
+
569
+ var TEST_OUTPUT;
570
+ eval(output);
571
+
572
+ expect(TEST_OUTPUT).toStrictEqual(6);
573
+ });
@@ -1,4 +1,7 @@
1
- import parseJS from "../../src/parser";
1
+ import { ok } from "assert";
2
+ import parseJS, { parseSync } from "../../src/parser";
3
+ import traverse from "../../src/traverse";
4
+ import { Location, Node } from "../../src/util/gen";
2
5
  import {
3
6
  getFunctionParameters,
4
7
  getIdentifierInfo,
@@ -41,6 +44,115 @@ describe("getIdentifierInfo", () => {
41
44
  getIdentifierInfo({ type: "Literal", value: true }, []);
42
45
  }).toThrow();
43
46
  });
47
+
48
+ function findIdentifier(tree: Node, identifierName: string) {
49
+ var searchLocation: Location;
50
+
51
+ traverse(tree, (o, p) => {
52
+ if (o.type === "Identifier" && o.name === identifierName) {
53
+ ok(!searchLocation);
54
+ searchLocation = [o, p];
55
+ }
56
+ });
57
+
58
+ ok(searchLocation);
59
+ return searchLocation;
60
+ }
61
+
62
+ test("Variant #4: Variable declaration assignment pattern", async () => {
63
+ var tree = parseSync(`
64
+ var [ definedIdentifier = nonDefinedIdentifier ] = [];
65
+ `);
66
+
67
+ var definedIdentifier = findIdentifier(tree, "definedIdentifier");
68
+ var definedInfo = getIdentifierInfo(
69
+ definedIdentifier[0],
70
+ definedIdentifier[1]
71
+ );
72
+ expect(definedInfo.spec.isDefined).toStrictEqual(true);
73
+ expect(definedInfo.spec.isReferenced).toStrictEqual(true);
74
+
75
+ var nonDefinedIdentifier = findIdentifier(tree, "nonDefinedIdentifier");
76
+ var nonDefinedInfo = getIdentifierInfo(
77
+ nonDefinedIdentifier[0],
78
+ nonDefinedIdentifier[1]
79
+ );
80
+ expect(nonDefinedInfo.spec.isDefined).toStrictEqual(false);
81
+ expect(nonDefinedInfo.spec.isReferenced).toStrictEqual(true);
82
+ });
83
+
84
+ test("Variant #5: Function parameter assignment pattern", async () => {
85
+ var tree = parseSync(`
86
+ function myFunction(definedIdentifier = nonDefinedIdentifier) {
87
+
88
+ }
89
+ `);
90
+
91
+ var myFunction = findIdentifier(tree, "myFunction");
92
+ var myFunctionInfo = getIdentifierInfo(myFunction[0], myFunction[1]);
93
+
94
+ expect(myFunctionInfo.isFunctionDeclaration).toStrictEqual(true);
95
+ expect(myFunctionInfo.spec.isDefined).toStrictEqual(true);
96
+
97
+ var definedIdentifier = findIdentifier(tree, "definedIdentifier");
98
+ var definedInfo = getIdentifierInfo(
99
+ definedIdentifier[0],
100
+ definedIdentifier[1]
101
+ );
102
+ expect(definedInfo.spec.isDefined).toStrictEqual(true);
103
+ expect(definedInfo.spec.isReferenced).toStrictEqual(true);
104
+
105
+ var nonDefinedIdentifier = findIdentifier(tree, "nonDefinedIdentifier");
106
+ var nonDefinedInfo = getIdentifierInfo(
107
+ nonDefinedIdentifier[0],
108
+ nonDefinedIdentifier[1]
109
+ );
110
+ expect(nonDefinedInfo.spec.isDefined).toStrictEqual(false);
111
+ expect(nonDefinedInfo.spec.isReferenced).toStrictEqual(true);
112
+ });
113
+
114
+ test("Variant #6: Object pattern", async () => {
115
+ var tree = parseSync(`
116
+ var { nonDefinedIdentifier: definedIdentifier } = {};
117
+
118
+ ( { nonModifiedIdentifier: modifiedIdentifier } = {} );
119
+ `);
120
+
121
+ var definedIdentifier = findIdentifier(tree, "definedIdentifier");
122
+ var definedInfo = getIdentifierInfo(
123
+ definedIdentifier[0],
124
+ definedIdentifier[1]
125
+ );
126
+ expect(definedInfo.spec.isDefined).toStrictEqual(true);
127
+ expect(definedInfo.spec.isReferenced).toStrictEqual(true);
128
+
129
+ var nonDefinedIdentifier = findIdentifier(tree, "nonDefinedIdentifier");
130
+ var nonDefinedInfo = getIdentifierInfo(
131
+ nonDefinedIdentifier[0],
132
+ nonDefinedIdentifier[1]
133
+ );
134
+ expect(nonDefinedInfo.spec.isDefined).toStrictEqual(false);
135
+ expect(nonDefinedInfo.spec.isReferenced).toStrictEqual(false);
136
+
137
+ var modifiedIdentifier = findIdentifier(tree, "modifiedIdentifier");
138
+ var modifiedInfo = getIdentifierInfo(
139
+ modifiedIdentifier[0],
140
+ modifiedIdentifier[1]
141
+ );
142
+ expect(modifiedInfo.spec.isDefined).toStrictEqual(false);
143
+ expect(modifiedInfo.spec.isModified).toStrictEqual(true);
144
+ expect(modifiedInfo.spec.isReferenced).toStrictEqual(true);
145
+
146
+ var nonModifiedIdentifier = findIdentifier(tree, "nonModifiedIdentifier");
147
+ var nonModifiedInfo = getIdentifierInfo(
148
+ nonModifiedIdentifier[0],
149
+ nonModifiedIdentifier[1]
150
+ );
151
+
152
+ expect(nonModifiedInfo.spec.isDefined).toStrictEqual(false);
153
+ expect(nonModifiedInfo.spec.isModified).toStrictEqual(false);
154
+ expect(nonModifiedInfo.spec.isReferenced).toStrictEqual(false);
155
+ });
44
156
  });
45
157
 
46
158
  describe("validateChain", () => {
@@ -1,7 +1,8 @@
1
+ import { ok } from "assert";
1
2
  import { compileJsSync } from "../../src/compiler";
2
- import parseJS from "../../src/parser";
3
- import { isBlock } from "../../src/traverse";
4
- import { Identifier } from "../../src/util/gen";
3
+ import parseJS, { parseSync } from "../../src/parser";
4
+ import traverse, { isBlock } from "../../src/traverse";
5
+ import { Identifier, Location } from "../../src/util/gen";
5
6
  import {
6
7
  deleteDeclaration,
7
8
  isVarContext,
@@ -10,6 +11,7 @@ import {
10
11
  getContexts,
11
12
  getLexContext,
12
13
  getVarContext,
14
+ computeFunctionLength,
13
15
  } from "../../src/util/insert";
14
16
 
15
17
  it("isBlock() should be true for block statements and program", async () => {
@@ -86,3 +88,55 @@ it("should throw when missing parameters", () => {
86
88
  expect(() => getLexContext(Identifier("test"), [])).toThrow();
87
89
  expect(() => getVarContext(Identifier("test"), [])).toThrow();
88
90
  });
91
+
92
+ test("computeFunctionLength", () => {
93
+ var tree = parseSync(`
94
+ function zeroParameters(){}; // 0
95
+ function oneParameter(a){}; // 1
96
+ function twoParameter(a,b){}; // 2
97
+ function restParameter1(...a){}; // 0
98
+ function restParameter2(a,b,...c){}; // 2
99
+ function defaultValue(a,b,c=1,d){}; // 2
100
+ function arrayPattern([a],[b = 2],[[c]]){}; // 3
101
+ function objectPattern({a},{b = 2},{c, d}){}; // 3
102
+ function mixed(a,{b},[c = 3],d,e=5,f,...g){}; // 4
103
+ `);
104
+
105
+ function getFunction(searchName: string): Location {
106
+ var searchLocation: Location;
107
+ traverse(tree, (o, p) => {
108
+ if (o.type === "FunctionDeclaration" && o.id.name === searchName) {
109
+ ok(!searchLocation);
110
+ searchLocation = [o, p];
111
+ }
112
+ });
113
+
114
+ ok(searchLocation);
115
+ return searchLocation;
116
+ }
117
+
118
+ expect(
119
+ computeFunctionLength(getFunction("zeroParameters")[0].params)
120
+ ).toStrictEqual(0);
121
+ expect(
122
+ computeFunctionLength(getFunction("oneParameter")[0].params)
123
+ ).toStrictEqual(1);
124
+ expect(
125
+ computeFunctionLength(getFunction("twoParameter")[0].params)
126
+ ).toStrictEqual(2);
127
+ expect(
128
+ computeFunctionLength(getFunction("restParameter1")[0].params)
129
+ ).toStrictEqual(0);
130
+ expect(
131
+ computeFunctionLength(getFunction("restParameter2")[0].params)
132
+ ).toStrictEqual(2);
133
+ expect(
134
+ computeFunctionLength(getFunction("arrayPattern")[0].params)
135
+ ).toStrictEqual(3);
136
+ expect(
137
+ computeFunctionLength(getFunction("objectPattern")[0].params)
138
+ ).toStrictEqual(3);
139
+ expect(computeFunctionLength(getFunction("mixed")[0].params)).toStrictEqual(
140
+ 4
141
+ );
142
+ });
@@ -1,89 +0,0 @@
1
- import { compileJsSync } from "../compiler";
2
- import { ObfuscateOrder } from "../order";
3
- import { ComputeProbabilityMap } from "../probability";
4
- import { isBlock } from "../traverse";
5
- import {
6
- CallExpression,
7
- Identifier,
8
- Literal,
9
- Node,
10
- VariableDeclaration,
11
- VariableDeclarator,
12
- } from "../util/gen";
13
- import { isFunction, prepend } from "../util/insert";
14
- import Transform from "./transform";
15
-
16
- export default class Eval extends Transform {
17
- constructor(o) {
18
- super(o, ObfuscateOrder.Eval);
19
- }
20
-
21
- match(object, parents) {
22
- return (
23
- isFunction(object) &&
24
- object.type != "ArrowFunctionExpression" &&
25
- !object.$eval &&
26
- !object.$dispatcherSkip
27
- );
28
- }
29
-
30
- transform(object, parents) {
31
- // Don't apply to getter/setters or class methods
32
- if (parents[0]) {
33
- if (
34
- parents[0].type === "MethodDefinition" &&
35
- parents[0].value === object
36
- ) {
37
- return;
38
- }
39
-
40
- if (
41
- parents[0].type === "Property" &&
42
- parents[0].value === object &&
43
- (parents[0].kind !== "init" || parents[0].method)
44
- ) {
45
- return;
46
- }
47
- }
48
-
49
- if (
50
- !ComputeProbabilityMap(
51
- this.options.eval,
52
- (x) => x,
53
- object.id && object.id.name
54
- )
55
- ) {
56
- return;
57
- }
58
-
59
- object.$eval = (o, p) => {
60
- var name;
61
- var requiresMove = false;
62
- if (object.type == "FunctionDeclaration") {
63
- name = object.id.name;
64
- object.type = "FunctionExpression";
65
- object.id = null;
66
- requiresMove = Array.isArray(p[0]) && isBlock(p[1]);
67
- }
68
-
69
- var code = compileJsSync(object, this.options);
70
- if (object.type == "FunctionExpression") {
71
- code = "(" + code + ")";
72
- }
73
-
74
- var literal = Literal(code);
75
-
76
- var expr: Node = CallExpression(Identifier("eval"), [literal]);
77
- if (name) {
78
- expr = VariableDeclaration(VariableDeclarator(name, expr));
79
- }
80
-
81
- if (requiresMove) {
82
- prepend(p[1], expr);
83
- p[0].splice(p[0].indexOf(object), 1);
84
- } else {
85
- this.replace(object, expr);
86
- }
87
- };
88
- }
89
- }
@@ -1,280 +0,0 @@
1
- import { ok } from "assert";
2
- import { ObfuscateOrder } from "../../order";
3
- import { ComputeProbabilityMap } from "../../probability";
4
- import { getBlock, isBlock, walk } from "../../traverse";
5
- import {
6
- AssignmentExpression,
7
- ExpressionStatement,
8
- Identifier,
9
- Location,
10
- VariableDeclarator,
11
- } from "../../util/gen";
12
- import {
13
- containsLexicallyBoundVariables,
14
- getFunctionParameters,
15
- getIdentifierInfo,
16
- } from "../../util/identifiers";
17
- import {
18
- getDefiningContext,
19
- getReferencingContexts,
20
- getVarContext,
21
- isForInitialize,
22
- isFunction,
23
- isVarContext,
24
- } from "../../util/insert";
25
- import Transform from "../transform";
26
-
27
- /**
28
- * Statement-based variable recycling.
29
- *
30
- * ```js
31
- * // Input
32
- * function percentage(decimal) {
33
- * var multiplied = x * 100;
34
- * var floored = Math.floor(multiplied);
35
- * var output = floored + "%"
36
- * return output;
37
- * }
38
- *
39
- * // Output
40
- * function percentage(decimal) {
41
- * var multiplied = x * 100;
42
- * var floored = Math.floor(multiplied);
43
- * multiplied = floored + "%";
44
- * return multiplied;
45
- * }
46
- * ```
47
- */
48
- export default class NameRecycling extends Transform {
49
- constructor(o) {
50
- super(o, ObfuscateOrder.NameRecycling);
51
- }
52
-
53
- match(object, parents) {
54
- return isBlock(object);
55
- }
56
-
57
- transform(object, parents) {
58
- return () => {
59
- if (containsLexicallyBoundVariables(object, parents)) {
60
- return;
61
- }
62
-
63
- var context = getVarContext(object, parents);
64
-
65
- var stmts = [...object.body];
66
- ok(Array.isArray(stmts));
67
-
68
- var definedMap = new Map<number, Set<string>>();
69
- var referencedMap = new Map<number, Set<string>>();
70
- var nodeMap = new Map<number, Map<string, Location[]>>();
71
-
72
- var lastReferenceMap = new Map<string, number>();
73
-
74
- var defined = new Set<string>();
75
- var illegal = new Set<string>();
76
-
77
- var fn = isFunction(parents[0]) ? parents[0] : null;
78
- if (fn) {
79
- definedMap.set(
80
- -1,
81
- new Set(
82
- getFunctionParameters(fn, parents.slice(1)).map((x) => x[0].name)
83
- )
84
- );
85
- }
86
-
87
- stmts.forEach((stmt, i) => {
88
- var definedHere = new Set<string>();
89
- var referencedHere = new Set<string>();
90
- var nodesHere = new Map<string, Location[]>();
91
-
92
- walk(stmt, [object.body, object, ...parents], (o, p) => {
93
- if (o.type == "Identifier") {
94
- return () => {
95
- var info = getIdentifierInfo(o, p);
96
- if (!info.spec.isReferenced) {
97
- return;
98
- }
99
-
100
- var comparingContext = info.spec.isDefined
101
- ? getDefiningContext(o, p)
102
- : getReferencingContexts(o, p).find((x) => isVarContext(x));
103
-
104
- if (comparingContext !== context) {
105
- illegal.add(o.name);
106
- this.log(o.name, "is different context");
107
- } else {
108
- if (!nodesHere.has(o.name)) {
109
- nodesHere.set(o.name, [[o, p]]);
110
- } else {
111
- nodesHere.get(o.name).push([o, p]);
112
- }
113
-
114
- if (info.spec.isDefined) {
115
- // Function Declarations can be used before they're defined, if so, don't change this
116
- if (
117
- info.isFunctionDeclaration &&
118
- lastReferenceMap.has(o.name)
119
- ) {
120
- illegal.add(o.name);
121
- }
122
- if (
123
- defined.has(o.name) ||
124
- getBlock(o, p) !== object ||
125
- info.isImportSpecifier
126
- ) {
127
- illegal.add(o.name);
128
- }
129
- defined.add(o.name);
130
- definedHere.add(o.name);
131
- } else {
132
- referencedHere.add(o.name);
133
- }
134
- }
135
-
136
- lastReferenceMap.set(o.name, i);
137
- };
138
- }
139
- });
140
-
141
- // console.log(i, definedHere);
142
-
143
- definedMap.set(i, definedHere);
144
- referencedMap.set(i, referencedHere);
145
- nodeMap.set(i, nodesHere);
146
- });
147
-
148
- this.log(illegal);
149
-
150
- illegal.forEach((name) => {
151
- nodeMap.forEach((value) => {
152
- value.delete(name);
153
- });
154
- });
155
-
156
- var available = new Set<string>();
157
- var newNames = Object.create(null);
158
-
159
- stmts.forEach((stmt, i) => {
160
- var nodes = nodeMap.get(i);
161
-
162
- nodes.forEach((locations, name) => {
163
- var newName = newNames[name];
164
-
165
- if (!newName) {
166
- var canChange = false;
167
-
168
- if (
169
- object.type == "Program" &&
170
- !ComputeProbabilityMap(this.options.renameGlobals, (x) => x, name)
171
- ) {
172
- return;
173
- }
174
-
175
- if (defined.has(name) && definedMap.get(i).has(name)) {
176
- canChange = true;
177
- }
178
-
179
- if (!canChange) {
180
- return;
181
- }
182
-
183
- if (available.size) {
184
- newName = available.keys().next().value;
185
- available.delete(newName);
186
-
187
- ok(name !== newName);
188
- newNames[name] = newName;
189
-
190
- defined.delete(name);
191
-
192
- this.log(name, "->", newName);
193
- }
194
- }
195
- });
196
-
197
- nodes.forEach((locations, name) => {
198
- var newName = newNames[name];
199
-
200
- if (newName) {
201
- locations.forEach(([object, parents]) => {
202
- object.name = newName;
203
-
204
- var declaratorIndex = parents.findIndex(
205
- (p) => p.type == "VariableDeclarator"
206
- );
207
- if (
208
- declaratorIndex !== -1 &&
209
- parents[declaratorIndex].id ===
210
- (parents[declaratorIndex - 1] || object)
211
- ) {
212
- var value =
213
- parents[declaratorIndex].init || Identifier("undefined");
214
-
215
- var expr = AssignmentExpression(
216
- "=",
217
- parents[declaratorIndex].id,
218
- value
219
- );
220
-
221
- if (parents[declaratorIndex + 1].length === 1) {
222
- if (
223
- isForInitialize(
224
- parents[declaratorIndex + 2],
225
- parents.slice(3)
226
- )
227
- ) {
228
- this.replace(parents[declaratorIndex + 2], expr);
229
- } else {
230
- this.replace(
231
- parents[declaratorIndex + 2],
232
- ExpressionStatement(expr)
233
- );
234
- }
235
- } else {
236
- this.replace(
237
- parents[declaratorIndex],
238
- VariableDeclarator(this.getPlaceholder(), expr)
239
- );
240
- }
241
- } else {
242
- if (parents[0].type == "FunctionDeclaration") {
243
- this.replace(
244
- parents[0],
245
- ExpressionStatement(
246
- AssignmentExpression("=", Identifier(newName), {
247
- ...parents[0],
248
- type: "FunctionExpression",
249
- id: null,
250
- })
251
- )
252
- );
253
- } else if (parents[0].type == "ClassDeclaration") {
254
- this.replace(
255
- parents[0],
256
- ExpressionStatement(
257
- AssignmentExpression("=", Identifier(newName), {
258
- ...parents[0],
259
- type: "ClassExpression",
260
- })
261
- )
262
- );
263
- }
264
- }
265
- });
266
- }
267
-
268
- if (defined.has(name)) {
269
- var lastRef = lastReferenceMap.get(name);
270
- var isDecommissioned = lastRef === i;
271
-
272
- if (isDecommissioned) {
273
- available.add(name);
274
- }
275
- }
276
- });
277
- });
278
- };
279
- }
280
- }