c-next 0.2.17 → 0.2.18
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/README.md +2 -2
- package/dist/index.js +7645 -5941
- package/dist/index.js.map +4 -4
- package/grammar/CNext.g4 +8 -0
- package/package.json +1 -3
- package/src/transpiler/Transpiler.ts +286 -26
- package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
- package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
- package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +8 -0
- package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
- package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
- package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
- package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
- package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
- package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
- package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
- package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
- package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
- package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
- package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +1257 -1185
- package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
- package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
- package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
- package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
- package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
- package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
- package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
- package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
- package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
- package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
- package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +45 -0
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
- package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
- package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +45 -4
- package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
- package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +13 -1
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +86 -14
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
- package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
- package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
- package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
- package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
- package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +28 -15
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
- package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +16 -13
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
- package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
- package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
- package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +65 -24
- package/src/transpiler/state/CodeGenState.ts +20 -16
- package/src/transpiler/types/ICachedFileEntry.ts +7 -0
- package/src/utils/cache/CacheManager.ts +13 -2
- package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
- package/src/utils/constants/TypeConstants.ts +13 -0
|
@@ -16,6 +16,22 @@ vi.mock("../../../TypeValidator", () => ({
|
|
|
16
16
|
},
|
|
17
17
|
}));
|
|
18
18
|
|
|
19
|
+
// Slice codegen resolves the source value's type (Issue #1081 review) — mock it
|
|
20
|
+
// so slice tests can control the source type independently of a real parse tree.
|
|
21
|
+
const { mockGetExpressionType, mockGetIntegerExpressionType } = vi.hoisted(
|
|
22
|
+
() => ({
|
|
23
|
+
mockGetExpressionType: vi.fn(),
|
|
24
|
+
mockGetIntegerExpressionType: vi.fn(),
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
vi.mock("../../../TypeResolver", () => ({
|
|
29
|
+
default: {
|
|
30
|
+
getExpressionType: mockGetExpressionType,
|
|
31
|
+
getIntegerExpressionType: mockGetIntegerExpressionType,
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
19
35
|
import arrayHandlers from "../ArrayHandlers";
|
|
20
36
|
import AssignmentKind from "../../AssignmentKind";
|
|
21
37
|
import IAssignmentContext from "../../IAssignmentContext";
|
|
@@ -42,6 +58,9 @@ function createMockContext(
|
|
|
42
58
|
cOp: "=",
|
|
43
59
|
generatedValue: "value",
|
|
44
60
|
targetCtx: {} as never,
|
|
61
|
+
// Truthy so slice codegen resolves the source type via the mocked
|
|
62
|
+
// TypeResolver.getExpressionType (Issue #1081 review).
|
|
63
|
+
valueCtx: {} as never,
|
|
45
64
|
hasThis: false,
|
|
46
65
|
hasGlobal: false,
|
|
47
66
|
hasMemberAccess: false,
|
|
@@ -63,6 +82,9 @@ describe("ArrayHandlers", () => {
|
|
|
63
82
|
CodeGenState.reset();
|
|
64
83
|
HandlerTestUtils.setupMockGenerator();
|
|
65
84
|
HandlerTestUtils.setupMockSymbols();
|
|
85
|
+
// Default: a source whose direct type is null is also unresolved as a
|
|
86
|
+
// composite. Tests that exercise composite resolution override this.
|
|
87
|
+
mockGetIntegerExpressionType.mockReturnValue(null);
|
|
66
88
|
});
|
|
67
89
|
|
|
68
90
|
describe("handler registration", () => {
|
|
@@ -241,32 +263,231 @@ describe("ArrayHandlers", () => {
|
|
|
241
263
|
const getHandler = () =>
|
|
242
264
|
arrayHandlers.find(([kind]) => kind === AssignmentKind.ARRAY_SLICE)?.[1];
|
|
243
265
|
|
|
244
|
-
|
|
266
|
+
// Every unrolled slice copy is prefixed with a comment naming the MISRA
|
|
267
|
+
// rule it satisfies and why the codegen looks the way it does (Issue #1081).
|
|
268
|
+
// Mirror production sliceUnrollComment: the rule is cited ONLY when an
|
|
269
|
+
// equivalent memcpy would pass incompatible pointer types.
|
|
270
|
+
const sliceComment = (destCType: string, srcCType: string) =>
|
|
271
|
+
"/* MISRA C:2012 Rule 21.15: slice copy unrolled to per-element writes " +
|
|
272
|
+
`(memcpy would pass incompatible pointer types: ${destCType}* vs ${srcCType}*). */`;
|
|
273
|
+
|
|
274
|
+
// Default the source type to a wide unsigned; tests override per case.
|
|
275
|
+
beforeEach(() => mockGetExpressionType.mockReturnValue("u64"));
|
|
276
|
+
|
|
277
|
+
// Issue #1081: slice assignment lowers to per-element little-endian writes
|
|
278
|
+
// (no memcpy), keeping MISRA C:2012 Rule 21.15 (compatible memcpy pointers)
|
|
279
|
+
// satisfied because no incompatible pointer punning is emitted.
|
|
280
|
+
it("generates unrolled byte writes for a u8 slice (no memcpy/string.h)", () => {
|
|
281
|
+
mockGetExpressionType.mockReturnValue("u32");
|
|
245
282
|
HandlerTestUtils.setupMockTypeRegistry([
|
|
246
|
-
["buffer", { arrayDimensions: [100], baseType: "u8" }],
|
|
283
|
+
["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
|
|
247
284
|
]);
|
|
248
285
|
HandlerTestUtils.setupMockGenerator({
|
|
249
286
|
tryEvaluateConstant: vi
|
|
250
287
|
.fn()
|
|
251
288
|
.mockReturnValueOnce(0)
|
|
252
|
-
.mockReturnValueOnce(
|
|
289
|
+
.mockReturnValueOnce(4),
|
|
253
290
|
});
|
|
254
291
|
const ctx = createMockContext({
|
|
255
292
|
identifiers: ["buffer"],
|
|
256
293
|
subscripts: [
|
|
257
294
|
{ mockValue: "0", start: { line: 1 } } as never,
|
|
258
|
-
{ mockValue: "
|
|
295
|
+
{ mockValue: "4", start: { line: 1 } } as never,
|
|
259
296
|
],
|
|
260
297
|
generatedValue: "source",
|
|
261
298
|
});
|
|
262
299
|
|
|
263
300
|
const result = getHandler()!(ctx);
|
|
264
301
|
|
|
265
|
-
|
|
266
|
-
|
|
302
|
+
// The source is materialized into a single unsigned temp so it is read
|
|
303
|
+
// exactly once (Issue #1085 review, Finding 2).
|
|
304
|
+
expect(result).toBe(
|
|
305
|
+
`${sliceComment("uint8_t", "uint32_t")}\n` +
|
|
306
|
+
"const uint32_t _tmp0 = (uint32_t)(source);\n" +
|
|
307
|
+
"buffer[0] = (uint8_t)(_tmp0);\n" +
|
|
308
|
+
"buffer[1] = (uint8_t)(_tmp0 >> 8U);\n" +
|
|
309
|
+
"buffer[2] = (uint8_t)(_tmp0 >> 16U);\n" +
|
|
310
|
+
"buffer[3] = (uint8_t)(_tmp0 >> 24U);",
|
|
311
|
+
);
|
|
312
|
+
// No memcpy means <string.h> is not required.
|
|
313
|
+
expect(CodeGenState.needsString).toBe(false);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("writes at element granularity for a u16 slice (offset = element index)", () => {
|
|
317
|
+
mockGetExpressionType.mockReturnValue("u64");
|
|
318
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
319
|
+
["arr16", { arrayDimensions: [16], baseType: "u16", bitWidth: 16 }],
|
|
320
|
+
]);
|
|
321
|
+
HandlerTestUtils.setupMockGenerator({
|
|
322
|
+
tryEvaluateConstant: vi
|
|
323
|
+
.fn()
|
|
324
|
+
.mockReturnValueOnce(0)
|
|
325
|
+
.mockReturnValueOnce(8),
|
|
326
|
+
});
|
|
327
|
+
const ctx = createMockContext({
|
|
328
|
+
identifiers: ["arr16"],
|
|
329
|
+
subscripts: [
|
|
330
|
+
{ mockValue: "0", start: { line: 1 } } as never,
|
|
331
|
+
{ mockValue: "8", start: { line: 1 } } as never,
|
|
332
|
+
],
|
|
333
|
+
generatedValue: "value",
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const result = getHandler()!(ctx);
|
|
337
|
+
|
|
338
|
+
expect(result).toBe(
|
|
339
|
+
`${sliceComment("uint16_t", "uint64_t")}\n` +
|
|
340
|
+
"const uint64_t _tmp0 = (uint64_t)(value);\n" +
|
|
341
|
+
"arr16[0] = (uint16_t)(_tmp0);\n" +
|
|
342
|
+
"arr16[1] = (uint16_t)(_tmp0 >> 16U);\n" +
|
|
343
|
+
"arr16[2] = (uint16_t)(_tmp0 >> 32U);\n" +
|
|
344
|
+
"arr16[3] = (uint16_t)(_tmp0 >> 48U);",
|
|
345
|
+
);
|
|
267
346
|
});
|
|
268
347
|
|
|
269
|
-
it("
|
|
348
|
+
it("omits the rule citation when source and element types match (no 21.15)", () => {
|
|
349
|
+
mockGetExpressionType.mockReturnValue("u32");
|
|
350
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
351
|
+
["arr32", { arrayDimensions: [16], baseType: "u32", bitWidth: 32 }],
|
|
352
|
+
]);
|
|
353
|
+
HandlerTestUtils.setupMockGenerator({
|
|
354
|
+
tryEvaluateConstant: vi
|
|
355
|
+
.fn()
|
|
356
|
+
.mockReturnValueOnce(0)
|
|
357
|
+
.mockReturnValueOnce(4),
|
|
358
|
+
});
|
|
359
|
+
const ctx = createMockContext({
|
|
360
|
+
identifiers: ["arr32"],
|
|
361
|
+
subscripts: [
|
|
362
|
+
{ mockValue: "0", start: { line: 1 } } as never,
|
|
363
|
+
{ mockValue: "4", start: { line: 1 } } as never,
|
|
364
|
+
],
|
|
365
|
+
generatedValue: "value",
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const result = getHandler()!(ctx);
|
|
369
|
+
|
|
370
|
+
// u32[] <- u32: an equivalent memcpy would be compliant, so no comment.
|
|
371
|
+
expect(result).toBe("arr32[0] = (uint32_t)(value);");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("accepts an in-bounds wide-element slice at a non-zero offset (Finding 1)", () => {
|
|
375
|
+
mockGetExpressionType.mockReturnValue("u64");
|
|
376
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
377
|
+
["arr64", { arrayDimensions: [8], baseType: "u64", bitWidth: 64 }],
|
|
378
|
+
]);
|
|
379
|
+
HandlerTestUtils.setupMockGenerator({
|
|
380
|
+
tryEvaluateConstant: vi
|
|
381
|
+
.fn()
|
|
382
|
+
.mockReturnValueOnce(4)
|
|
383
|
+
.mockReturnValueOnce(8),
|
|
384
|
+
});
|
|
385
|
+
const ctx = createMockContext({
|
|
386
|
+
identifiers: ["arr64"],
|
|
387
|
+
subscripts: [
|
|
388
|
+
{ mockValue: "4", start: { line: 1 } } as never,
|
|
389
|
+
{ mockValue: "8", start: { line: 1 } } as never,
|
|
390
|
+
],
|
|
391
|
+
generatedValue: "value",
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const result = getHandler()!(ctx);
|
|
395
|
+
|
|
396
|
+
// Element span is offset(4) + 1 element = 5 <= capacity 8. The old check
|
|
397
|
+
// compared byte length against element capacity (4 + 8 = 12 > 8) and
|
|
398
|
+
// wrongly rejected this in-bounds slice. Single element → no temp;
|
|
399
|
+
// same-type u64[] <- u64 → no rule citation.
|
|
400
|
+
expect(result).toBe("arr64[4] = (uint64_t)(value);");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("reports the element span (not bytes) in the out-of-bounds message", () => {
|
|
404
|
+
mockGetExpressionType.mockReturnValue("u64");
|
|
405
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
406
|
+
["arr16", { arrayDimensions: [4], baseType: "u16", bitWidth: 16 }],
|
|
407
|
+
]);
|
|
408
|
+
HandlerTestUtils.setupMockGenerator({
|
|
409
|
+
tryEvaluateConstant: vi
|
|
410
|
+
.fn()
|
|
411
|
+
.mockReturnValueOnce(2)
|
|
412
|
+
.mockReturnValueOnce(8),
|
|
413
|
+
});
|
|
414
|
+
const ctx = createMockContext({
|
|
415
|
+
identifiers: ["arr16"],
|
|
416
|
+
subscripts: [
|
|
417
|
+
{ mockValue: "2", start: { line: 7 } } as never,
|
|
418
|
+
{ mockValue: "8", start: { line: 7 } } as never,
|
|
419
|
+
],
|
|
420
|
+
generatedValue: "value",
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// offset 2 + (8 bytes / 2) = 6 elements > capacity 4.
|
|
424
|
+
expect(() => getHandler()!(ctx)).toThrow(
|
|
425
|
+
"offset(2) + 4 element(s) = 6 exceeds buffer capacity(4)",
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it("casts a signed source to unsigned once in the temp (MISRA 10.1)", () => {
|
|
430
|
+
mockGetExpressionType.mockReturnValue("i32");
|
|
431
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
432
|
+
["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
|
|
433
|
+
]);
|
|
434
|
+
HandlerTestUtils.setupMockGenerator({
|
|
435
|
+
tryEvaluateConstant: vi
|
|
436
|
+
.fn()
|
|
437
|
+
.mockReturnValueOnce(0)
|
|
438
|
+
.mockReturnValueOnce(4),
|
|
439
|
+
});
|
|
440
|
+
const ctx = createMockContext({
|
|
441
|
+
identifiers: ["buffer"],
|
|
442
|
+
subscripts: [
|
|
443
|
+
{ mockValue: "0", start: { line: 1 } } as never,
|
|
444
|
+
{ mockValue: "4", start: { line: 1 } } as never,
|
|
445
|
+
],
|
|
446
|
+
generatedValue: "value",
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const result = getHandler()!(ctx);
|
|
450
|
+
|
|
451
|
+
// The signed source is cast to its same-width unsigned type once, in the
|
|
452
|
+
// temp declaration, so every shift on the temp is MISRA Rule 10.1-clean.
|
|
453
|
+
expect(result).toBe(
|
|
454
|
+
`${sliceComment("uint8_t", "int32_t")}\n` +
|
|
455
|
+
"const uint32_t _tmp0 = (uint32_t)(value);\n" +
|
|
456
|
+
"buffer[0] = (uint8_t)(_tmp0);\n" +
|
|
457
|
+
"buffer[1] = (uint8_t)(_tmp0 >> 8U);\n" +
|
|
458
|
+
"buffer[2] = (uint8_t)(_tmp0 >> 16U);\n" +
|
|
459
|
+
"buffer[3] = (uint8_t)(_tmp0 >> 24U);",
|
|
460
|
+
);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("uses the signed-element double-cast for a signed destination (MISRA 10.8)", () => {
|
|
464
|
+
mockGetExpressionType.mockReturnValue("i32");
|
|
465
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
466
|
+
["arrI", { arrayDimensions: [16], baseType: "i32", bitWidth: 32 }],
|
|
467
|
+
]);
|
|
468
|
+
HandlerTestUtils.setupMockGenerator({
|
|
469
|
+
tryEvaluateConstant: vi
|
|
470
|
+
.fn()
|
|
471
|
+
.mockReturnValueOnce(0)
|
|
472
|
+
.mockReturnValueOnce(4),
|
|
473
|
+
});
|
|
474
|
+
const ctx = createMockContext({
|
|
475
|
+
identifiers: ["arrI"],
|
|
476
|
+
subscripts: [
|
|
477
|
+
{ mockValue: "0", start: { line: 1 } } as never,
|
|
478
|
+
{ mockValue: "4", start: { line: 1 } } as never,
|
|
479
|
+
],
|
|
480
|
+
generatedValue: "value",
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const result = getHandler()!(ctx);
|
|
484
|
+
|
|
485
|
+
// i32[] <- i32: same type, so no rule citation; dest cast still applies.
|
|
486
|
+
expect(result).toBe("arrI[0] = (int32_t)(uint32_t)(value);");
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("generates double-cast char writes for a string slice (MISRA 10.8)", () => {
|
|
490
|
+
mockGetExpressionType.mockReturnValue("u16");
|
|
270
491
|
HandlerTestUtils.setupMockTypeRegistry([
|
|
271
492
|
[
|
|
272
493
|
"str",
|
|
@@ -282,20 +503,267 @@ describe("ArrayHandlers", () => {
|
|
|
282
503
|
tryEvaluateConstant: vi
|
|
283
504
|
.fn()
|
|
284
505
|
.mockReturnValueOnce(5)
|
|
285
|
-
.mockReturnValueOnce(
|
|
506
|
+
.mockReturnValueOnce(2),
|
|
286
507
|
});
|
|
287
508
|
const ctx = createMockContext({
|
|
288
509
|
identifiers: ["str"],
|
|
289
510
|
subscripts: [
|
|
290
511
|
{ mockValue: "5", start: { line: 1 } } as never,
|
|
291
|
-
{ mockValue: "
|
|
512
|
+
{ mockValue: "2", start: { line: 1 } } as never,
|
|
292
513
|
],
|
|
293
514
|
generatedValue: "data",
|
|
294
515
|
});
|
|
295
516
|
|
|
296
517
|
const result = getHandler()!(ctx);
|
|
297
518
|
|
|
298
|
-
expect(result).toBe(
|
|
519
|
+
expect(result).toBe(
|
|
520
|
+
`${sliceComment("char", "uint16_t")}\n` +
|
|
521
|
+
"const uint16_t _tmp0 = (uint16_t)(data);\n" +
|
|
522
|
+
"str[5] = (char)(uint8_t)(_tmp0);\n" +
|
|
523
|
+
"str[6] = (char)(uint8_t)(_tmp0 >> 8U);",
|
|
524
|
+
);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it("sizes the temp to the slice length for an unresolved source type", () => {
|
|
528
|
+
mockGetExpressionType.mockReturnValue(null); // e.g. a computed expression
|
|
529
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
530
|
+
["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
|
|
531
|
+
]);
|
|
532
|
+
HandlerTestUtils.setupMockGenerator({
|
|
533
|
+
tryEvaluateConstant: vi
|
|
534
|
+
.fn()
|
|
535
|
+
.mockReturnValueOnce(0)
|
|
536
|
+
.mockReturnValueOnce(4),
|
|
537
|
+
});
|
|
538
|
+
const ctx = createMockContext({
|
|
539
|
+
identifiers: ["buffer"],
|
|
540
|
+
subscripts: [
|
|
541
|
+
{ mockValue: "0", start: { line: 1 } } as never,
|
|
542
|
+
{ mockValue: "4", start: { line: 1 } } as never,
|
|
543
|
+
],
|
|
544
|
+
generatedValue: "expr",
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const result = getHandler()!(ctx);
|
|
548
|
+
|
|
549
|
+
// Unknown source: NO Rule 21.15 citation — we cannot prove the source type
|
|
550
|
+
// is incompatible with the destination element, so asserting "incompatible
|
|
551
|
+
// pointer types" would be false (Issue #1085 review, Finding #1). The temp
|
|
552
|
+
// is still sized to the 4-byte slice length (uint32_t), not the widest
|
|
553
|
+
// type — so the cast does not widen a composite expression like `a + b`
|
|
554
|
+
// (MISRA Rule 10.8), while every shift on the temp stays unsigned (10.1)
|
|
555
|
+
// and in range (Finding 3).
|
|
556
|
+
expect(result).toBe(
|
|
557
|
+
"const uint32_t _tmp0 = (uint32_t)(expr);\n" +
|
|
558
|
+
"buffer[0] = (uint8_t)(_tmp0);\n" +
|
|
559
|
+
"buffer[1] = (uint8_t)(_tmp0 >> 8U);\n" +
|
|
560
|
+
"buffer[2] = (uint8_t)(_tmp0 >> 16U);\n" +
|
|
561
|
+
"buffer[3] = (uint8_t)(_tmp0 >> 24U);",
|
|
562
|
+
);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("two-steps a composite signed source so no composite is cast (Rule 10.8)", () => {
|
|
566
|
+
// Direct type unresolved (composite), but resolvable as a signed i32 via
|
|
567
|
+
// Rule 10.4's same-category guarantee. The source must be bound to its
|
|
568
|
+
// own signed type first, then reinterpreted — the unsigned cast lands on
|
|
569
|
+
// the simple temp, never on the `a + b` composite.
|
|
570
|
+
mockGetExpressionType.mockReturnValue(null);
|
|
571
|
+
mockGetIntegerExpressionType.mockReturnValue("i32");
|
|
572
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
573
|
+
["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
|
|
574
|
+
]);
|
|
575
|
+
HandlerTestUtils.setupMockGenerator({
|
|
576
|
+
tryEvaluateConstant: vi
|
|
577
|
+
.fn()
|
|
578
|
+
.mockReturnValueOnce(0)
|
|
579
|
+
.mockReturnValueOnce(4),
|
|
580
|
+
});
|
|
581
|
+
const ctx = createMockContext({
|
|
582
|
+
identifiers: ["buffer"],
|
|
583
|
+
subscripts: [
|
|
584
|
+
{ mockValue: "0", start: { line: 1 } } as never,
|
|
585
|
+
{ mockValue: "4", start: { line: 1 } } as never,
|
|
586
|
+
],
|
|
587
|
+
generatedValue: "a + b",
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
const result = getHandler()!(ctx);
|
|
591
|
+
|
|
592
|
+
expect(result).toBe(
|
|
593
|
+
"/* MISRA C:2012 Rule 21.15: slice copy unrolled to per-element writes " +
|
|
594
|
+
"(memcpy would pass incompatible pointer types: uint8_t* vs int32_t*). */\n" +
|
|
595
|
+
"const int32_t _tmp0 = a + b;\n" +
|
|
596
|
+
"const uint32_t _tmp1 = (uint32_t)_tmp0;\n" +
|
|
597
|
+
"buffer[0] = (uint8_t)(_tmp1);\n" +
|
|
598
|
+
"buffer[1] = (uint8_t)(_tmp1 >> 8U);\n" +
|
|
599
|
+
"buffer[2] = (uint8_t)(_tmp1 >> 16U);\n" +
|
|
600
|
+
"buffer[3] = (uint8_t)(_tmp1 >> 24U);",
|
|
601
|
+
);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it("throws when slice length is not a multiple of the element size", () => {
|
|
605
|
+
mockGetExpressionType.mockReturnValue("u64");
|
|
606
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
607
|
+
["arr16", { arrayDimensions: [16], baseType: "u16", bitWidth: 16 }],
|
|
608
|
+
]);
|
|
609
|
+
HandlerTestUtils.setupMockGenerator({
|
|
610
|
+
tryEvaluateConstant: vi
|
|
611
|
+
.fn()
|
|
612
|
+
.mockReturnValueOnce(0)
|
|
613
|
+
.mockReturnValueOnce(3),
|
|
614
|
+
});
|
|
615
|
+
const ctx = createMockContext({
|
|
616
|
+
identifiers: ["arr16"],
|
|
617
|
+
subscripts: [
|
|
618
|
+
{ mockValue: "0", start: { line: 7 } } as never,
|
|
619
|
+
{ mockValue: "3", start: { line: 7 } } as never,
|
|
620
|
+
],
|
|
621
|
+
generatedValue: "value",
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
expect(() => getHandler()!(ctx)).toThrow(
|
|
625
|
+
"must be a multiple of the element size",
|
|
626
|
+
);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it("throws when slice length exceeds the source value width", () => {
|
|
630
|
+
mockGetExpressionType.mockReturnValue("u32"); // 4-byte source
|
|
631
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
632
|
+
["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
|
|
633
|
+
]);
|
|
634
|
+
HandlerTestUtils.setupMockGenerator({
|
|
635
|
+
tryEvaluateConstant: vi
|
|
636
|
+
.fn()
|
|
637
|
+
.mockReturnValueOnce(0)
|
|
638
|
+
.mockReturnValueOnce(8), // copying 8 bytes from a 4-byte value
|
|
639
|
+
});
|
|
640
|
+
const ctx = createMockContext({
|
|
641
|
+
identifiers: ["buffer"],
|
|
642
|
+
subscripts: [
|
|
643
|
+
{ mockValue: "0", start: { line: 8 } } as never,
|
|
644
|
+
{ mockValue: "8", start: { line: 8 } } as never,
|
|
645
|
+
],
|
|
646
|
+
generatedValue: "value",
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
expect(() => getHandler()!(ctx)).toThrow(
|
|
650
|
+
"exceeds the source value width",
|
|
651
|
+
);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it("throws on a non-integer (float) slice source", () => {
|
|
655
|
+
mockGetExpressionType.mockReturnValue("f32");
|
|
656
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
657
|
+
["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
|
|
658
|
+
]);
|
|
659
|
+
HandlerTestUtils.setupMockGenerator({
|
|
660
|
+
tryEvaluateConstant: vi
|
|
661
|
+
.fn()
|
|
662
|
+
.mockReturnValueOnce(0)
|
|
663
|
+
.mockReturnValueOnce(4),
|
|
664
|
+
});
|
|
665
|
+
const ctx = createMockContext({
|
|
666
|
+
identifiers: ["buffer"],
|
|
667
|
+
subscripts: [
|
|
668
|
+
{ mockValue: "0", start: { line: 9 } } as never,
|
|
669
|
+
{ mockValue: "4", start: { line: 9 } } as never,
|
|
670
|
+
],
|
|
671
|
+
generatedValue: "fval",
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
expect(() => getHandler()!(ctx)).toThrow(
|
|
675
|
+
"source must be an integer value",
|
|
676
|
+
);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
it("throws on slice assignment into a float array", () => {
|
|
680
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
681
|
+
["arrF", { arrayDimensions: [16], baseType: "f32", bitWidth: 32 }],
|
|
682
|
+
]);
|
|
683
|
+
HandlerTestUtils.setupMockGenerator({
|
|
684
|
+
tryEvaluateConstant: vi
|
|
685
|
+
.fn()
|
|
686
|
+
.mockReturnValueOnce(0)
|
|
687
|
+
.mockReturnValueOnce(4),
|
|
688
|
+
});
|
|
689
|
+
const ctx = createMockContext({
|
|
690
|
+
identifiers: ["arrF"],
|
|
691
|
+
subscripts: [
|
|
692
|
+
{ mockValue: "0", start: { line: 9 } } as never,
|
|
693
|
+
{ mockValue: "4", start: { line: 9 } } as never,
|
|
694
|
+
],
|
|
695
|
+
generatedValue: "value",
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
expect(() => getHandler()!(ctx)).toThrow(
|
|
699
|
+
"Slice assignment is not supported for element type",
|
|
700
|
+
);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it("rejects a negative literal that does not fit the slice (mirror of the positive overflow check)", () => {
|
|
704
|
+
// A negative literal types as null via getExpressionType (unary minus), but
|
|
705
|
+
// is still a compile-time constant. It must be fit-checked like a positive
|
|
706
|
+
// one: buf[0,1] <- -300 cannot fit a 1-byte slice (-300 < -128) and must be
|
|
707
|
+
// rejected, not silently truncated to (uint8_t)(-300) (Issue #1085 review).
|
|
708
|
+
mockGetExpressionType.mockReturnValue(null);
|
|
709
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
710
|
+
["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
|
|
711
|
+
]);
|
|
712
|
+
HandlerTestUtils.setupMockGenerator({
|
|
713
|
+
tryEvaluateConstant: vi
|
|
714
|
+
.fn()
|
|
715
|
+
.mockReturnValueOnce(0) // offset
|
|
716
|
+
.mockReturnValueOnce(1) // length (1-byte slice)
|
|
717
|
+
.mockReturnValue(-300), // source value (folded; re-read by resolveLiteralSliceSource)
|
|
718
|
+
});
|
|
719
|
+
const ctx = createMockContext({
|
|
720
|
+
identifiers: ["buffer"],
|
|
721
|
+
subscripts: [
|
|
722
|
+
{ mockValue: "0", start: { line: 7 } } as never,
|
|
723
|
+
{ mockValue: "1", start: { line: 7 } } as never,
|
|
724
|
+
],
|
|
725
|
+
generatedValue: "-300",
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
expect(() => getHandler()!(ctx)).toThrow(
|
|
729
|
+
"Slice assignment literal value (-300) does not fit in the 1-byte slice",
|
|
730
|
+
);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it("serializes an in-range negative literal as two's-complement little-endian bytes", () => {
|
|
734
|
+
// -1 fits a 4-byte slice (-2^31 <= -1 < 2^32), so it is accepted and written
|
|
735
|
+
// as 0xFFFFFFFF little-endian. The literal types to the slice width (u32),
|
|
736
|
+
// so an equivalent memcpy would be incompatible -> Rule 21.15 is cited.
|
|
737
|
+
mockGetExpressionType.mockReturnValue(null);
|
|
738
|
+
HandlerTestUtils.setupMockTypeRegistry([
|
|
739
|
+
["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
|
|
740
|
+
]);
|
|
741
|
+
HandlerTestUtils.setupMockGenerator({
|
|
742
|
+
tryEvaluateConstant: vi
|
|
743
|
+
.fn()
|
|
744
|
+
.mockReturnValueOnce(0) // offset
|
|
745
|
+
.mockReturnValueOnce(4) // length
|
|
746
|
+
.mockReturnValue(-1), // source value
|
|
747
|
+
});
|
|
748
|
+
const ctx = createMockContext({
|
|
749
|
+
identifiers: ["buffer"],
|
|
750
|
+
subscripts: [
|
|
751
|
+
{ mockValue: "0", start: { line: 1 } } as never,
|
|
752
|
+
{ mockValue: "4", start: { line: 1 } } as never,
|
|
753
|
+
],
|
|
754
|
+
generatedValue: "-1",
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
const result = getHandler()!(ctx);
|
|
758
|
+
|
|
759
|
+
expect(result).toBe(
|
|
760
|
+
`${sliceComment("uint8_t", "uint32_t")}\n` +
|
|
761
|
+
"const uint32_t _tmp0 = (uint32_t)(-1);\n" +
|
|
762
|
+
"buffer[0] = (uint8_t)(_tmp0);\n" +
|
|
763
|
+
"buffer[1] = (uint8_t)(_tmp0 >> 8U);\n" +
|
|
764
|
+
"buffer[2] = (uint8_t)(_tmp0 >> 16U);\n" +
|
|
765
|
+
"buffer[3] = (uint8_t)(_tmp0 >> 24U);",
|
|
766
|
+
);
|
|
299
767
|
});
|
|
300
768
|
|
|
301
769
|
it("throws on compound assignment", () => {
|
|
@@ -382,7 +850,7 @@ describe("ArrayHandlers", () => {
|
|
|
382
850
|
|
|
383
851
|
it("throws on out of bounds access", () => {
|
|
384
852
|
HandlerTestUtils.setupMockTypeRegistry([
|
|
385
|
-
["buffer", { arrayDimensions: [50], baseType: "u8" }],
|
|
853
|
+
["buffer", { arrayDimensions: [50], baseType: "u8", bitWidth: 8 }],
|
|
386
854
|
]);
|
|
387
855
|
HandlerTestUtils.setupMockGenerator({
|
|
388
856
|
tryEvaluateConstant: vi
|
|
@@ -214,6 +214,9 @@ interface IOrchestrator {
|
|
|
214
214
|
conditionType: string,
|
|
215
215
|
): void;
|
|
216
216
|
|
|
217
|
+
/** Reject an always-true literal loop condition (ADR-113 / #1075, E0707) */
|
|
218
|
+
validateLoopConditionNotAlwaysTrue(ctx: Parser.ExpressionContext): void;
|
|
219
|
+
|
|
217
220
|
/** Validate no function calls in condition (Issue #254, E0702) */
|
|
218
221
|
validateConditionNoFunctionCall(
|
|
219
222
|
ctx: Parser.ExpressionContext,
|
|
@@ -104,6 +104,29 @@ const _parameterExpectsAddressOf = (
|
|
|
104
104
|
return paramBaseType === argType;
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Resolve an argument expression's C-Next type for the address-of decision.
|
|
109
|
+
* Prefers the expression type, then the codegen variable registry, then the C
|
|
110
|
+
* symbol table — the last covers extern C globals (e.g. an lvgl font
|
|
111
|
+
* `lv_font_montserrat_32` of type `lv_font_t`, Issue #985) whose type only
|
|
112
|
+
* header symbol collection knows. Returns null when the type can't be resolved
|
|
113
|
+
* or the argument is already an address-of expression.
|
|
114
|
+
*/
|
|
115
|
+
const _resolveArgType = (
|
|
116
|
+
e: ExpressionContext,
|
|
117
|
+
argCode: string,
|
|
118
|
+
typeInfoBaseType: string | undefined,
|
|
119
|
+
orchestrator: IOrchestrator,
|
|
120
|
+
): string | null => {
|
|
121
|
+
const exprType = orchestrator.getExpressionType(e);
|
|
122
|
+
if (exprType) return exprType;
|
|
123
|
+
if (argCode.startsWith("&")) return null;
|
|
124
|
+
if (typeInfoBaseType) return typeInfoBaseType;
|
|
125
|
+
const cSymbol = CodeGenState.symbolTable?.getCSymbol(argCode);
|
|
126
|
+
if (cSymbol?.kind === "variable" && !cSymbol.isArray) return cSymbol.type;
|
|
127
|
+
return null;
|
|
128
|
+
};
|
|
129
|
+
|
|
107
130
|
/**
|
|
108
131
|
* Generate argument code for a C/C++ function call.
|
|
109
132
|
* Handles automatic address-of (&) for struct arguments passed to pointer params.
|
|
@@ -148,22 +171,12 @@ const _generateCFunctionArg = (
|
|
|
148
171
|
return wrapWithCppEnumCast(argCode, e, targetParam?.baseType, orchestrator);
|
|
149
172
|
}
|
|
150
173
|
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// Issue #322: If getExpressionType returns null (e.g., for this.member),
|
|
155
|
-
// fall back to looking up the generated code in the type registry
|
|
156
|
-
let isPointerVariable = false;
|
|
174
|
+
// Resolve the argument's type (expression type → variable registry → C symbol
|
|
175
|
+
// table for extern globals) to decide whether it needs address-of.
|
|
157
176
|
const typeInfo = CodeGenState.getVariableTypeInfo(argCode);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// Issue #895 Bug B: Check if variable was inferred as a pointer
|
|
164
|
-
if (typeInfo?.isPointer) {
|
|
165
|
-
isPointerVariable = true;
|
|
166
|
-
}
|
|
177
|
+
const argType = _resolveArgType(e, argCode, typeInfo?.baseType, orchestrator);
|
|
178
|
+
// Issue #895 Bug B: a variable already inferred as a pointer must not get `&`.
|
|
179
|
+
const isPointerVariable = typeInfo?.isPointer ?? false;
|
|
167
180
|
|
|
168
181
|
// Issue #948: Check if argument is an opaque scope variable (already a pointer)
|
|
169
182
|
// Issue #996: ...including an element of an opaque-handle array (arr[i])
|