@papicandela/mcx-core 0.2.2 → 0.2.6
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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/index.js +388 -113
- package/package.json +8 -2
- package/src/adapter.ts +8 -4
- package/src/config.ts +6 -0
- package/src/executor.ts +5 -3
- package/src/index.ts +1 -0
- package/src/sandbox/analyzer/analyzer.test.ts +3 -3
- package/src/sandbox/analyzer/analyzer.ts +2 -2
- package/src/sandbox/analyzer/rules/no-dangerous-globals.ts +135 -33
- package/src/sandbox/analyzer/rules/no-infinite-loop.ts +40 -13
- package/src/sandbox/bun-worker.ts +107 -13
- package/src/sandbox/network-policy.ts +110 -54
- package/src/type-generator.ts +86 -15
- package/src/types.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papicandela/mcx-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "MCX Core - MCP Code Execution Framework",
|
|
5
5
|
"author": "papicandela",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/schizoidcock/mcx"
|
|
10
10
|
},
|
|
11
|
-
"keywords": [
|
|
11
|
+
"keywords": [
|
|
12
|
+
"mcp",
|
|
13
|
+
"claude",
|
|
14
|
+
"ai",
|
|
15
|
+
"code-execution",
|
|
16
|
+
"sandbox"
|
|
17
|
+
],
|
|
12
18
|
"main": "dist/index.js",
|
|
13
19
|
"types": "dist/index.d.ts",
|
|
14
20
|
"type": "module",
|
package/src/adapter.ts
CHANGED
|
@@ -38,14 +38,18 @@ function createParameterValidator(
|
|
|
38
38
|
schema = z.unknown();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
// IMPORTANT: Order matters in Zod!
|
|
42
|
+
// .optional() must come BEFORE .default() so that:
|
|
43
|
+
// - absent input resolves to the default (not undefined)
|
|
44
|
+
// - z.string().optional().default("x") = ZodDefault<ZodOptional<...>>
|
|
45
45
|
if (!param.required) {
|
|
46
46
|
schema = schema.optional();
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
if (param.default !== undefined) {
|
|
50
|
+
schema = schema.default(param.default);
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
shape[param.name] = schema;
|
|
50
54
|
}
|
|
51
55
|
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Adapter, MCXConfig, SandboxConfig, Skill } from "./types.js";
|
|
2
|
+
import { DEFAULT_NETWORK_POLICY } from "./sandbox/network-policy.js";
|
|
3
|
+
import { DEFAULT_ANALYSIS_CONFIG } from "./sandbox/analyzer/index.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Default MCX configuration values.
|
|
7
|
+
* Must include all sandbox fields to ensure network isolation is always enabled.
|
|
5
8
|
*/
|
|
6
9
|
const DEFAULT_CONFIG: Required<Omit<MCXConfig, "adapters" | "skills" | "env">> = {
|
|
7
10
|
sandbox: {
|
|
@@ -9,6 +12,9 @@ const DEFAULT_CONFIG: Required<Omit<MCXConfig, "adapters" | "skills" | "env">> =
|
|
|
9
12
|
memoryLimit: 128,
|
|
10
13
|
allowAsync: true,
|
|
11
14
|
globals: {},
|
|
15
|
+
networkPolicy: DEFAULT_NETWORK_POLICY,
|
|
16
|
+
normalizeCode: true,
|
|
17
|
+
analysis: DEFAULT_ANALYSIS_CONFIG,
|
|
12
18
|
},
|
|
13
19
|
adaptersDir: "./adapters",
|
|
14
20
|
skillsDir: "./skills",
|
package/src/executor.ts
CHANGED
|
@@ -121,7 +121,7 @@ export class MCXExecutor {
|
|
|
121
121
|
*/
|
|
122
122
|
registerAdapter(adapter: Adapter): void {
|
|
123
123
|
if (this.adapters.has(adapter.name)) {
|
|
124
|
-
console.
|
|
124
|
+
console.error(`[MCX] Adapter "${adapter.name}" is being overwritten`);
|
|
125
125
|
}
|
|
126
126
|
this.adapters.set(adapter.name, adapter);
|
|
127
127
|
}
|
|
@@ -159,7 +159,7 @@ export class MCXExecutor {
|
|
|
159
159
|
*/
|
|
160
160
|
registerSkill(skill: Skill): void {
|
|
161
161
|
if (this.skills.has(skill.name)) {
|
|
162
|
-
console.
|
|
162
|
+
console.error(`[MCX] Skill "${skill.name}" is being overwritten`);
|
|
163
163
|
}
|
|
164
164
|
this.skills.set(skill.name, skill);
|
|
165
165
|
}
|
|
@@ -288,12 +288,14 @@ export class MCXExecutor {
|
|
|
288
288
|
};
|
|
289
289
|
} catch (error) {
|
|
290
290
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
291
|
+
// Truncate stack to 5 lines to prevent context bloat
|
|
292
|
+
const stack = err.stack ? err.stack.split("\n").slice(0, 5).join("\n") : undefined;
|
|
291
293
|
return {
|
|
292
294
|
success: false,
|
|
293
295
|
error: {
|
|
294
296
|
name: err.name,
|
|
295
297
|
message: err.message,
|
|
296
|
-
stack
|
|
298
|
+
stack,
|
|
297
299
|
},
|
|
298
300
|
logs: [],
|
|
299
301
|
executionTime: performance.now() - startTime,
|
package/src/index.ts
CHANGED
|
@@ -179,10 +179,10 @@ describe("analyze", () => {
|
|
|
179
179
|
)).toBe(true);
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
-
it("
|
|
182
|
+
it("errors on require (security: could access dangerous modules)", () => {
|
|
183
183
|
const result = analyze("const fs = require('fs');");
|
|
184
|
-
expect(result.
|
|
185
|
-
|
|
184
|
+
expect(result.errors.some(e =>
|
|
185
|
+
e.rule === "no-dangerous-globals" && e.message.includes("require")
|
|
186
186
|
)).toBe(true);
|
|
187
187
|
});
|
|
188
188
|
|
|
@@ -222,9 +222,9 @@ export function analyze(
|
|
|
222
222
|
|
|
223
223
|
const elapsed = performance.now() - start;
|
|
224
224
|
|
|
225
|
-
// Log if we exceed performance budget
|
|
225
|
+
// Log if we exceed performance budget (use stderr to avoid breaking stdio transport)
|
|
226
226
|
if (elapsed > 50) {
|
|
227
|
-
console.
|
|
227
|
+
console.error(`[mcx-analyzer] Exceeded 50ms budget: ${elapsed.toFixed(1)}ms`);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
return { warnings, errors, elapsed };
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* Rule: no-dangerous-globals
|
|
3
3
|
*
|
|
4
4
|
* Detects usage of dangerous globals that are not available or unsafe in sandbox:
|
|
5
|
-
* - Dynamic code execution
|
|
6
|
-
* - Function constructor
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
5
|
+
* - Dynamic code execution (blocked as error)
|
|
6
|
+
* - Function constructor (blocked as error)
|
|
7
|
+
* - AsyncFunction constructor via prototype chain (blocked as error)
|
|
8
|
+
* - process object (warning)
|
|
9
|
+
* - require function (blocked as error - could access fs/child_process)
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import type * as acorn from "acorn";
|
|
@@ -17,72 +18,158 @@ const FUNC_CONSTRUCTOR = "Func" + "tion";
|
|
|
17
18
|
const REQUIRE_NAME = "req" + "uire";
|
|
18
19
|
const PROCESS_NAME = "pro" + "cess";
|
|
19
20
|
|
|
21
|
+
// Globals that expose Function constructor
|
|
22
|
+
const DANGEROUS_GLOBALS = ["globalThis", "self", "window"];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if a node is accessing .constructor on a function expression
|
|
26
|
+
* Detects: Object.getPrototypeOf(async function(){}).constructor
|
|
27
|
+
* (function(){}).constructor
|
|
28
|
+
* (async ()=>{}).constructor
|
|
29
|
+
*/
|
|
30
|
+
function isConstructorOnFunction(node: acorn.MemberExpression): boolean {
|
|
31
|
+
// Check if accessing .constructor
|
|
32
|
+
const prop = node.property;
|
|
33
|
+
const isConstructorAccess =
|
|
34
|
+
(!node.computed && prop.type === "Identifier" && (prop as acorn.Identifier).name === "constructor") ||
|
|
35
|
+
(node.computed && prop.type === "Literal" && (prop as acorn.Literal).value === "constructor");
|
|
36
|
+
|
|
37
|
+
if (!isConstructorAccess) return false;
|
|
38
|
+
|
|
39
|
+
// Check if the object is a function expression or call result that could return Function
|
|
40
|
+
const obj = node.object;
|
|
41
|
+
|
|
42
|
+
// Direct: (function(){}).constructor or (async ()=>{}).constructor
|
|
43
|
+
if (obj.type === "FunctionExpression" || obj.type === "ArrowFunctionExpression") {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Via Object.getPrototypeOf: Object.getPrototypeOf(fn).constructor
|
|
48
|
+
if (obj.type === "CallExpression") {
|
|
49
|
+
const call = obj as acorn.CallExpression;
|
|
50
|
+
if (call.callee.type === "MemberExpression") {
|
|
51
|
+
const callee = call.callee as acorn.MemberExpression;
|
|
52
|
+
if (
|
|
53
|
+
callee.object.type === "Identifier" &&
|
|
54
|
+
(callee.object as acorn.Identifier).name === "Object" &&
|
|
55
|
+
callee.property.type === "Identifier" &&
|
|
56
|
+
(callee.property as acorn.Identifier).name === "getPrototypeOf"
|
|
57
|
+
) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
20
66
|
export const rule: Rule = {
|
|
21
67
|
name: "no-dangerous-globals",
|
|
22
68
|
severity: "warn",
|
|
23
|
-
description: "
|
|
24
|
-
visits: ["CallExpression", "NewExpression", "
|
|
69
|
+
description: "Block dangerous globals that could escape sandbox",
|
|
70
|
+
visits: ["CallExpression", "NewExpression", "MemberExpression"],
|
|
25
71
|
|
|
26
72
|
visitors: {
|
|
27
73
|
CallExpression(node, context) {
|
|
28
74
|
const callExpr = node as acorn.CallExpression;
|
|
75
|
+
const calleeName = callExpr.callee.type === "Identifier"
|
|
76
|
+
? (callExpr.callee as acorn.Identifier).name
|
|
77
|
+
: null;
|
|
29
78
|
|
|
30
|
-
//
|
|
31
|
-
if (
|
|
32
|
-
callExpr.callee.type === "Identifier" &&
|
|
33
|
-
(callExpr.callee as acorn.Identifier).name === EVAL_NAME
|
|
34
|
-
) {
|
|
79
|
+
// SECURITY: Dynamic code execution is a sandbox escape vector - block
|
|
80
|
+
if (calleeName === EVAL_NAME) {
|
|
35
81
|
context.report({
|
|
36
|
-
severity: "
|
|
37
|
-
message: `${EVAL_NAME}() is
|
|
82
|
+
severity: "error",
|
|
83
|
+
message: `${EVAL_NAME}() is blocked in sandbox - potential code injection`,
|
|
38
84
|
line: context.getLine(node),
|
|
39
85
|
});
|
|
40
86
|
return;
|
|
41
87
|
}
|
|
42
88
|
|
|
43
|
-
//
|
|
44
|
-
if (
|
|
45
|
-
callExpr.callee.type === "Identifier" &&
|
|
46
|
-
(callExpr.callee as acorn.Identifier).name === REQUIRE_NAME
|
|
47
|
-
) {
|
|
89
|
+
// SECURITY: Function constructor called without new is equally dangerous
|
|
90
|
+
if (calleeName === FUNC_CONSTRUCTOR) {
|
|
48
91
|
context.report({
|
|
49
|
-
severity: "
|
|
50
|
-
message: `${
|
|
92
|
+
severity: "error",
|
|
93
|
+
message: `${FUNC_CONSTRUCTOR}() is blocked in sandbox - potential code injection`,
|
|
51
94
|
line: context.getLine(node),
|
|
52
95
|
});
|
|
53
96
|
return;
|
|
54
97
|
}
|
|
98
|
+
|
|
99
|
+
// SECURITY: require() could access fs, child_process - block as error
|
|
100
|
+
if (calleeName === REQUIRE_NAME) {
|
|
101
|
+
context.report({
|
|
102
|
+
severity: "error",
|
|
103
|
+
message: `${REQUIRE_NAME}() is blocked in sandbox - could access dangerous modules`,
|
|
104
|
+
line: context.getLine(node),
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// SECURITY: Detect (fn).constructor() or Object.getPrototypeOf(fn).constructor()
|
|
110
|
+
// This catches: new (Object.getPrototypeOf(async function(){}).constructor)(code)
|
|
111
|
+
if (callExpr.callee.type === "MemberExpression") {
|
|
112
|
+
if (isConstructorOnFunction(callExpr.callee as acorn.MemberExpression)) {
|
|
113
|
+
context.report({
|
|
114
|
+
severity: "error",
|
|
115
|
+
message: `Accessing .constructor on functions is blocked - potential sandbox escape`,
|
|
116
|
+
line: context.getLine(node),
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
55
121
|
},
|
|
56
122
|
|
|
57
123
|
NewExpression(node, context) {
|
|
58
124
|
const newExpr = node as acorn.NewExpression;
|
|
59
125
|
|
|
60
|
-
//
|
|
126
|
+
// SECURITY: Function constructor is a sandbox escape vector - block
|
|
61
127
|
if (
|
|
62
128
|
newExpr.callee.type === "Identifier" &&
|
|
63
129
|
(newExpr.callee as acorn.Identifier).name === FUNC_CONSTRUCTOR
|
|
64
130
|
) {
|
|
65
131
|
context.report({
|
|
66
|
-
severity: "
|
|
67
|
-
message: `${FUNC_CONSTRUCTOR} constructor is
|
|
132
|
+
severity: "error",
|
|
133
|
+
message: `${FUNC_CONSTRUCTOR} constructor is blocked in sandbox - potential code injection`,
|
|
68
134
|
line: context.getLine(node),
|
|
69
135
|
});
|
|
136
|
+
return;
|
|
70
137
|
}
|
|
71
|
-
},
|
|
72
138
|
|
|
73
|
-
|
|
74
|
-
|
|
139
|
+
// SECURITY: Detect new (fn).constructor() patterns
|
|
140
|
+
if (newExpr.callee.type === "MemberExpression") {
|
|
141
|
+
if (isConstructorOnFunction(newExpr.callee as acorn.MemberExpression)) {
|
|
142
|
+
context.report({
|
|
143
|
+
severity: "error",
|
|
144
|
+
message: `Accessing .constructor on functions is blocked - potential sandbox escape`,
|
|
145
|
+
line: context.getLine(node),
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
75
150
|
|
|
76
|
-
//
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
151
|
+
// SECURITY: Detect new globalThis.Function(), new self.Function(), etc.
|
|
152
|
+
if (newExpr.callee.type === "MemberExpression") {
|
|
153
|
+
const member = newExpr.callee as acorn.MemberExpression;
|
|
154
|
+
if (
|
|
155
|
+
member.object.type === "Identifier" &&
|
|
156
|
+
DANGEROUS_GLOBALS.includes((member.object as acorn.Identifier).name) &&
|
|
157
|
+
member.property.type === "Identifier" &&
|
|
158
|
+
(member.property as acorn.Identifier).name === FUNC_CONSTRUCTOR
|
|
159
|
+
) {
|
|
160
|
+
context.report({
|
|
161
|
+
severity: "error",
|
|
162
|
+
message: `${FUNC_CONSTRUCTOR} constructor is blocked in sandbox - potential code injection`,
|
|
163
|
+
line: context.getLine(node),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
83
166
|
}
|
|
84
167
|
},
|
|
85
168
|
|
|
169
|
+
// Note: Removed Identifier visitor for 'process' - MemberExpression catches
|
|
170
|
+
// process.X usage, and bare 'process' references fail at runtime anyway.
|
|
171
|
+
// This prevents duplicate warnings for each process.X access.
|
|
172
|
+
|
|
86
173
|
MemberExpression(node, context) {
|
|
87
174
|
const memberExpr = node as acorn.MemberExpression;
|
|
88
175
|
|
|
@@ -96,6 +183,21 @@ export const rule: Rule = {
|
|
|
96
183
|
message: `'${PROCESS_NAME}' is not available in sandbox`,
|
|
97
184
|
line: context.getLine(node),
|
|
98
185
|
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// SECURITY: Detect globalThis.Function, self.Function access
|
|
190
|
+
if (
|
|
191
|
+
memberExpr.object.type === "Identifier" &&
|
|
192
|
+
DANGEROUS_GLOBALS.includes((memberExpr.object as acorn.Identifier).name) &&
|
|
193
|
+
memberExpr.property.type === "Identifier" &&
|
|
194
|
+
(memberExpr.property as acorn.Identifier).name === FUNC_CONSTRUCTOR
|
|
195
|
+
) {
|
|
196
|
+
context.report({
|
|
197
|
+
severity: "error",
|
|
198
|
+
message: `Accessing ${FUNC_CONSTRUCTOR} via globals is blocked - potential sandbox escape`,
|
|
199
|
+
line: context.getLine(node),
|
|
200
|
+
});
|
|
99
201
|
}
|
|
100
202
|
},
|
|
101
203
|
},
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Rule: no-infinite-loop
|
|
3
3
|
*
|
|
4
|
-
* Detects infinite loops without
|
|
5
|
-
* - while(true) { ... } without break
|
|
6
|
-
* - for(;;) { ... } without break
|
|
4
|
+
* Detects infinite loops without exit statements:
|
|
5
|
+
* - while(true) { ... } without break/return/throw
|
|
6
|
+
* - for(;;) { ... } without break/return/throw
|
|
7
|
+
* - do { ... } while(true) without break/return/throw
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import type * as acorn from "acorn";
|
|
@@ -18,12 +19,17 @@ function isLiteralTrue(node: acorn.Node | null | undefined): boolean {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
* Check if a block contains
|
|
22
|
+
* Check if a block contains an exit statement (break, return, throw)
|
|
23
|
+
* These all terminate or exit the loop, so they prevent infinite loops.
|
|
22
24
|
*/
|
|
23
|
-
function
|
|
25
|
+
function hasExitStatement(node: acorn.Node): boolean {
|
|
26
|
+
// Exit statements
|
|
24
27
|
if (node.type === "BreakStatement") return true;
|
|
28
|
+
if (node.type === "ReturnStatement") return true;
|
|
29
|
+
if (node.type === "ThrowStatement") return true;
|
|
25
30
|
|
|
26
31
|
// Don't descend into nested loops or switches (break would apply to them)
|
|
32
|
+
// But DO descend for return/throw since they exit the function entirely
|
|
27
33
|
if (
|
|
28
34
|
node.type === "WhileStatement" ||
|
|
29
35
|
node.type === "ForStatement" ||
|
|
@@ -35,6 +41,15 @@ function hasBreak(node: acorn.Node): boolean {
|
|
|
35
41
|
return false;
|
|
36
42
|
}
|
|
37
43
|
|
|
44
|
+
// Don't descend into nested functions (return/throw would apply to them)
|
|
45
|
+
if (
|
|
46
|
+
node.type === "FunctionDeclaration" ||
|
|
47
|
+
node.type === "FunctionExpression" ||
|
|
48
|
+
node.type === "ArrowFunctionExpression"
|
|
49
|
+
) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
// Check children
|
|
39
54
|
for (const key of Object.keys(node)) {
|
|
40
55
|
const child = (node as unknown as Record<string, unknown>)[key];
|
|
@@ -42,11 +57,11 @@ function hasBreak(node: acorn.Node): boolean {
|
|
|
42
57
|
if (Array.isArray(child)) {
|
|
43
58
|
for (const item of child) {
|
|
44
59
|
if (item && typeof item === "object" && "type" in item) {
|
|
45
|
-
if (
|
|
60
|
+
if (hasExitStatement(item as acorn.Node)) return true;
|
|
46
61
|
}
|
|
47
62
|
}
|
|
48
63
|
} else if ("type" in child) {
|
|
49
|
-
if (
|
|
64
|
+
if (hasExitStatement(child as acorn.Node)) return true;
|
|
50
65
|
}
|
|
51
66
|
}
|
|
52
67
|
}
|
|
@@ -57,17 +72,17 @@ function hasBreak(node: acorn.Node): boolean {
|
|
|
57
72
|
export const rule: Rule = {
|
|
58
73
|
name: "no-infinite-loop",
|
|
59
74
|
severity: "error",
|
|
60
|
-
description: "Disallow infinite loops without
|
|
61
|
-
visits: ["WhileStatement", "ForStatement"],
|
|
75
|
+
description: "Disallow infinite loops without exit statements",
|
|
76
|
+
visits: ["WhileStatement", "ForStatement", "DoWhileStatement"],
|
|
62
77
|
|
|
63
78
|
visitors: {
|
|
64
79
|
WhileStatement(node, context) {
|
|
65
80
|
const whileNode = node as acorn.WhileStatement;
|
|
66
81
|
|
|
67
|
-
if (isLiteralTrue(whileNode.test) && !
|
|
82
|
+
if (isLiteralTrue(whileNode.test) && !hasExitStatement(whileNode.body)) {
|
|
68
83
|
context.report({
|
|
69
84
|
severity: "error",
|
|
70
|
-
message: "Infinite loop: while(true) without break",
|
|
85
|
+
message: "Infinite loop: while(true) without break/return/throw",
|
|
71
86
|
line: context.getLine(node),
|
|
72
87
|
});
|
|
73
88
|
}
|
|
@@ -77,10 +92,22 @@ export const rule: Rule = {
|
|
|
77
92
|
const forNode = node as acorn.ForStatement;
|
|
78
93
|
|
|
79
94
|
// for(;;) without test condition
|
|
80
|
-
if (!forNode.test && !
|
|
95
|
+
if (!forNode.test && !hasExitStatement(forNode.body)) {
|
|
96
|
+
context.report({
|
|
97
|
+
severity: "error",
|
|
98
|
+
message: "Infinite loop: for(;;) without break/return/throw",
|
|
99
|
+
line: context.getLine(node),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
DoWhileStatement(node, context) {
|
|
105
|
+
const doWhileNode = node as acorn.DoWhileStatement;
|
|
106
|
+
|
|
107
|
+
if (isLiteralTrue(doWhileNode.test) && !hasExitStatement(doWhileNode.body)) {
|
|
81
108
|
context.report({
|
|
82
109
|
severity: "error",
|
|
83
|
-
message: "Infinite loop:
|
|
110
|
+
message: "Infinite loop: do...while(true) without break/return/throw",
|
|
84
111
|
line: context.getLine(node),
|
|
85
112
|
});
|
|
86
113
|
}
|