progmune-runtime 3.7.3 → 3.7.5
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/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +4 -4
- package/README.zh-CN.md +4 -4
- package/dist/call-sequence.js +59 -15
- package/dist/call-sequence.test.js +139 -0
- package/dist/extract-ir-c.js +727 -0
- package/dist/extract-ir-c.test.js +438 -0
- package/dist/extract-project-ir.js +7 -0
- package/dist/extract-project-ir.test.js +87 -0
- package/dist/failure-collector.js +5 -1
- package/dist/sdk.js +1 -1
- package/dist/ssg-validator.js +7 -0
- package/dist/trust/engine.js +33 -18
- package/dist/trust/ssg-bridge.js +7 -2
- package/docs/Progmune_/351/241/271/347/233/256/345/205/250/350/247/243.html +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* C IR extractor tests — fixture-string-based parser tests via parseCSource
|
|
4
|
+
* (FS I/O limited to one mkdtemp integration case, per repo convention).
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const vitest_1 = require("vitest");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const extract_ir_c_1 = require("./extract-ir-c");
|
|
45
|
+
const PROJECT = "/proj";
|
|
46
|
+
/** Parse a string as /proj/x.c → file "x.c". */
|
|
47
|
+
function parse(src, file = "x.c") {
|
|
48
|
+
return (0, extract_ir_c_1.parseCSource)(src, path.join(PROJECT, file), PROJECT);
|
|
49
|
+
}
|
|
50
|
+
function fn(ir, name) {
|
|
51
|
+
const f = ir.find((x) => x.name === name);
|
|
52
|
+
(0, vitest_1.expect)(f, `expected function ${name}`).toBeDefined();
|
|
53
|
+
return f;
|
|
54
|
+
}
|
|
55
|
+
(0, vitest_1.describe)("extract-ir-c", () => {
|
|
56
|
+
(0, vitest_1.it)("基础签名 + 调用列表(auth_flow fixture 内容)", () => {
|
|
57
|
+
const ir = parse(`
|
|
58
|
+
int authenticate(const char* user, const char* pass) {
|
|
59
|
+
if (!verify_password(user, pass)) return 0;
|
|
60
|
+
char* token = generate_jwt(user);
|
|
61
|
+
session_t* sess = create_session(token);
|
|
62
|
+
return sess ? 1 : 0;
|
|
63
|
+
}
|
|
64
|
+
void do_logout(session_t* sess) {
|
|
65
|
+
logout(sess);
|
|
66
|
+
}
|
|
67
|
+
`);
|
|
68
|
+
(0, vitest_1.expect)(ir.map((f) => f.name)).toEqual(["authenticate", "do_logout"]);
|
|
69
|
+
const auth = fn(ir, "authenticate");
|
|
70
|
+
(0, vitest_1.expect)(auth.params).toEqual([
|
|
71
|
+
{ name: "user", type: "const char*" },
|
|
72
|
+
{ name: "pass", type: "const char*" },
|
|
73
|
+
]);
|
|
74
|
+
(0, vitest_1.expect)(auth.returnType).toBe("int");
|
|
75
|
+
(0, vitest_1.expect)(auth.calls).toEqual(["verify_password", "generate_jwt", "create_session"]);
|
|
76
|
+
(0, vitest_1.expect)(auth.exported).toBe(true);
|
|
77
|
+
(0, vitest_1.expect)(auth.external).toBe(false); // isProjectFn 契约
|
|
78
|
+
(0, vitest_1.expect)(auth.file).toBe("x.c");
|
|
79
|
+
(0, vitest_1.expect)(auth.tags).toEqual(["c"]);
|
|
80
|
+
const logout = fn(ir, "do_logout");
|
|
81
|
+
(0, vitest_1.expect)(logout.params).toEqual([{ name: "sess", type: "session_t*" }]);
|
|
82
|
+
(0, vitest_1.expect)(logout.returnType).toBe("void");
|
|
83
|
+
(0, vitest_1.expect)(logout.calls).toEqual(["logout"]);
|
|
84
|
+
(0, vitest_1.expect)(logout.outputs).toEqual([]);
|
|
85
|
+
});
|
|
86
|
+
(0, vitest_1.it)("static 函数 → exported=false,仍被提取", () => {
|
|
87
|
+
const ir = parse(`static int helper(int x) { return x; }`);
|
|
88
|
+
const h = fn(ir, "helper");
|
|
89
|
+
(0, vitest_1.expect)(h.exported).toBe(false);
|
|
90
|
+
(0, vitest_1.expect)(h.params).toEqual([{ name: "x", type: "int" }]);
|
|
91
|
+
});
|
|
92
|
+
(0, vitest_1.it)("多行签名 + 无空格星号返回类型", () => {
|
|
93
|
+
const ir = parse(`
|
|
94
|
+
int
|
|
95
|
+
compute(const char* a) {
|
|
96
|
+
return strlen(a);
|
|
97
|
+
}
|
|
98
|
+
static const char*
|
|
99
|
+
get_name(void) {
|
|
100
|
+
return "x";
|
|
101
|
+
}
|
|
102
|
+
`);
|
|
103
|
+
const c = fn(ir, "compute");
|
|
104
|
+
(0, vitest_1.expect)(c.returnType).toBe("int");
|
|
105
|
+
(0, vitest_1.expect)(c.params).toEqual([{ name: "a", type: "const char*" }]);
|
|
106
|
+
(0, vitest_1.expect)(c.calls).toEqual(["strlen"]);
|
|
107
|
+
const g = fn(ir, "get_name");
|
|
108
|
+
(0, vitest_1.expect)(g.returnType).toBe("const char*");
|
|
109
|
+
(0, vitest_1.expect)(g.exported).toBe(false);
|
|
110
|
+
(0, vitest_1.expect)(g.params).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
(0, vitest_1.it)("注释与字符串内的花括号不腐蚀括号计数", () => {
|
|
113
|
+
const ir = parse(`
|
|
114
|
+
void f(void) {
|
|
115
|
+
/* { */
|
|
116
|
+
// }
|
|
117
|
+
const char* s = "}";
|
|
118
|
+
g();
|
|
119
|
+
}
|
|
120
|
+
void g2(void) { h(); }
|
|
121
|
+
`);
|
|
122
|
+
(0, vitest_1.expect)(ir.map((x) => x.name)).toEqual(["f", "g2"]);
|
|
123
|
+
(0, vitest_1.expect)(fn(ir, "f").calls).toEqual(["g"]);
|
|
124
|
+
(0, vitest_1.expect)(fn(ir, "g2").calls).toEqual(["h"]);
|
|
125
|
+
});
|
|
126
|
+
(0, vitest_1.it)("字符串内容不产生调用", () => {
|
|
127
|
+
const ir = parse(`void f(void) { const char* s = "foo("; g(); }`);
|
|
128
|
+
(0, vitest_1.expect)(fn(ir, "f").calls).toEqual(["g"]);
|
|
129
|
+
});
|
|
130
|
+
(0, vitest_1.it)("块注释 @progmune 注解", () => {
|
|
131
|
+
const ir = parse(`
|
|
132
|
+
/* @progmune(namespace="auth", pre=["UNAUTHENTICATED"], post=["PASSWORD_VERIFIED"]) */
|
|
133
|
+
void verify(const char* u, const char* p) { check(u, p); }
|
|
134
|
+
`);
|
|
135
|
+
const v = fn(ir, "verify");
|
|
136
|
+
(0, vitest_1.expect)(v.protocol).toEqual({
|
|
137
|
+
namespace: "auth",
|
|
138
|
+
pre_states: ["UNAUTHENTICATED"],
|
|
139
|
+
post_states: ["PASSWORD_VERIFIED"],
|
|
140
|
+
});
|
|
141
|
+
(0, vitest_1.expect)(v.calls).toEqual(["check"]);
|
|
142
|
+
});
|
|
143
|
+
(0, vitest_1.it)("多行注解 + 全部文档标签", () => {
|
|
144
|
+
const ir = parse(`
|
|
145
|
+
/*
|
|
146
|
+
* @progmune(namespace="auth", pre=["A"], post=["B"], invalidate=["C"])
|
|
147
|
+
* @purpose verify user
|
|
148
|
+
* @description verifies credentials
|
|
149
|
+
* @tags auth, security
|
|
150
|
+
* @requires P1, P2
|
|
151
|
+
* @produces T1
|
|
152
|
+
* @useWhen login; recovery
|
|
153
|
+
* @inputs user, pass
|
|
154
|
+
* @outputs token
|
|
155
|
+
*/
|
|
156
|
+
int auth(const char* user, const char* pass) { return 1; }
|
|
157
|
+
`);
|
|
158
|
+
const a = fn(ir, "auth");
|
|
159
|
+
(0, vitest_1.expect)(a.protocol).toEqual({
|
|
160
|
+
namespace: "auth",
|
|
161
|
+
pre_states: ["A"],
|
|
162
|
+
post_states: ["B"],
|
|
163
|
+
invalidate: ["C"],
|
|
164
|
+
});
|
|
165
|
+
(0, vitest_1.expect)(a.purpose).toBe("verify user");
|
|
166
|
+
(0, vitest_1.expect)(a.description).toBe("verifies credentials");
|
|
167
|
+
(0, vitest_1.expect)(a.tags).toEqual(["auth", "security"]);
|
|
168
|
+
(0, vitest_1.expect)(a.requires).toEqual(["P1", "P2"]);
|
|
169
|
+
(0, vitest_1.expect)(a.produces).toEqual(["T1"]);
|
|
170
|
+
(0, vitest_1.expect)(a.useWhen).toEqual(["login", "recovery"]);
|
|
171
|
+
(0, vitest_1.expect)(a.inputs).toEqual(["user", "pass"]);
|
|
172
|
+
(0, vitest_1.expect)(a.outputs).toEqual(["token"]);
|
|
173
|
+
});
|
|
174
|
+
(0, vitest_1.it)("// @progmune 单行变体", () => {
|
|
175
|
+
const ir = parse(`
|
|
176
|
+
// @progmune(namespace="file", pre=["OPEN"], post=["CLOSED"])
|
|
177
|
+
void open_file(void) { }
|
|
178
|
+
`);
|
|
179
|
+
(0, vitest_1.expect)(fn(ir, "open_file").protocol).toEqual({
|
|
180
|
+
namespace: "file",
|
|
181
|
+
pre_states: ["OPEN"],
|
|
182
|
+
post_states: ["CLOSED"],
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
(0, vitest_1.it)("注解与函数之间允许空行", () => {
|
|
186
|
+
const ir = parse(`
|
|
187
|
+
/* @progmune(namespace="auth", pre=["A"], post=["B"]) */
|
|
188
|
+
|
|
189
|
+
void f(void) {}
|
|
190
|
+
`);
|
|
191
|
+
(0, vitest_1.expect)(fn(ir, "f").protocol?.namespace).toBe("auth");
|
|
192
|
+
});
|
|
193
|
+
(0, vitest_1.it)("只有文档标签、无 @progmune → protocol undefined", () => {
|
|
194
|
+
const ir = parse(`
|
|
195
|
+
/**
|
|
196
|
+
* @purpose read config
|
|
197
|
+
* @tags io
|
|
198
|
+
*/
|
|
199
|
+
void read_conf(void) { }
|
|
200
|
+
`);
|
|
201
|
+
const r = fn(ir, "read_conf");
|
|
202
|
+
(0, vitest_1.expect)(r.purpose).toBe("read config");
|
|
203
|
+
(0, vitest_1.expect)(r.tags).toEqual(["io"]);
|
|
204
|
+
(0, vitest_1.expect)(r.protocol).toBeUndefined();
|
|
205
|
+
});
|
|
206
|
+
(0, vitest_1.it)("纯文件头注释不挂载到首个函数", () => {
|
|
207
|
+
const ir = parse(`
|
|
208
|
+
/* module overview — plain description */
|
|
209
|
+
int f(void) { return 0; }
|
|
210
|
+
`);
|
|
211
|
+
const f = fn(ir, "f");
|
|
212
|
+
(0, vitest_1.expect)(f.purpose).toBe("");
|
|
213
|
+
(0, vitest_1.expect)(f.description).toBe("");
|
|
214
|
+
(0, vitest_1.expect)(f.protocol).toBeUndefined();
|
|
215
|
+
});
|
|
216
|
+
(0, vitest_1.it)("参数边界:数组/多维/函数指针/变参/裸 void/多词类型", () => {
|
|
217
|
+
const ir = parse(`
|
|
218
|
+
void a(char buf[256]) {}
|
|
219
|
+
void b(int m[2][3]) {}
|
|
220
|
+
void c(void (*cb)(int)) {}
|
|
221
|
+
void d(...) {}
|
|
222
|
+
void e(void) {}
|
|
223
|
+
void g(unsigned long long n) {}
|
|
224
|
+
`);
|
|
225
|
+
(0, vitest_1.expect)(fn(ir, "a").params).toEqual([{ name: "buf", type: "char" }]);
|
|
226
|
+
(0, vitest_1.expect)(fn(ir, "b").params).toEqual([{ name: "m", type: "int" }]);
|
|
227
|
+
(0, vitest_1.expect)(fn(ir, "c").params).toEqual([{ name: "cb", type: "void (*cb)(int)" }]);
|
|
228
|
+
(0, vitest_1.expect)(fn(ir, "d").params).toEqual([{ name: "...", type: "..." }]);
|
|
229
|
+
(0, vitest_1.expect)(fn(ir, "e").params).toEqual([]);
|
|
230
|
+
(0, vitest_1.expect)(fn(ir, "g").params).toEqual([{ name: "n", type: "unsigned long long" }]);
|
|
231
|
+
});
|
|
232
|
+
(0, vitest_1.it)("成员调用取 ->/. 之后的调用名(函数指针分发仍静态不可见)", () => {
|
|
233
|
+
const ir = parse(`void close_conn(conn_t* cf) { cf->close_one(); cf->next->close_two(); obj.method(x); }`);
|
|
234
|
+
(0, vitest_1.expect)(fn(ir, "close_conn").calls).toEqual(["close_one", "close_two", "method"]);
|
|
235
|
+
});
|
|
236
|
+
(0, vitest_1.it)("自调用被排除;重复调用保留(状态机需要重复语义)", () => {
|
|
237
|
+
const ir = parse(`void f(void) { f(); g(); g(); h(); }`);
|
|
238
|
+
(0, vitest_1.expect)(fn(ir, "f").calls).toEqual(["g", "g", "h"]);
|
|
239
|
+
});
|
|
240
|
+
(0, vitest_1.it)("struct 定义体被跳过(单行 + 多行)", () => {
|
|
241
|
+
const ir = parse(`
|
|
242
|
+
typedef struct { int x; } Foo;
|
|
243
|
+
void after_struct(void) { g(); }
|
|
244
|
+
typedef struct {
|
|
245
|
+
int x;
|
|
246
|
+
} Bar;
|
|
247
|
+
void after_struct2(void) { h(); }
|
|
248
|
+
`);
|
|
249
|
+
(0, vitest_1.expect)(ir.map((x) => x.name)).toEqual(["after_struct", "after_struct2"]);
|
|
250
|
+
(0, vitest_1.expect)(fn(ir, "after_struct").calls).toEqual(["g"]);
|
|
251
|
+
(0, vitest_1.expect)(fn(ir, "after_struct2").calls).toEqual(["h"]);
|
|
252
|
+
});
|
|
253
|
+
(0, vitest_1.it)("goto 合成 goto_<label> 调用", () => {
|
|
254
|
+
const ir = parse(`void f(void) { goto cleanup; g(); cleanup: ; }`);
|
|
255
|
+
(0, vitest_1.expect)(fn(ir, "f").calls).toEqual(["g", "goto_cleanup"]);
|
|
256
|
+
});
|
|
257
|
+
(0, vitest_1.it)("函数体内的预处理行整体跳过(花括号与调用均不计数)", () => {
|
|
258
|
+
const ir = parse(`
|
|
259
|
+
void f(void) {
|
|
260
|
+
#define X {
|
|
261
|
+
#undef X
|
|
262
|
+
g();
|
|
263
|
+
}
|
|
264
|
+
`);
|
|
265
|
+
(0, vitest_1.expect)(fn(ir, "f").calls).toEqual(["g"]);
|
|
266
|
+
});
|
|
267
|
+
(0, vitest_1.it)("嵌套块括号正确闭合", () => {
|
|
268
|
+
const ir = parse(`void f(void) { if (x) { g(); } h(); }`);
|
|
269
|
+
(0, vitest_1.expect)(fn(ir, "f").calls).toEqual(["g", "h"]);
|
|
270
|
+
});
|
|
271
|
+
(0, vitest_1.it)("__attribute__ 前缀被剥离,不进返回类型", () => {
|
|
272
|
+
const ir = parse(`
|
|
273
|
+
static __attribute__((unused)) int helper(void) { return 0; }
|
|
274
|
+
void caller(void) { helper(); }
|
|
275
|
+
`);
|
|
276
|
+
const h = fn(ir, "helper");
|
|
277
|
+
(0, vitest_1.expect)(h.returnType).toBe("int");
|
|
278
|
+
(0, vitest_1.expect)(h.exported).toBe(false);
|
|
279
|
+
(0, vitest_1.expect)(fn(ir, "caller").calls).toEqual(["helper"]);
|
|
280
|
+
});
|
|
281
|
+
(0, vitest_1.it)("头文件原型被忽略;static inline 定义被提取", () => {
|
|
282
|
+
const ir = parse(`
|
|
283
|
+
int prototype_only(const char* x);
|
|
284
|
+
static inline int add1(int x) { return x + 1; }
|
|
285
|
+
`, "header.h");
|
|
286
|
+
(0, vitest_1.expect)(ir.map((x) => x.name)).toEqual(["add1"]);
|
|
287
|
+
(0, vitest_1.expect)(fn(ir, "add1").exported).toBe(false);
|
|
288
|
+
(0, vitest_1.expect)(fn(ir, "add1").params).toEqual([{ name: "x", type: "int" }]);
|
|
289
|
+
});
|
|
290
|
+
(0, vitest_1.it)("extractIRC 集成:真实 fixture 内容", () => {
|
|
291
|
+
const authFlow = `
|
|
292
|
+
int authenticate(const char* user, const char* pass) {
|
|
293
|
+
if (!verify_password(user, pass)) return 0;
|
|
294
|
+
char* token = generate_jwt(user);
|
|
295
|
+
session_t* sess = create_session(token);
|
|
296
|
+
return sess ? 1 : 0;
|
|
297
|
+
}
|
|
298
|
+
void do_logout(session_t* sess) {
|
|
299
|
+
logout(sess);
|
|
300
|
+
}
|
|
301
|
+
`;
|
|
302
|
+
const dbHandler = `
|
|
303
|
+
void run_query(const char* host, const char* sql) {
|
|
304
|
+
connect_db(host);
|
|
305
|
+
query_db(sql);
|
|
306
|
+
disconnect_db();
|
|
307
|
+
}
|
|
308
|
+
void run_insert(const char* host, const char* data) {
|
|
309
|
+
connect_db(host);
|
|
310
|
+
query_db(data);
|
|
311
|
+
disconnect_db();
|
|
312
|
+
}
|
|
313
|
+
void verify_and_session(const char* user, const char* pass) {
|
|
314
|
+
verify_password(user, pass);
|
|
315
|
+
generate_jwt(user);
|
|
316
|
+
create_session();
|
|
317
|
+
}
|
|
318
|
+
void auth_and_logout(const char* user, const char* pass) {
|
|
319
|
+
verify_password(user, pass);
|
|
320
|
+
generate_jwt(user);
|
|
321
|
+
create_session();
|
|
322
|
+
logout();
|
|
323
|
+
}
|
|
324
|
+
`;
|
|
325
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-c-"));
|
|
326
|
+
try {
|
|
327
|
+
fs.writeFileSync(path.join(dir, "auth_flow.c"), authFlow);
|
|
328
|
+
fs.mkdirSync(path.join(dir, "src"), { recursive: true });
|
|
329
|
+
fs.writeFileSync(path.join(dir, "src", "db_handler.c"), dbHandler);
|
|
330
|
+
const ir = (0, extract_ir_c_1.extractIRC)(dir);
|
|
331
|
+
(0, vitest_1.expect)(ir.map((f) => f.name).sort()).toEqual([
|
|
332
|
+
"auth_and_logout", "authenticate", "do_logout",
|
|
333
|
+
"run_insert", "run_query", "verify_and_session",
|
|
334
|
+
]);
|
|
335
|
+
const runQuery = fn(ir, "run_query");
|
|
336
|
+
(0, vitest_1.expect)(runQuery.file).toBe(path.join("src", "db_handler.c"));
|
|
337
|
+
(0, vitest_1.expect)(runQuery.calls).toEqual(["connect_db", "query_db", "disconnect_db"]);
|
|
338
|
+
(0, vitest_1.expect)(fn(ir, "auth_and_logout").calls).toEqual(["verify_password", "generate_jwt", "create_session", "logout"]);
|
|
339
|
+
// isProjectFn 契约:全部 external=false + 真实相对路径
|
|
340
|
+
for (const f of ir) {
|
|
341
|
+
(0, vitest_1.expect)(f.external).toBe(false);
|
|
342
|
+
(0, vitest_1.expect)(f.file).toBeTruthy();
|
|
343
|
+
(0, vitest_1.expect)(f.file).not.toBe("(external)");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
(0, vitest_1.it)("回归:顶层长标识符调用赋值行不触发指数级回溯(44 字符缓冲 11s → 即时)", () => {
|
|
351
|
+
// libssh authentication.c 真实触发:v2 签名正则的类型 token 循环对
|
|
352
|
+
// `name = ssh_userauth_kbdint_getname(session);` 穷举标识符切分(2^k)
|
|
353
|
+
const t0 = Date.now();
|
|
354
|
+
const ir = parse(`
|
|
355
|
+
int f(void) { return 0; }
|
|
356
|
+
name = ssh_userauth_kbdint_getname(session);
|
|
357
|
+
int g(void) { return 1; }
|
|
358
|
+
`);
|
|
359
|
+
(0, vitest_1.expect)(Date.now() - t0).toBeLessThan(2000);
|
|
360
|
+
(0, vitest_1.expect)(ir.map((x) => x.name)).toEqual(["f", "g"]);
|
|
361
|
+
});
|
|
362
|
+
(0, vitest_1.it)("非生产表面目录被跳过:tests/examples/deps 与测试文件名(Python 先例同款)", () => {
|
|
363
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-surface-"));
|
|
364
|
+
try {
|
|
365
|
+
fs.writeFileSync(path.join(dir, "main.c"), "void prod(void) {}\n");
|
|
366
|
+
fs.mkdirSync(path.join(dir, "tests"), { recursive: true });
|
|
367
|
+
fs.writeFileSync(path.join(dir, "tests", "torture_x.c"), "void in_tests(void) {}\n");
|
|
368
|
+
fs.mkdirSync(path.join(dir, "examples"), { recursive: true });
|
|
369
|
+
fs.writeFileSync(path.join(dir, "examples", "demo.c"), "void in_examples(void) {}\n");
|
|
370
|
+
fs.mkdirSync(path.join(dir, "deps", "vendor_lib"), { recursive: true });
|
|
371
|
+
fs.writeFileSync(path.join(dir, "deps", "vendor_lib", "lib.c"), "void in_deps(void) {}\n");
|
|
372
|
+
fs.writeFileSync(path.join(dir, "helper_test.c"), "void testfile(void) {}\n");
|
|
373
|
+
const ir = (0, extract_ir_c_1.extractIRC)(dir);
|
|
374
|
+
(0, vitest_1.expect)(ir.map((f) => f.name)).toEqual(["prod"]);
|
|
375
|
+
}
|
|
376
|
+
finally {
|
|
377
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
(0, vitest_1.it)("#if 0 死代码块被剥离:体内不平衡花括号不腐蚀计数", () => {
|
|
381
|
+
const ir = parse(`
|
|
382
|
+
void f(void) {
|
|
383
|
+
#if 0
|
|
384
|
+
void dead(void) { { {
|
|
385
|
+
#endif
|
|
386
|
+
g();
|
|
387
|
+
}
|
|
388
|
+
void g2(void) { h(); }
|
|
389
|
+
`);
|
|
390
|
+
(0, vitest_1.expect)(ir.map((x) => x.name)).toEqual(["f", "g2"]);
|
|
391
|
+
(0, vitest_1.expect)(fn(ir, "f").calls).toEqual(["g"]);
|
|
392
|
+
(0, vitest_1.expect)(fn(ir, "g2").calls).toEqual(["h"]);
|
|
393
|
+
});
|
|
394
|
+
(0, vitest_1.it)("#if 0 死代码块被剥离:顶层死函数不产生幻影函数;嵌套 #if 死区内保持死", () => {
|
|
395
|
+
const ir = parse(`
|
|
396
|
+
#if 0
|
|
397
|
+
void dead_fn(void) { broken {
|
|
398
|
+
#if 1
|
|
399
|
+
void dead_inner(void) { { {
|
|
400
|
+
#endif
|
|
401
|
+
void also_dead(void) { { {
|
|
402
|
+
#endif
|
|
403
|
+
void live_fn(void) { g(); }
|
|
404
|
+
`);
|
|
405
|
+
(0, vitest_1.expect)(ir.map((x) => x.name)).toEqual(["live_fn"]);
|
|
406
|
+
(0, vitest_1.expect)(fn(ir, "live_fn").calls).toEqual(["g"]);
|
|
407
|
+
});
|
|
408
|
+
(0, vitest_1.it)("#if 0 || X 表达式不求值,按活区处理", () => {
|
|
409
|
+
const ir = parse(`
|
|
410
|
+
#if 0 || 1
|
|
411
|
+
void maybe_live(void) { g(); }
|
|
412
|
+
#endif
|
|
413
|
+
void after(void) { h(); }
|
|
414
|
+
`);
|
|
415
|
+
(0, vitest_1.expect)(ir.map((x) => x.name)).toEqual(["maybe_live", "after"]);
|
|
416
|
+
(0, vitest_1.expect)(fn(ir, "maybe_live").calls).toEqual(["g"]);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
(0, vitest_1.describe)("extract-ir-c pointer-return regression", () => {
|
|
420
|
+
(0, vitest_1.it)("单行指针返回函数必须被提取(char */SSL */const char */FILE * 等)", () => {
|
|
421
|
+
const src = [
|
|
422
|
+
"int a(void) { return 1; }",
|
|
423
|
+
"char *b(void) { return 0; }",
|
|
424
|
+
"SSL *c(SSL_CTX *ctx) { return 0; }",
|
|
425
|
+
"const char *d(int x) { return 0; }",
|
|
426
|
+
"static FILE *f(void) { return 0; }",
|
|
427
|
+
"int (*handler)(int) { return 0; }",
|
|
428
|
+
].join("\n");
|
|
429
|
+
const fns = (0, extract_ir_c_1.parseCSource)(src, "ptr.c", "/");
|
|
430
|
+
const names = fns.map((f) => f.name);
|
|
431
|
+
(0, vitest_1.expect)(names).toEqual(vitest_1.expect.arrayContaining(["b", "c", "d", "f"]));
|
|
432
|
+
// 函数指针变量不是函数定义
|
|
433
|
+
(0, vitest_1.expect)(names).not.toContain("handler");
|
|
434
|
+
// 返回类型保留指针
|
|
435
|
+
const c = fns.find((f) => f.name === "c");
|
|
436
|
+
(0, vitest_1.expect)(c?.returnType).toBe("SSL *");
|
|
437
|
+
});
|
|
438
|
+
});
|
|
@@ -53,9 +53,11 @@ const fs = __importStar(require("fs"));
|
|
|
53
53
|
const path = __importStar(require("path"));
|
|
54
54
|
const extract_ir_1 = require("./extract-ir");
|
|
55
55
|
const extract_ir_python_1 = require("./extract-ir-python");
|
|
56
|
+
const extract_ir_c_1 = require("./extract-ir-c");
|
|
56
57
|
const SKIP_DIRS = new Set([
|
|
57
58
|
"node_modules", "dist", "build", ".git", ".progmune_corpus",
|
|
58
59
|
"__pycache__", "venv", ".venv",
|
|
60
|
+
"benchmarks", // vendored C 基准仓库(与 extract-ir-c.ts collectCFiles 口径一致)
|
|
59
61
|
]);
|
|
60
62
|
/** 有界递归扫描:项目是否含指定扩展名源文件(首个命中即返回)。 */
|
|
61
63
|
function hasSourceFiles(projectRoot, exts) {
|
|
@@ -98,6 +100,11 @@ exports.LANGUAGE_EXTRACTORS = [
|
|
|
98
100
|
detect: (p) => hasSourceFiles(p, new Set([".py"])),
|
|
99
101
|
extract: (p) => (0, extract_ir_python_1.extractIRPython)(p),
|
|
100
102
|
},
|
|
103
|
+
{
|
|
104
|
+
language: "c",
|
|
105
|
+
detect: (p) => hasSourceFiles(p, new Set([".c", ".h"])),
|
|
106
|
+
extract: (p) => (0, extract_ir_c_1.extractIRC)(p),
|
|
107
|
+
},
|
|
101
108
|
];
|
|
102
109
|
/** 项目检测到的语言列表(审计/标签用)。 */
|
|
103
110
|
function detectLanguages(projectRoot) {
|
|
@@ -45,6 +45,7 @@ const fs = __importStar(require("fs"));
|
|
|
45
45
|
const os = __importStar(require("os"));
|
|
46
46
|
const path = __importStar(require("path"));
|
|
47
47
|
const extract_project_ir_1 = require("./extract-project-ir");
|
|
48
|
+
const call_sequence_1 = require("./call-sequence");
|
|
48
49
|
(0, vitest_1.describe)("extractProjectIR(注册表合并)", () => {
|
|
49
50
|
(0, vitest_1.it)("合并所有检测到语言的函数", () => {
|
|
50
51
|
const tsFns = [{ name: "getSession", file: "src/auth.ts" }];
|
|
@@ -56,6 +57,18 @@ const extract_project_ir_1 = require("./extract-project-ir");
|
|
|
56
57
|
const ir = (0, extract_project_ir_1.extractProjectIR)("/tmp/fake", extractors);
|
|
57
58
|
(0, vitest_1.expect)(ir).toEqual([...tsFns, ...pyFns]);
|
|
58
59
|
});
|
|
60
|
+
(0, vitest_1.it)("三语言合并(TS + Python + C)按注册表顺序", () => {
|
|
61
|
+
const tsFns = [{ name: "getSession", file: "src/auth.ts" }];
|
|
62
|
+
const pyFns = [{ name: "verify_password", file: "auth_service.py" }];
|
|
63
|
+
const cFns = [{ name: "authenticate", file: "src/auth_flow.c" }];
|
|
64
|
+
const extractors = [
|
|
65
|
+
{ language: "typescript", detect: () => true, extract: () => tsFns },
|
|
66
|
+
{ language: "python", detect: () => true, extract: () => pyFns },
|
|
67
|
+
{ language: "c", detect: () => true, extract: () => cFns },
|
|
68
|
+
];
|
|
69
|
+
const ir = (0, extract_project_ir_1.extractProjectIR)("/tmp/fake", extractors);
|
|
70
|
+
(0, vitest_1.expect)(ir).toEqual([...tsFns, ...pyFns, ...cFns]);
|
|
71
|
+
});
|
|
59
72
|
(0, vitest_1.it)("未检测到的语言不运行提取器", () => {
|
|
60
73
|
const ran = [];
|
|
61
74
|
const extractors = [
|
|
@@ -74,6 +87,15 @@ const extract_project_ir_1 = require("./extract-project-ir");
|
|
|
74
87
|
const ir = (0, extract_project_ir_1.extractProjectIR)("/tmp/fake", extractors);
|
|
75
88
|
(0, vitest_1.expect)(ir).toEqual([{ name: "f" }]);
|
|
76
89
|
});
|
|
90
|
+
(0, vitest_1.it)("C 提取器失败被吞,其余语言结果保留", () => {
|
|
91
|
+
vitest_1.vi.spyOn(console, "error").mockImplementation(() => { });
|
|
92
|
+
const extractors = [
|
|
93
|
+
{ language: "typescript", detect: () => true, extract: () => [{ name: "f" }] },
|
|
94
|
+
{ language: "c", detect: () => true, extract: () => { throw new Error("broken c parse"); } },
|
|
95
|
+
];
|
|
96
|
+
const ir = (0, extract_project_ir_1.extractProjectIR)("/tmp/fake", extractors);
|
|
97
|
+
(0, vitest_1.expect)(ir).toEqual([{ name: "f" }]);
|
|
98
|
+
});
|
|
77
99
|
(0, vitest_1.it)("所有检测到的语言都失败时抛错(保留 execute 硬失败语义)", () => {
|
|
78
100
|
vitest_1.vi.spyOn(console, "error").mockImplementation(() => { });
|
|
79
101
|
const extractors = [
|
|
@@ -113,4 +135,69 @@ const extract_project_ir_1 = require("./extract-project-ir");
|
|
|
113
135
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
114
136
|
}
|
|
115
137
|
});
|
|
138
|
+
(0, vitest_1.it)("纯 C 项目(.c)被检测", () => {
|
|
139
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-lang3-"));
|
|
140
|
+
try {
|
|
141
|
+
fs.writeFileSync(path.join(dir, "main.c"), "int main(void) { return 0; }\n");
|
|
142
|
+
(0, vitest_1.expect)((0, extract_project_ir_1.detectLanguages)(dir)).toEqual(["c"]);
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
(0, vitest_1.it)("仅头文件(.h)也算 C 项目", () => {
|
|
149
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-lang4-"));
|
|
150
|
+
try {
|
|
151
|
+
fs.writeFileSync(path.join(dir, "header.h"), "int add(int a, int b);\n");
|
|
152
|
+
(0, vitest_1.expect)((0, extract_project_ir_1.detectLanguages)(dir)).toEqual(["c"]);
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
(0, vitest_1.it)("TS + C 混合项目两种语言都被检测", () => {
|
|
159
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-lang5-"));
|
|
160
|
+
try {
|
|
161
|
+
fs.writeFileSync(path.join(dir, "main.ts"), "export function f() {}");
|
|
162
|
+
fs.writeFileSync(path.join(dir, "helper.c"), "void helper(void) {}\n");
|
|
163
|
+
(0, vitest_1.expect)((0, extract_project_ir_1.detectLanguages)(dir).sort()).toEqual(["c", "typescript"]);
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
(0, vitest_1.it)("benchmarks/ 下的 C 源不计入检测(vendored 基准仓库,与 extract 口径一致)", () => {
|
|
170
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-lang6-"));
|
|
171
|
+
try {
|
|
172
|
+
fs.mkdirSync(path.join(dir, "benchmarks", "curl"), { recursive: true });
|
|
173
|
+
fs.writeFileSync(path.join(dir, "benchmarks", "curl", "lib.c"), "void f(void) {}\n");
|
|
174
|
+
(0, vitest_1.expect)((0, extract_project_ir_1.detectLanguages)(dir)).toEqual([]);
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
(0, vitest_1.it)("TS + C 混合项目:真实注册表合并 IR 含两语言函数,词段门控集合为并集", () => {
|
|
181
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-mix-"));
|
|
182
|
+
try {
|
|
183
|
+
// ts-morph 需要 tsconfig(无 tsconfig 时 TS 提取抛错、按设计被注册表吞掉)
|
|
184
|
+
fs.writeFileSync(path.join(dir, "tsconfig.json"), JSON.stringify({
|
|
185
|
+
compilerOptions: { target: "es2020", module: "commonjs" },
|
|
186
|
+
include: ["**/*.ts"],
|
|
187
|
+
}));
|
|
188
|
+
fs.writeFileSync(path.join(dir, "auth.ts"), "export function verifySession(t: string) { return t; }\n");
|
|
189
|
+
fs.writeFileSync(path.join(dir, "helper.c"), "void authenticate(void) { verify_password(); generate_jwt(); }\n");
|
|
190
|
+
const ir = (0, extract_project_ir_1.extractProjectIR)(dir);
|
|
191
|
+
const names = ir.map((f) => `${f.file}:${f.name}`).sort();
|
|
192
|
+
(0, vitest_1.expect)(names).toEqual(["auth.ts:verifySession", "helper.c:authenticate"]);
|
|
193
|
+
// 词段匹配门控集合(ssg-bridge projectFunctions)为两语言并集——
|
|
194
|
+
// C 函数名进入集合是设计行为(混合项目 C 侧才参与词段匹配)
|
|
195
|
+
const gate = (0, call_sequence_1.collectProjectFunctionNames)(ir);
|
|
196
|
+
(0, vitest_1.expect)(gate.has("verifySession")).toBe(true);
|
|
197
|
+
(0, vitest_1.expect)(gate.has("authenticate")).toBe(true);
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
116
203
|
});
|
|
@@ -46,7 +46,11 @@ exports.loadFailures = loadFailures;
|
|
|
46
46
|
exports.failureStats = failureStats;
|
|
47
47
|
exports.formatFailureStats = formatFailureStats;
|
|
48
48
|
const fs = __importStar(require("fs"));
|
|
49
|
-
const
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
// 统一写入路径:与 failure-corpus.ts 一致,落在项目级 .progmune_corpus/emitter-failures/
|
|
51
|
+
// (旧路径为仓库根 failure-corpus/,已废弃——避免两套语料并存)
|
|
52
|
+
const CORPUS_DIR = path.resolve(process.env.PROGMUNE_CORPUS_DIR ||
|
|
53
|
+
path.resolve(process.env.PROGMUNE_PROJECT_DIR || process.cwd(), ".progmune_corpus"), "emitter-failures");
|
|
50
54
|
/** Classify a compile error string into a root cause. */
|
|
51
55
|
/** Classify a compile error into a root cause category. */
|
|
52
56
|
/** @requires ERROR_STRING @produces ROOT_CAUSE */
|
package/dist/sdk.js
CHANGED
|
@@ -20,7 +20,7 @@ const risk_model_1 = require("./risk-model");
|
|
|
20
20
|
const protocol_knowledge_1 = require("./protocol-knowledge");
|
|
21
21
|
const evidence_repository_1 = require("./evidence-repository");
|
|
22
22
|
/** Runtime version — stable public identifier. Internal layers evolve underneath. */
|
|
23
|
-
exports.RUNTIME_VERSION = "3.7.
|
|
23
|
+
exports.RUNTIME_VERSION = "3.7.5";
|
|
24
24
|
function verify(filePath) {
|
|
25
25
|
const cert = (0, certify_1.certify)(filePath);
|
|
26
26
|
const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
|
package/dist/ssg-validator.js
CHANGED
|
@@ -163,6 +163,13 @@ function findFixPathStatic(rules, namespace, current, targetPreStates) {
|
|
|
163
163
|
nsFuncs.push({ name: fn, rule });
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
|
+
// 项目注解原语优先展开(修复路径输出真实函数名而非通用规则名);
|
|
167
|
+
// stable sort:无 displayName 的规则(内置/TS/Python 惯例)相对顺序不变
|
|
168
|
+
nsFuncs.sort((a, b) => {
|
|
169
|
+
const pa = a.rule.displayName ? 0 : 1;
|
|
170
|
+
const pb = b.rule.displayName ? 0 : 1;
|
|
171
|
+
return pa - pb;
|
|
172
|
+
});
|
|
166
173
|
// BFS
|
|
167
174
|
const startKey = [...new Set(current)].sort().join(",");
|
|
168
175
|
const visited = new Set();
|