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.
Files changed (81) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +7645 -5941
  3. package/dist/index.js.map +4 -4
  4. package/grammar/CNext.g4 +8 -0
  5. package/package.json +1 -3
  6. package/src/transpiler/Transpiler.ts +286 -26
  7. package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
  8. package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
  9. package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
  10. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +8 -0
  11. package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
  12. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
  13. package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
  14. package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
  15. package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
  16. package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
  17. package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
  18. package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
  19. package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
  20. package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
  21. package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
  22. package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
  23. package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
  24. package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
  25. package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
  26. package/src/transpiler/logic/parser/grammar/CNextParser.ts +1257 -1185
  27. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
  28. package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
  29. package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
  30. package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
  31. package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
  32. package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
  33. package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
  34. package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
  35. package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
  36. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
  37. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
  38. package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
  39. package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
  40. package/src/transpiler/logic/symbols/SymbolTable.ts +45 -0
  41. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
  42. package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
  43. package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
  44. package/src/transpiler/output/codegen/CodeGenerator.ts +45 -4
  45. package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
  46. package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
  47. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +13 -1
  48. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
  49. package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
  50. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +86 -14
  51. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
  52. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
  53. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
  54. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
  55. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
  56. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
  57. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
  58. package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
  59. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
  60. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
  61. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
  62. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
  63. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
  64. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +28 -15
  65. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
  66. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
  67. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
  68. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
  69. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
  70. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +16 -13
  71. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
  72. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
  73. package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
  74. package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
  75. package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
  76. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +65 -24
  77. package/src/transpiler/state/CodeGenState.ts +20 -16
  78. package/src/transpiler/types/ICachedFileEntry.ts +7 -0
  79. package/src/utils/cache/CacheManager.ts +13 -2
  80. package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
  81. package/src/utils/constants/TypeConstants.ts +13 -0
@@ -155,17 +155,14 @@ const generatePostfixExpression = (
155
155
  // Issue #895: Callback-compatible params need pointer semantics even in C++ mode
156
156
  const forcePointerSemantics = paramInfo?.forcePointerSemantics ?? false;
157
157
 
158
- // Issue #579: Check if we have subscript access on a non-array parameter
159
- const hasSubscriptOps = ops.some((op) => op.expression().length > 0);
160
- const isNonArrayParamWithSubscript =
161
- paramInfo && !paramInfo.isArray && !paramInfo.isStruct && hasSubscriptOps;
162
-
163
- let result: string;
164
- if (isNonArrayParamWithSubscript) {
165
- result = rootIdentifier!;
166
- } else {
167
- result = orchestrator.generatePrimaryExpr(primary);
168
- }
158
+ // Issue #1100: Subscripted parameters resolve through the normal primary
159
+ // expression path (ParameterDereferenceResolver), same as any other
160
+ // parameter reference. This is a no-op for array/struct/string params
161
+ // (already pointer-like, so `buf[idx]` is unaffected), and correctly
162
+ // dereferences a scalar parameter that became a pointer because it's
163
+ // modified elsewhere in the function, so bit access (`v[4]`) reads
164
+ // through the pointer instead of pointer-indexing past it.
165
+ const result: string = orchestrator.generatePrimaryExpr(primary);
169
166
 
170
167
  const primaryTypeInfo = rootIdentifier
171
168
  ? CodeGenState.getVariableTypeInfo(rootIdentifier)
@@ -1805,6 +1802,14 @@ const handleBitRangeSubscript = (
1805
1802
  orchestrator.generateExpression(exprs[1]),
1806
1803
  ]);
1807
1804
 
1805
+ // Issue #1094: resolve a const/macro width to its numeric value so the mask is
1806
+ // precomputed (byte-identical to a literal width) instead of a runtime
1807
+ // ((1U << W) - 1) — which is UB at full width (1U << 32) and uses the wrong
1808
+ // base type for >32-bit widths. The "U" suffix matches the literal path, which
1809
+ // generates bit widths under a size_t expectedType.
1810
+ const widthConst = orchestrator.tryEvaluateConstant(exprs[1]);
1811
+ const maskWidth = widthConst === undefined ? width : `${widthConst}U`;
1812
+
1808
1813
  const isFloatType =
1809
1814
  ctx.primaryTypeInfo?.baseType === "f32" ||
1810
1815
  ctx.primaryTypeInfo?.baseType === "f64";
@@ -1817,13 +1822,19 @@ const handleBitRangeSubscript = (
1817
1822
  baseType: ctx.primaryTypeInfo!.baseType,
1818
1823
  start,
1819
1824
  width,
1825
+ maskWidth,
1820
1826
  },
1821
1827
  state,
1822
1828
  orchestrator,
1823
1829
  effects,
1824
1830
  );
1825
1831
  } else {
1826
- const mask = orchestrator.generateBitMask(width);
1832
+ // Issue #1094: 64-bit operands need a 64-bit mask base (1ULL / wide hex) so
1833
+ // widths > 32 don't shift past a 32-bit literal's width.
1834
+ const is64BitOperand =
1835
+ ctx.primaryTypeInfo?.baseType === "u64" ||
1836
+ ctx.primaryTypeInfo?.baseType === "i64";
1837
+ const mask = orchestrator.generateBitMask(maskWidth, is64BitOperand);
1827
1838
  // Skip shift when start is 0 (either "0" or "0U" with MISRA suffix)
1828
1839
  let expr: string;
1829
1840
  if (start === "0" || start === "0U") {
@@ -1861,6 +1872,8 @@ interface IFloatBitRangeContext {
1861
1872
  baseType: string;
1862
1873
  start: string;
1863
1874
  width: string;
1875
+ /** Width formatted for mask generation (const-resolved when possible, #1094). */
1876
+ maskWidth: string;
1864
1877
  }
1865
1878
 
1866
1879
  /**
@@ -1892,7 +1905,7 @@ const handleFloatBitRange = (
1892
1905
  const floatType = getFloatTypeName(ctx.baseType);
1893
1906
  const intType = isF64 ? "uint64_t" : "uint32_t";
1894
1907
  const shadowName = `__bits_${ctx.rootIdentifier}`;
1895
- const mask = orchestrator.generateBitMask(ctx.width, isF64);
1908
+ const mask = orchestrator.generateBitMask(ctx.maskWidth, isF64);
1896
1909
 
1897
1910
  const needsDeclaration = !orchestrator.hasFloatBitShadow(shadowName);
1898
1911
  if (needsDeclaration) {
@@ -147,6 +147,7 @@ function createMockOrchestrator(overrides?: {
147
147
  ) => TTypeInfo | null;
148
148
  validateCrossScopeVisibility?: (scope: string, member: string) => void;
149
149
  generateBitMask?: (width: string, is64?: boolean) => string;
150
+ tryEvaluateConstant?: (ctx: unknown) => number | undefined;
150
151
  hasFloatBitShadow?: (name: string) => boolean;
151
152
  registerFloatBitShadow?: (name: string) => void;
152
153
  addPendingTempDeclaration?: (decl: string) => void;
@@ -172,7 +173,7 @@ function createMockOrchestrator(overrides?: {
172
173
  isCNextFunction: vi.fn(),
173
174
  isStructType: vi.fn(),
174
175
  getTypeName: vi.fn(),
175
- tryEvaluateConstant: vi.fn(),
176
+ tryEvaluateConstant: overrides?.tryEvaluateConstant ?? vi.fn(),
176
177
  getZeroInitializer: vi.fn(),
177
178
  getExpressionEnumType: vi.fn(),
178
179
  isIntegerExpression: vi.fn(),
@@ -1265,6 +1266,98 @@ describe("PostfixExpressionGenerator", () => {
1265
1266
  const result = generatePostfixExpression(ctx, input, state, orchestrator);
1266
1267
  expect(result.code).toBe("((val) & 0xFF)");
1267
1268
  });
1269
+
1270
+ // Issue #1094: a const/macro width must be resolved to its numeric value (with
1271
+ // a "U" suffix to match the literal path) before mask generation, instead of
1272
+ // being passed through as the identifier — otherwise the mask falls back to a
1273
+ // runtime ((1U << W) - 1) that is UB at full width.
1274
+ it("resolves a const width to a precomputed mask (#1094)", () => {
1275
+ const typeRegistry = new Map<string, TTypeInfo>([
1276
+ [
1277
+ "val",
1278
+ { baseType: "u32", bitWidth: 32, isArray: false, isConst: false },
1279
+ ],
1280
+ ]);
1281
+ const ctx = createMockPostfixExpressionContext("val", [
1282
+ createMockPostfixOp({
1283
+ expressions: [
1284
+ createMockExpression("0"),
1285
+ createMockExpression("WIDTH"),
1286
+ ],
1287
+ }),
1288
+ ]);
1289
+ const input = createMockInput({ typeRegistry });
1290
+ const state = createMockState();
1291
+ const generateBitMask = vi.fn(() => "0xFFFFFFFFU");
1292
+ const orchestrator = createMockOrchestrator({
1293
+ generatePrimaryExpr: () => "val",
1294
+ generateExpression: (ctx) => ctx.getText(),
1295
+ generateBitMask,
1296
+ tryEvaluateConstant: () => 32,
1297
+ });
1298
+
1299
+ const result = generatePostfixExpression(ctx, input, state, orchestrator);
1300
+ // Width resolved to "32U" (not the identifier "WIDTH"); u32 operand → not 64-bit.
1301
+ expect(generateBitMask).toHaveBeenCalledWith("32U", false);
1302
+ expect(result.code).toBe("((val) & 0xFFFFFFFFU)");
1303
+ });
1304
+
1305
+ it("passes the raw width when it is not a const (#1094)", () => {
1306
+ const typeRegistry = new Map<string, TTypeInfo>([
1307
+ [
1308
+ "val",
1309
+ { baseType: "u32", bitWidth: 32, isArray: false, isConst: false },
1310
+ ],
1311
+ ]);
1312
+ const ctx = createMockPostfixExpressionContext("val", [
1313
+ createMockPostfixOp({
1314
+ expressions: [createMockExpression("0"), createMockExpression("n")],
1315
+ }),
1316
+ ]);
1317
+ const input = createMockInput({ typeRegistry });
1318
+ const state = createMockState();
1319
+ const generateBitMask = vi.fn(() => "((1U << n) - 1)");
1320
+ const orchestrator = createMockOrchestrator({
1321
+ generatePrimaryExpr: () => "val",
1322
+ generateExpression: (ctx) => ctx.getText(),
1323
+ generateBitMask,
1324
+ tryEvaluateConstant: () => undefined,
1325
+ });
1326
+
1327
+ generatePostfixExpression(ctx, input, state, orchestrator);
1328
+ // Non-const width: the generated expression string flows through unchanged.
1329
+ expect(generateBitMask).toHaveBeenCalledWith("n", false);
1330
+ });
1331
+
1332
+ it("marks a u64 operand as 64-bit for the mask base (#1094)", () => {
1333
+ const typeRegistry = new Map<string, TTypeInfo>([
1334
+ [
1335
+ "val",
1336
+ { baseType: "u64", bitWidth: 64, isArray: false, isConst: false },
1337
+ ],
1338
+ ]);
1339
+ const ctx = createMockPostfixExpressionContext("val", [
1340
+ createMockPostfixOp({
1341
+ expressions: [
1342
+ createMockExpression("0"),
1343
+ createMockExpression("WIDTH"),
1344
+ ],
1345
+ }),
1346
+ ]);
1347
+ const input = createMockInput({ typeRegistry });
1348
+ const state = createMockState();
1349
+ const generateBitMask = vi.fn(() => "((1ULL << 40U) - 1)");
1350
+ const orchestrator = createMockOrchestrator({
1351
+ generatePrimaryExpr: () => "val",
1352
+ generateExpression: (ctx) => ctx.getText(),
1353
+ generateBitMask,
1354
+ tryEvaluateConstant: () => 40,
1355
+ });
1356
+
1357
+ generatePostfixExpression(ctx, input, state, orchestrator);
1358
+ // u64 operand → is64Bit true, so the mask uses a 64-bit base (1ULL).
1359
+ expect(generateBitMask).toHaveBeenCalledWith("40U", true);
1360
+ });
1268
1361
  });
1269
1362
 
1270
1363
  describe("float bit indexing", () => {
@@ -1371,6 +1464,41 @@ describe("PostfixExpressionGenerator", () => {
1371
1464
  // Uses union member .u for bit access
1372
1465
  expect(result.code).toBe("(__bits_f.u & 0xFF)");
1373
1466
  });
1467
+
1468
+ // Issue #1094: the float branch must resolve a const width too, and tell the
1469
+ // mask generator the f64 union is 64-bit.
1470
+ it("resolves a const width for f64 bit indexing (#1094)", () => {
1471
+ const typeRegistry = new Map<string, TTypeInfo>([
1472
+ [
1473
+ "d",
1474
+ { baseType: "f64", bitWidth: 64, isArray: false, isConst: false },
1475
+ ],
1476
+ ]);
1477
+ const ctx = createMockPostfixExpressionContext("d", [
1478
+ createMockPostfixOp({
1479
+ expressions: [
1480
+ createMockExpression("0"),
1481
+ createMockExpression("WIDTH"),
1482
+ ],
1483
+ }),
1484
+ ]);
1485
+ const input = createMockInput({ typeRegistry });
1486
+ const state = createMockState({ inFunctionBody: true });
1487
+ const generateBitMask = vi.fn(() => "0xFFFFFFFFFFFFFFFFULL");
1488
+ const orchestrator = createMockOrchestrator({
1489
+ generatePrimaryExpr: () => "d",
1490
+ generateExpression: (ctx) => ctx.getText(),
1491
+ generateBitMask,
1492
+ tryEvaluateConstant: () => 64,
1493
+ hasFloatBitShadow: () => true,
1494
+ isFloatShadowCurrent: () => true,
1495
+ });
1496
+
1497
+ const result = generatePostfixExpression(ctx, input, state, orchestrator);
1498
+ // Const width "64U" passed; f64 union → 64-bit mask base.
1499
+ expect(generateBitMask).toHaveBeenCalledWith("64U", true);
1500
+ expect(result.code).toBe("(__bits_d.u & 0xFFFFFFFFFFFFFFFFULL)");
1501
+ });
1374
1502
  });
1375
1503
 
1376
1504
  describe("function call", () => {
@@ -14,6 +14,7 @@ import {
14
14
  WhileStatementContext,
15
15
  DoWhileStatementContext,
16
16
  ForStatementContext,
17
+ ForeverStatementContext,
17
18
  ForVarDeclContext,
18
19
  ForAssignmentContext,
19
20
  ExpressionContext,
@@ -167,6 +168,9 @@ const generateWhile = (
167
168
  // Issue #884: Validate condition is a boolean expression (E0701)
168
169
  orchestrator.validateConditionIsBoolean(node.expression(), "while");
169
170
 
171
+ // ADR-113 / #1075: reject always-true literal condition (E0707)
172
+ orchestrator.validateLoopConditionNotAlwaysTrue(node.expression());
173
+
170
174
  const condition = orchestrator.generateExpression(node.expression());
171
175
 
172
176
  // Issue #250: Flush any temp vars from condition BEFORE generating body
@@ -201,6 +205,9 @@ const generateDoWhile = (
201
205
  // Issue #884: Validate condition is a boolean expression (E0701)
202
206
  orchestrator.validateConditionIsBoolean(node.expression(), "do-while");
203
207
 
208
+ // ADR-113 / #1075: reject always-true literal condition (E0707)
209
+ orchestrator.validateLoopConditionNotAlwaysTrue(node.expression());
210
+
204
211
  const body = orchestrator.generateBlock(node.block());
205
212
  const condition = orchestrator.generateExpression(node.expression());
206
213
 
@@ -286,6 +293,15 @@ const generateFor = (
286
293
  ): IGeneratorOutput => {
287
294
  const effects: TGeneratorEffect[] = [];
288
295
 
296
+ // ADR-113 / #1075 E0707: a for-loop with no controlling expression (`for (;;)`)
297
+ // is a disguised infinite loop. C-Next has one source form for that — `forever`.
298
+ if (!node.expression()) {
299
+ throw new Error(
300
+ "Error E0707: for-loop has no controlling expression (infinite loop)\n" +
301
+ " help: write 'forever { ... }' for an intentional infinite loop",
302
+ );
303
+ }
304
+
289
305
  let init = "";
290
306
  const forInit = node.forInit();
291
307
  if (forInit) {
@@ -313,15 +329,20 @@ const generateFor = (
313
329
  // Issue #250: Flush temps from init before generating condition
314
330
  const initTemps = orchestrator.flushPendingTempDeclarations();
315
331
 
316
- let condition = "";
317
- if (node.expression()) {
318
- // Issue #254: Validate no function calls in condition (E0702)
319
- orchestrator.validateConditionNoFunctionCall(node.expression()!, "for");
332
+ // The empty-header case (`for (;;)`) already threw E0707 above, so the
333
+ // controlling expression is guaranteed present here.
334
+ const conditionExpr = node.expression()!;
320
335
 
321
- // Issue #884: Validate condition is a boolean expression (E0701)
322
- orchestrator.validateConditionIsBoolean(node.expression()!, "for");
323
- condition = orchestrator.generateExpression(node.expression()!);
324
- }
336
+ // Issue #254: Validate no function calls in condition (E0702)
337
+ orchestrator.validateConditionNoFunctionCall(conditionExpr, "for");
338
+
339
+ // Issue #884: Validate condition is a boolean expression (E0701)
340
+ orchestrator.validateConditionIsBoolean(conditionExpr, "for");
341
+
342
+ // ADR-113 / #1075: reject always-true literal condition (E0707)
343
+ orchestrator.validateLoopConditionNotAlwaysTrue(conditionExpr);
344
+
345
+ const condition = orchestrator.generateExpression(conditionExpr);
325
346
 
326
347
  // Issue #250: Flush temps from condition before generating update
327
348
  const conditionTemps = orchestrator.flushPendingTempDeclarations();
@@ -357,6 +378,40 @@ const generateFor = (
357
378
  return { code: result, effects };
358
379
  };
359
380
 
381
+ /**
382
+ * Generate C code for a forever statement (ADR-113).
383
+ *
384
+ * Lowers to the MISRA C:2012 Rule 14.3-compliant infinite-loop idiom `for (;;)`
385
+ * (the carve-out that rule explicitly permits — no controlling expression to be
386
+ * flagged as invariant). A `forever` loop may appear only in a void function
387
+ * (E0705): a value-returning function can never honor its return type if it
388
+ * loops forever.
389
+ */
390
+ const generateForever = (
391
+ node: ForeverStatementContext,
392
+ _input: IGeneratorInput,
393
+ _state: IGeneratorState,
394
+ orchestrator: IOrchestrator,
395
+ ): IGeneratorOutput => {
396
+ const effects: TGeneratorEffect[] = [];
397
+
398
+ // ADR-113 E0705: forever is void-only.
399
+ const returnType = orchestrator.getCurrentFunctionReturnType();
400
+ if (returnType && returnType !== "void") {
401
+ throw new Error(
402
+ "Error E0705: forever loop in non-void function\n" +
403
+ " help: a forever loop never returns a value; make the function return void, " +
404
+ "or use a while loop with an exit condition",
405
+ );
406
+ }
407
+
408
+ const body = orchestrator.generateBlock(node.block());
409
+ const comment =
410
+ "/* MISRA C:2012 Rule 14.3: infinite loop is intentional (C-Next `forever`) */";
411
+
412
+ return { code: `${comment}\nfor (;;) ${body}`, effects };
413
+ };
414
+
360
415
  // Export all control flow generators
361
416
  const controlFlowGenerators = {
362
417
  generateReturn,
@@ -364,6 +419,7 @@ const controlFlowGenerators = {
364
419
  generateWhile,
365
420
  generateDoWhile,
366
421
  generateFor,
422
+ generateForever,
367
423
  generateForVarDecl,
368
424
  generateForAssignment,
369
425
  };
@@ -365,6 +365,7 @@ function createMockOrchestrator(options?: {
365
365
  flushPendingTempDeclarations: vi.fn(() => options?.tempDeclarations ?? ""),
366
366
  validateConditionNoFunctionCall: vi.fn(),
367
367
  validateConditionIsBoolean: vi.fn(),
368
+ validateLoopConditionNotAlwaysTrue: vi.fn(),
368
369
  countStringLengthAccesses: vi.fn(() => new Map()),
369
370
  countBlockLengthAccesses: vi.fn(),
370
371
  setupLengthCache: vi.fn(() => options?.lengthCacheDecls ?? ""),
@@ -898,15 +899,15 @@ describe("ControlFlowGenerator", () => {
898
899
  // ========================================================================
899
900
 
900
901
  describe("generateFor", () => {
901
- it("generates empty for loop (infinite loop)", () => {
902
- const ctx = createMockForStatement();
902
+ it("rejects an empty for(;;) header as a disguised infinite loop (ADR-113 / #1075, E0707)", () => {
903
+ const ctx = createMockForStatement(); // no controlling expression
903
904
  const input = createMockInput();
904
905
  const state = createMockState();
905
906
  const orchestrator = createMockOrchestrator({ statementCode: "{ }" });
906
907
 
907
- const result = generateFor(ctx, input, state, orchestrator);
908
-
909
- expect(result.code).toBe("for (; ; ) { }");
908
+ expect(() => generateFor(ctx, input, state, orchestrator)).toThrow(
909
+ /E0707: for-loop has no controlling expression/,
910
+ );
910
911
  });
911
912
 
912
913
  it("generates for loop with all parts", () => {
@@ -947,6 +948,7 @@ describe("ControlFlowGenerator", () => {
947
948
  operator: createMockAssignmentOperator("<-"),
948
949
  }),
949
950
  }),
951
+ expr: createMockExpression({ text: "i < 10" }),
950
952
  });
951
953
  const input = createMockInput();
952
954
  const state = createMockState();
@@ -1007,6 +1009,7 @@ describe("ControlFlowGenerator", () => {
1007
1009
  init: createMockForInit({
1008
1010
  varDecl: createMockForVarDecl(),
1009
1011
  }),
1012
+ expr: createMockExpression({ text: "i < 10" }),
1010
1013
  });
1011
1014
  const input = createMockInput();
1012
1015
  const state = createMockState();
@@ -71,7 +71,10 @@ class AssignmentExpectedTypeResolver {
71
71
  // Case 2b: Simple array element access (arr[i] <- value)
72
72
  // Issue #872: Resolve element type for MISRA 7.2 U suffix
73
73
  if (identifiers.length === 1 && hasSubscript) {
74
- return AssignmentExpectedTypeResolver.resolveForArrayElement(baseId);
74
+ return AssignmentExpectedTypeResolver.resolveForArrayElement(
75
+ baseId,
76
+ postfixOps,
77
+ );
75
78
  }
76
79
 
77
80
  // Case 2c: Member chain with array access (struct.arr[i] <- value)
@@ -87,6 +90,17 @@ class AssignmentExpectedTypeResolver {
87
90
  return { expectedType: null, assignmentContext: null };
88
91
  }
89
92
 
93
+ /**
94
+ * True if any postfix subscript is the 2-expression `[offset, length]` form —
95
+ * an array slice or scalar bit-range write (vs a 1-expression element/bit
96
+ * access). The grammar models both as `'[' expression ',' expression ']'`.
97
+ */
98
+ private static hasRangeSubscript(
99
+ postfixOps: Parser.PostfixTargetOpContext[],
100
+ ): boolean {
101
+ return postfixOps.some((op) => op.expression().length === 2);
102
+ }
103
+
90
104
  /**
91
105
  * Resolve expected type for a simple identifier target.
92
106
  */
@@ -124,13 +138,27 @@ class AssignmentExpectedTypeResolver {
124
138
  /**
125
139
  * Resolve expected type for array element access.
126
140
  * Issue #872: arr[i] <- value needs baseType for MISRA 7.2 U suffix.
141
+ *
142
+ * An array SLICE (2-expression subscript `arr[off, len]`) is the exception:
143
+ * its source serializes at the SOURCE's own width, so leaking the element type
144
+ * as expectedType truncates an element-width-sensitive source such as a
145
+ * bit-extraction (Issue #1085: `buf[0,4] <- b[0,32]` wrote only the low byte).
146
+ * This applies only to arrays — a 2-expression subscript on a scalar is a
147
+ * bit-range write, whose value is genuinely the field's type (unchanged).
127
148
  */
128
- private static resolveForArrayElement(id: string): IExpectedTypeResult {
149
+ private static resolveForArrayElement(
150
+ id: string,
151
+ postfixOps: Parser.PostfixTargetOpContext[],
152
+ ): IExpectedTypeResult {
129
153
  const typeInfo = CodeGenState.getVariableTypeInfo(id);
130
154
  if (!typeInfo?.isArray) {
131
155
  return { expectedType: null, assignmentContext: null };
132
156
  }
133
157
 
158
+ if (AssignmentExpectedTypeResolver.hasRangeSubscript(postfixOps)) {
159
+ return { expectedType: null, assignmentContext: null };
160
+ }
161
+
134
162
  // Element type is the baseType (e.g., u8[10] -> "u8")
135
163
  return { expectedType: typeInfo.baseType, assignmentContext: null };
136
164
  }
@@ -13,7 +13,6 @@
13
13
  */
14
14
 
15
15
  import * as Parser from "../../../logic/parser/grammar/CNextParser.js";
16
- import FormatUtils from "../../../../utils/FormatUtils.js";
17
16
  import StringUtils from "../../../../utils/StringUtils.js";
18
17
  import CodeGenState from "../../../state/CodeGenState.js";
19
18
 
@@ -412,7 +411,10 @@ class StringDeclHelper {
412
411
  return { code, handled: true };
413
412
  }
414
413
 
415
- // String variable: need strcpy (cannot use array initialization in C)
414
+ // String variable: cannot use C array initialization, so declare empty and
415
+ // copy. Issue #1044: use the same bounded copy as the reassignment path
416
+ // (strncpy + explicit null terminator via StringUtils.copyWithNull) rather
417
+ // than an unbounded strcpy, which flawfinder flags as CWE-120.
416
418
  // Issue #1030: string-to-string initialization
417
419
  if (!CodeGenState.inFunctionBody) {
418
420
  throw new Error(
@@ -422,11 +424,12 @@ class StringDeclHelper {
422
424
  }
423
425
 
424
426
  const srcExpr = callbacks.generateExpression(expression);
425
- const indent = FormatUtils.indent(CodeGenState.indentLevel);
427
+ // Issue #1037: continuation lines carry no indent of their own — the block
428
+ // emitter (CodeGenerator.generateBlock) prefixes every line.
426
429
  const lines: string[] = [];
427
430
  lines.push(
428
431
  `${constMod}char ${name}[${capacity + 1}] = "";`,
429
- `${indent}strcpy(${name}, ${srcExpr});`,
432
+ StringUtils.copyWithNull(name, srcExpr, capacity),
430
433
  );
431
434
  return { code: lines.join("\n"), handled: true };
432
435
  }
@@ -637,14 +640,14 @@ class StringDeclHelper {
637
640
  );
638
641
  }
639
642
 
640
- // Generate safe concatenation code
641
- const indent = FormatUtils.indent(CodeGenState.indentLevel);
643
+ // Generate safe concatenation code. Issue #1037: continuation lines carry
644
+ // no indent of their own — the block emitter prefixes every line.
642
645
  const lines: string[] = [];
643
646
  lines.push(
644
647
  `${constMod}char ${name}[${capacity + 1}] = "";`,
645
- `${indent}strncpy(${name}, ${concatOps.left}, ${capacity});`,
646
- `${indent}strncat(${name}, ${concatOps.right}, ${capacity} - strlen(${name}));`,
647
- `${indent}${name}[${capacity}] = ${C_NULL_CHAR};`,
648
+ `strncpy(${name}, ${concatOps.left}, ${capacity});`,
649
+ `strncat(${name}, ${concatOps.right}, ${capacity} - strlen(${name}));`,
650
+ `${name}[${capacity}] = ${C_NULL_CHAR};`,
648
651
  );
649
652
  return { code: lines.join("\n"), handled: true };
650
653
  }
@@ -688,13 +691,13 @@ class StringDeclHelper {
688
691
  );
689
692
  }
690
693
 
691
- // Generate safe substring extraction code
692
- const indent = FormatUtils.indent(CodeGenState.indentLevel);
694
+ // Generate safe substring extraction code. Issue #1037: continuation lines
695
+ // carry no indent of their own — the block emitter prefixes every line.
693
696
  const lines: string[] = [];
694
697
  lines.push(
695
698
  `${constMod}char ${name}[${capacity + 1}] = "";`,
696
- `${indent}strncpy(${name}, ${substringOps.source} + ${substringOps.start}, ${substringOps.length});`,
697
- `${indent}${name}[${substringOps.length}] = ${C_NULL_CHAR};`,
699
+ `strncpy(${name}, ${substringOps.source} + ${substringOps.start}, ${substringOps.length});`,
700
+ `${name}[${substringOps.length}] = ${C_NULL_CHAR};`,
698
701
  );
699
702
  return { code: lines.join("\n"), handled: true };
700
703
  }
@@ -256,6 +256,25 @@ describe("AssignmentExpectedTypeResolver", () => {
256
256
 
257
257
  expect(result.expectedType).toBeNull();
258
258
  });
259
+
260
+ // Issue #1085: an array SLICE (2-expression subscript `arr[off, len]`)
261
+ // serializes the source at the SOURCE's own width. Leaking the element type
262
+ // as expectedType truncates a wider source (e.g. a bit-extraction), so the
263
+ // resolver must return null for the slice form — unlike a 1-expression
264
+ // element access, which keeps the element type for the MISRA 7.2 U suffix.
265
+ it("should return null for an array slice (2-expression subscript)", () => {
266
+ CodeGenState.setVariableTypeInfo("buffer", {
267
+ baseType: "u8",
268
+ bitWidth: 8,
269
+ isArray: true,
270
+ isConst: false,
271
+ });
272
+ const target = parseAssignmentTarget("buffer[0, 4]");
273
+
274
+ const result = AssignmentExpectedTypeResolver.resolve(target);
275
+
276
+ expect(result.expectedType).toBeNull();
277
+ });
259
278
  });
260
279
  });
261
280
  });
@@ -310,7 +310,8 @@ describe("StringDeclHelper", () => {
310
310
  });
311
311
 
312
312
  it("allows assignment when source capacity fits", () => {
313
- // Issue #1030: String variable initialization now uses strcpy
313
+ // Issue #1044: String variable initialization uses a bounded strncpy
314
+ // (shared with the reassignment path), not an unsafe strcpy (CWE-120).
314
315
  const callbacks = {
315
316
  ...defaultCallbacks,
316
317
  getStringExprCapacity: vi.fn(() => 20),
@@ -338,9 +339,49 @@ describe("StringDeclHelper", () => {
338
339
  );
339
340
 
340
341
  expect(result.handled).toBe(true);
341
- // Issue #1030: String variable initialization uses strcpy
342
+ // Issue #1044: bounded copy, not strcpy
342
343
  expect(result.code).toContain('char dest[33] = "";');
343
- expect(result.code).toContain("strcpy(dest, smallString)");
344
+ expect(result.code).toContain("strncpy(dest, smallString, 32);");
345
+ expect(result.code).toContain("dest[32] = '\\0';");
346
+ expect(result.code).not.toContain("strcpy(dest, smallString)");
347
+ });
348
+
349
+ it("does not indent continuation lines (the block emitter owns indentation) — Issue #1037", () => {
350
+ // indentLevel = 1 (beforeEach). generateBlock prefixes every line of a
351
+ // statement, so the helper must NOT add its own indent or continuation
352
+ // lines double-indent.
353
+ const callbacks = {
354
+ ...defaultCallbacks,
355
+ getStringExprCapacity: vi.fn(() => 20),
356
+ generateExpression: vi.fn(() => "smallString"),
357
+ };
358
+
359
+ const typeCtx = {
360
+ stringType: () => ({
361
+ INTEGER_LITERAL: () => ({ getText: () => "32" }),
362
+ }),
363
+ } as never;
364
+
365
+ const expression = {
366
+ getText: () => "smallString",
367
+ } as never;
368
+
369
+ const result = StringDeclHelper.generateStringDecl(
370
+ typeCtx,
371
+ "dest",
372
+ expression,
373
+ [],
374
+ { extern: "", const: "", atomic: "", volatile: "" },
375
+ false,
376
+ callbacks,
377
+ );
378
+
379
+ const lines = result.code.split("\n");
380
+ expect(lines[0]).toBe('char dest[33] = "";');
381
+ // Continuation line must have no leading whitespace of its own.
382
+ expect(lines[1]).toBe(
383
+ "strncpy(dest, smallString, 32); dest[32] = '\\0';",
384
+ );
344
385
  });
345
386
 
346
387
  it("throws error for string variable initialization at global scope", () => {
@@ -411,12 +452,15 @@ describe("StringDeclHelper", () => {
411
452
  );
412
453
 
413
454
  expect(result.handled).toBe(true);
414
- expect(result.code).toContain('char combined[33] = "";');
415
- expect(result.code).toContain("strncpy(combined, str1, 32)");
416
- expect(result.code).toContain(
417
- "strncat(combined, str2, 32 - strlen(combined))",
455
+ // Issue #1037: every line carries no indent of its own (the block emitter
456
+ // prefixes each line); continuation lines must not double-indent.
457
+ const concatLines = result.code.split("\n");
458
+ expect(concatLines[0]).toBe('char combined[33] = "";');
459
+ expect(concatLines[1]).toBe("strncpy(combined, str1, 32);");
460
+ expect(concatLines[2]).toBe(
461
+ "strncat(combined, str2, 32 - strlen(combined));",
418
462
  );
419
- expect(result.code).toContain("combined[32] = '\\0';");
463
+ expect(concatLines[3]).toBe("combined[32] = '\\0';");
420
464
  });
421
465
 
422
466
  it("throws error for concatenation at global scope", () => {
@@ -560,9 +604,11 @@ describe("StringDeclHelper", () => {
560
604
  );
561
605
 
562
606
  expect(result.handled).toBe(true);
563
- expect(result.code).toContain('char sub[11] = "";');
564
- expect(result.code).toContain("strncpy(sub, srcStr + 0, 5)");
565
- expect(result.code).toContain("sub[5] = '\\0';");
607
+ // Issue #1037: continuation lines carry no indent of their own.
608
+ const subLines = result.code.split("\n");
609
+ expect(subLines[0]).toBe('char sub[11] = "";');
610
+ expect(subLines[1]).toBe("strncpy(sub, srcStr + 0, 5);");
611
+ expect(subLines[2]).toBe("sub[5] = '\\0';");
566
612
  });
567
613
 
568
614
  it("throws error for substring at global scope", () => {