c-next 0.2.2 → 0.2.3

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.
@@ -1,14 +1,17 @@
1
1
  /**
2
- * FloatBitHelper - Generates float bit write operations using shadow variables
2
+ * FloatBitHelper - Generates float bit write operations using union-based type punning
3
3
  *
4
4
  * Issue #644: Extracted from CodeGenerator to reduce file size.
5
+ * Issue #857: Changed from memcpy to union for MISRA C:2012 Rule 21.15 compliance.
5
6
  *
6
- * Floats don't support direct bit access in C, so we use a shadow integer
7
- * variable and memcpy to read/write bit values. The pattern:
8
- * 1. Declare shadow variable if needed
9
- * 2. memcpy float shadow (if not already current)
10
- * 3. Modify shadow bits
11
- * 4. memcpy shadow float
7
+ * Floats don't support direct bit access in C, so we use a union for type punning.
8
+ * The pattern:
9
+ * 1. Declare union variable if needed: union { float f; uint32_t u; } __bits_name;
10
+ * 2. Copy float to union: __bits_name.f = floatVar;
11
+ * 3. Modify bits via union member: __bits_name.u = ...
12
+ * 4. Copy back: floatVar = __bits_name.f;
13
+ *
14
+ * This approach is MISRA-compliant because union type punning is well-defined in C99+.
12
15
  *
13
16
  * Migrated to use CodeGenState instead of constructor DI.
14
17
  */
@@ -30,16 +33,25 @@ interface IFloatBitCallbacks {
30
33
  }
31
34
 
32
35
  /**
33
- * Generates float bit write operations using shadow variables and memcpy.
36
+ * Get the C float type name for a C-Next float type.
37
+ */
38
+ const getFloatTypeName = (baseType: string): string => {
39
+ return baseType === "f64" ? "double" : "float";
40
+ };
41
+
42
+ /**
43
+ * Generates float bit write operations using union-based type punning.
34
44
  *
35
45
  * For single bit: width is null, uses bitIndex only
36
46
  * For bit range: width is provided, uses bitIndex as start position
37
47
  */
38
48
  class FloatBitHelper {
39
49
  /**
40
- * Generate float bit write using shadow variable + memcpy.
50
+ * Generate float bit write using union-based type punning.
41
51
  * Returns null if typeInfo is not a float type.
42
52
  *
53
+ * Uses union { float f; uint32_t u; } for MISRA 21.15 compliance instead of memcpy.
54
+ *
43
55
  * @param name - Variable name being written
44
56
  * @param typeInfo - Type information for the variable
45
57
  * @param bitIndex - Bit index expression (start position)
@@ -62,11 +74,11 @@ class FloatBitHelper {
62
74
  return null;
63
75
  }
64
76
 
65
- callbacks.requireInclude("string"); // For memcpy
66
77
  callbacks.requireInclude("float_static_assert"); // For size verification
67
78
 
68
79
  const isF64 = typeInfo.baseType === "f64";
69
- const shadowType = isF64 ? "uint64_t" : "uint32_t";
80
+ const floatType = getFloatTypeName(typeInfo.baseType);
81
+ const intType = isF64 ? "uint64_t" : "uint32_t";
70
82
  const shadowName = `__bits_${name}`;
71
83
  const maskSuffix = isF64 ? "ULL" : "U";
72
84
 
@@ -76,13 +88,15 @@ class FloatBitHelper {
76
88
  CodeGenState.floatBitShadows.add(shadowName);
77
89
  }
78
90
 
79
- // Check if shadow already has current value (skip redundant memcpy read)
91
+ // Check if shadow already has current value (skip redundant read)
80
92
  const shadowIsCurrent = CodeGenState.floatShadowCurrent.has(shadowName);
81
93
 
82
- const decl = needsDeclaration ? `${shadowType} ${shadowName}; ` : "";
83
- const readMemcpy = shadowIsCurrent
84
- ? ""
85
- : `memcpy(&${shadowName}, &${name}, sizeof(${name})); `;
94
+ // Union declaration: union { float f; uint32_t u; } __bits_name;
95
+ const decl = needsDeclaration
96
+ ? `union { ${floatType} f; ${intType} u; } ${shadowName};\n`
97
+ : "";
98
+ // Read from float into union: __bits_name.f = floatVar;
99
+ const readUnion = shadowIsCurrent ? "" : `${shadowName}.f = ${name};\n`;
86
100
 
87
101
  // Mark shadow as current after this write
88
102
  CodeGenState.floatShadowCurrent.add(shadowName);
@@ -90,17 +104,17 @@ class FloatBitHelper {
90
104
  if (width === null) {
91
105
  // Single bit assignment: floatVar[3] <- true
92
106
  return (
93
- `${decl}${readMemcpy}` +
94
- `${shadowName} = (${shadowName} & ~(1${maskSuffix} << ${bitIndex})) | ((${shadowType})${callbacks.foldBooleanToInt(value)} << ${bitIndex}); ` +
95
- `memcpy(&${name}, &${shadowName}, sizeof(${name}));`
107
+ `${decl}${readUnion}` +
108
+ `${shadowName}.u = (${shadowName}.u & ~(1${maskSuffix} << ${bitIndex})) | ((${intType})${callbacks.foldBooleanToInt(value)} << ${bitIndex});\n` +
109
+ `${name} = ${shadowName}.f;`
96
110
  );
97
111
  } else {
98
112
  // Bit range assignment: floatVar[0, 8] <- b0
99
113
  const mask = callbacks.generateBitMask(width, isF64);
100
114
  return (
101
- `${decl}${readMemcpy}` +
102
- `${shadowName} = (${shadowName} & ~(${mask} << ${bitIndex})) | (((${shadowType})${value} & ${mask}) << ${bitIndex}); ` +
103
- `memcpy(&${name}, &${shadowName}, sizeof(${name}));`
115
+ `${decl}${readUnion}` +
116
+ `${shadowName}.u = (${shadowName}.u & ~(${mask} << ${bitIndex})) | (((${intType})${value} & ${mask}) << ${bitIndex});\n` +
117
+ `${name} = ${shadowName}.f;`
104
118
  );
105
119
  }
106
120
  }
@@ -243,7 +243,7 @@ describe("ArrayAccessHelper", () => {
243
243
  expect(mockDeps.generateBitMask).toHaveBeenCalledWith("8");
244
244
  });
245
245
 
246
- it("should route to float bit range for f32", () => {
246
+ it("should route to float bit range for f32 (no string.h needed)", () => {
247
247
  const info: IArrayAccessInfo = {
248
248
  rawName: "fval",
249
249
  resolvedName: "fval",
@@ -260,8 +260,9 @@ describe("ArrayAccessHelper", () => {
260
260
  };
261
261
 
262
262
  const result = ArrayAccessHelper.generateBitRange(info, mockDeps);
263
- expect(result).toContain("memcpy");
264
- expect(mockDeps.requireInclude).toHaveBeenCalledWith("string");
263
+ expect(result).toContain("__bits_fval");
264
+ // No string.h - uses union-based type punning (MISRA 21.15 compliant)
265
+ expect(mockDeps.requireInclude).not.toHaveBeenCalledWith("string");
265
266
  expect(mockDeps.requireInclude).toHaveBeenCalledWith(
266
267
  "float_static_assert",
267
268
  );
@@ -356,9 +357,10 @@ describe("ArrayAccessHelper", () => {
356
357
 
357
358
  const result = ArrayAccessHelper.generateFloatBitRange(info, mockDeps);
358
359
 
359
- expect(result).toContain("memcpy(&__bits_fval, &fval, sizeof(fval))");
360
- expect(result).toContain("__bits_fval & 0xFF");
361
- expect(mockDeps.requireInclude).toHaveBeenCalledWith("string");
360
+ // Note: BitRangeHelper.buildFloatBitReadExpr still uses memcpy internally,
361
+ // but ArrayAccessHelper no longer requires string.h (MISRA 21.15 compliant)
362
+ expect(result).toContain("__bits_fval");
363
+ expect(mockDeps.requireInclude).not.toHaveBeenCalledWith("string");
362
364
  expect(mockDeps.requireInclude).toHaveBeenCalledWith(
363
365
  "float_static_assert",
364
366
  );
@@ -2,6 +2,7 @@
2
2
  * Unit tests for FloatBitHelper
3
3
  *
4
4
  * Issue #644: Tests for the extracted float bit write helper.
5
+ * Issue #857: Updated for union-based type punning (MISRA 21.15 compliance).
5
6
  * Migrated to use CodeGenState instead of constructor DI.
6
7
  */
7
8
 
@@ -57,7 +58,7 @@ describe("FloatBitHelper", () => {
57
58
  expect(callbacks.requireInclude).not.toHaveBeenCalled();
58
59
  });
59
60
 
60
- it("generates single bit write for f32", () => {
61
+ it("generates single bit write for f32 using union", () => {
61
62
  const typeInfo: TTypeInfo = {
62
63
  baseType: "f32",
63
64
  bitWidth: 32,
@@ -74,17 +75,27 @@ describe("FloatBitHelper", () => {
74
75
  callbacks,
75
76
  );
76
77
 
77
- expect(result).toContain("uint32_t __bits_myFloat;");
78
- expect(result).toContain("memcpy(&__bits_myFloat, &myFloat");
79
- expect(result).toContain("& ~(1U << 3)");
80
- expect(result).toContain("memcpy(&myFloat, &__bits_myFloat");
81
- expect(callbacks.requireInclude).toHaveBeenCalledWith("string");
78
+ // Union declaration
79
+ expect(result).toContain(
80
+ "union { float f; uint32_t u; } __bits_myFloat;",
81
+ );
82
+ // Read via union
83
+ expect(result).toContain("__bits_myFloat.f = myFloat;");
84
+ // Bit manipulation via .u
85
+ expect(result).toContain(
86
+ "__bits_myFloat.u = (__bits_myFloat.u & ~(1U << 3))",
87
+ );
88
+ // Write back via union
89
+ expect(result).toContain("myFloat = __bits_myFloat.f;");
90
+ // No memcpy for MISRA compliance
91
+ expect(result).not.toContain("memcpy");
92
+ expect(callbacks.requireInclude).not.toHaveBeenCalledWith("string");
82
93
  expect(callbacks.requireInclude).toHaveBeenCalledWith(
83
94
  "float_static_assert",
84
95
  );
85
96
  });
86
97
 
87
- it("generates single bit write for f64", () => {
98
+ it("generates single bit write for f64 using union", () => {
88
99
  const typeInfo: TTypeInfo = {
89
100
  baseType: "f64",
90
101
  bitWidth: 64,
@@ -101,11 +112,13 @@ describe("FloatBitHelper", () => {
101
112
  callbacks,
102
113
  );
103
114
 
104
- expect(result).toContain("uint64_t __bits_myDouble;");
115
+ expect(result).toContain(
116
+ "union { double f; uint64_t u; } __bits_myDouble;",
117
+ );
105
118
  expect(result).toContain("1ULL << 5");
106
119
  });
107
120
 
108
- it("generates bit range write for f32", () => {
121
+ it("generates bit range write for f32 using union", () => {
109
122
  const typeInfo: TTypeInfo = {
110
123
  baseType: "f32",
111
124
  bitWidth: 32,
@@ -122,11 +135,13 @@ describe("FloatBitHelper", () => {
122
135
  callbacks,
123
136
  );
124
137
 
125
- expect(result).toContain("uint32_t __bits_myFloat;");
138
+ expect(result).toContain(
139
+ "union { float f; uint32_t u; } __bits_myFloat;",
140
+ );
126
141
  expect(callbacks.generateBitMask).toHaveBeenCalledWith("8", false);
127
142
  });
128
143
 
129
- it("skips declaration when shadow already exists", () => {
144
+ it("skips union declaration when shadow already exists", () => {
130
145
  const typeInfo: TTypeInfo = {
131
146
  baseType: "f32",
132
147
  bitWidth: 32,
@@ -146,11 +161,11 @@ describe("FloatBitHelper", () => {
146
161
  callbacks,
147
162
  );
148
163
 
149
- expect(result).not.toContain("uint32_t __bits_myFloat;");
150
- expect(result).toContain("memcpy(&__bits_myFloat");
164
+ expect(result).not.toContain("union { float f; uint32_t u; }");
165
+ expect(result).toContain("__bits_myFloat.f = myFloat;");
151
166
  });
152
167
 
153
- it("skips redundant memcpy read when shadow is current", () => {
168
+ it("skips redundant union read when shadow is current", () => {
154
169
  const typeInfo: TTypeInfo = {
155
170
  baseType: "f32",
156
171
  bitWidth: 32,
@@ -171,10 +186,11 @@ describe("FloatBitHelper", () => {
171
186
  callbacks,
172
187
  );
173
188
 
174
- expect(result).not.toContain("uint32_t __bits_myFloat;");
175
- // Should not have read memcpy, only write memcpy
176
- const memcpyMatches = result!.match(/memcpy/g);
177
- expect(memcpyMatches).toHaveLength(1);
189
+ expect(result).not.toContain("union { float f; uint32_t u; }");
190
+ // Should not have the read assignment, only the write-back
191
+ expect(result).not.toContain("__bits_myFloat.f = myFloat;");
192
+ // Should still have the write-back
193
+ expect(result).toContain("myFloat = __bits_myFloat.f;");
178
194
  });
179
195
 
180
196
  it("marks shadow as current after write", () => {