c-next 0.2.16 → 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.
Files changed (118) hide show
  1. package/README.md +18 -2
  2. package/dist/index.js +8897 -6260
  3. package/dist/index.js.map +4 -4
  4. package/grammar/CNext.g4 +12 -0
  5. package/package.json +4 -2
  6. package/src/transpiler/Transpiler.ts +376 -48
  7. package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
  8. package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
  9. package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
  10. package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
  11. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +65 -10
  12. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
  13. package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
  14. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
  15. package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
  16. package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
  17. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
  18. package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
  19. package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
  20. package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
  21. package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
  22. package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
  23. package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
  24. package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
  25. package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
  26. package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
  27. package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
  28. package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
  29. package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
  30. package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
  31. package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
  32. package/src/transpiler/logic/parser/grammar/CNextParser.ts +1318 -1178
  33. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
  34. package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
  35. package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
  36. package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
  37. package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
  38. package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
  39. package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
  40. package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
  41. package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
  42. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
  43. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
  44. package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
  45. package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
  46. package/src/transpiler/logic/symbols/SymbolTable.ts +62 -2
  47. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +55 -4
  48. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
  49. package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
  50. package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
  51. package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
  52. package/src/transpiler/output/codegen/CodeGenerator.ts +196 -98
  53. package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
  54. package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
  55. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
  56. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +163 -35
  57. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  58. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
  59. package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
  60. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +97 -14
  61. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
  62. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
  63. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
  64. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
  65. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
  66. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
  67. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
  68. package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
  69. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
  70. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
  71. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
  72. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
  73. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
  74. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
  75. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
  76. package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
  77. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +76 -58
  78. package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
  79. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
  80. package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
  81. package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
  82. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
  83. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
  84. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
  85. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
  86. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
  87. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
  88. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +240 -42
  89. package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
  90. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
  91. package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
  92. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
  93. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
  94. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
  95. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
  96. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +322 -9
  97. package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
  98. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
  99. package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
  100. package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
  101. package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
  102. package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
  103. package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
  104. package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
  105. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
  106. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +140 -24
  107. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
  108. package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
  109. package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
  110. package/src/transpiler/state/CodeGenState.ts +91 -22
  111. package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
  112. package/src/transpiler/types/ICachedFileEntry.ts +7 -0
  113. package/src/utils/LiteralUtils.ts +23 -0
  114. package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
  115. package/src/utils/cache/CacheManager.ts +13 -2
  116. package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
  117. package/src/utils/constants/TypeConstants.ts +13 -0
  118. package/src/utils/types/IParameterSymbol.ts +1 -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
- it("generates memcpy for valid slice assignment", () => {
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(10),
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: "10", start: { line: 1 } } as never,
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
- expect(result).toBe("memcpy(&buffer[0], &source, 10);");
266
- expect(CodeGenState.needsString).toBe(true);
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("generates memcpy for string slice", () => {
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(10),
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: "10", start: { line: 1 } } as never,
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("memcpy(&str[5], &data, 10);");
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,
@@ -26,6 +26,7 @@ import ArrayDimensionUtils from "./ArrayDimensionUtils";
26
26
  import QualifiedNameGenerator from "../../utils/QualifiedNameGenerator";
27
27
  import CodeGenState from "../../../../state/CodeGenState";
28
28
  import ScopeUtils from "../../../../../utils/ScopeUtils";
29
+ import VariableModifierBuilder from "../../helpers/VariableModifierBuilder";
29
30
 
30
31
  /**
31
32
  * Generate initializer expression for a variable declaration.
@@ -38,10 +39,12 @@ function generateInitializer(
38
39
  ): string {
39
40
  if (varDecl.expression()) {
40
41
  // Issue #872: Set expectedType for MISRA 7.2 U suffix compliance
42
+ // Issue #992: withDeclarationInit suppresses compound literals at file scope (GCC 9-12 compat)
41
43
  const typeName = orchestrator.generateType(varDecl.type());
42
- return CodeGenState.withExpectedType(
43
- typeName,
44
- () => ` = ${orchestrator.generateExpression(varDecl.expression()!)}`,
44
+ return CodeGenState.withExpectedType(typeName, () =>
45
+ CodeGenState.withDeclarationInit(
46
+ () => ` = ${orchestrator.generateExpression(varDecl.expression()!)}`,
47
+ ),
45
48
  );
46
49
  }
47
50
  // ADR-015: Zero initialization for uninitialized scope variables
@@ -204,12 +207,23 @@ function generateRegularVariable(
204
207
  orchestrator.markOpaqueScopeVariable(fullName);
205
208
  }
206
209
 
207
- // Issue #282: Add 'const' modifier for const variables
210
+ // Issue #998: Use VariableModifierBuilder for consistent modifier handling
211
+ // This handles const, atomic, volatile, and validates mutual exclusion
212
+ // Scope variables are always at file scope (not in function body), no initializer for modifier purposes
213
+ const modifiers = VariableModifierBuilder.build(
214
+ varDecl,
215
+ false, // inFunctionBody - scope vars are file scope
216
+ false, // hasInitializer - doesn't affect volatile/atomic handling
217
+ false, // cppMode - doesn't affect volatile/atomic handling
218
+ );
219
+ // For scope variables: static for private, no modifier for public
220
+ // Then add volatile (from atomic or volatile keyword), then const
221
+ const staticPrefix = isPrivate ? "static " : "";
222
+ const volatilePrefix = modifiers.atomic || modifiers.volatile;
208
223
  const constPrefix = isConst ? "const " : "";
209
- const prefix = isPrivate ? "static " : "";
210
224
 
211
225
  // Build declaration with all dimensions
212
- let decl = `${prefix}${constPrefix}${type} ${fullName}`;
226
+ let decl = `${staticPrefix}${volatilePrefix}${constPrefix}${type} ${fullName}`;
213
227
  decl += ArrayDimensionUtils.generateArrayTypeDimension(
214
228
  arrayTypeCtx,
215
229
  orchestrator,
@@ -225,7 +239,12 @@ function generateRegularVariable(
225
239
 
226
240
  // Issue #948: Opaque types use NULL initialization instead of {0}
227
241
  // Issue #958: External typedef struct types also use NULL initialization
228
- if (isOpaque || isExternalStruct) {
242
+ // Issue #996: ...but only for SCALAR handles, which are single pointers. An
243
+ // *array* of opaque handles needs a brace initializer ({0}), not a scalar
244
+ // NULL. Route arrays through generateInitializer, which uses
245
+ // getZeroInitializer(type, isArray) — the single source of truth for
246
+ // zero-initialization (ADR-015).
247
+ if ((isOpaque || isExternalStruct) && !isArray) {
229
248
  decl += " = NULL";
230
249
  } else {
231
250
  decl += generateInitializer(varDecl, isArray, orchestrator);