All files / TRANSPILE/3-Render/codegen/resolution SizeofResolver.ts

100% Statements 30/30
96.42% Branches 27/28
100% Functions 6/6
100% Lines 30/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175                                                                          41x       9x               21x   6x   5x                           9x       3x       6x 6x 2x 2x         4x 1x       3x                     21x 21x 9x                     12x                     9x 3x       6x 2x   4x                   4x                           5x 1x 1x 1x               4x         4x      
/**
 * SizeofResolver - Handles sizeof expression generation
 *
 * Extracted from CodeGenerator to reduce complexity.
 * Uses CodeGenState for all state access.
 *
 * ADR-023: sizeof expression handling with safety checks:
 * - E0601: sizeof on array parameter is error (returns pointer size)
 * - E0602: Side effects in sizeof are error (MISRA C:2012 Rule 13.6)
 *
 * ## It takes an operand, not a node (#1445)
 *
 * Every question here is about a NAME and `CodeGenState`: is `arr` a
 * parameter, is `cfg` a local that shadows a file-scope name, is `Scope` a
 * known scope. The tree was consulted only to find out WHICH grammar
 * alternative matched, which is the caller's question -- so `TSizeofOperand`
 * arrives already discriminated and this module names no parse type.
 *
 * The one thing it must not do is render a type name for `a.b` before
 * deciding `a.b` is a type, which is why that arm carries a thunk. See
 * `TSizeofOperand`.
 */
 
import TSizeofOperand from "../types/TSizeofOperand";
import invariant from "../../../../utils/invariant";
import type TranspileState from "../../../TranspileState";
 
/**
 * Resolves sizeof expressions to C code.
 */
export default class SizeofResolver {
  /**
   * Generate sizeof expression.
   * sizeof(type) -> sizeof(c_type)
   * sizeof(variable) -> sizeof(variable)
   */
  static generate(operand: TSizeofOperand, state: TranspileState): string {
    switch (operand.kind) {
      case "qualified-type":
        // `a.b` matched the qualified-TYPE alternative, and may still be a
        // member access -- only `CodeGenState` knows which.
        return (
          this.sizeofQualifiedType(
            operand.firstName,
            operand.memberName,
            state,
          ) ?? `sizeof(${operand.renderTypeName()})`
        );
      case "user-type":
        return this.sizeofUserType(operand.text, state);
      case "plain-type":
        return `sizeof(${operand.cTypeName})`;
      case "expression":
        return this.sizeofExpression(operand, state);
    }
  }
 
  /**
   * Handle sizeof(qualified.type) - may be struct.member access
   * Returns null if this is actually a type reference (Scope.Type)
   */
  private static sizeofQualifiedType(
    firstName: string,
    memberName: string,
    state: TranspileState,
  ): string | null {
    // Check if first identifier is a local variable (struct instance)
    if (state.localVariables.has(firstName)) {
      // ADR-057: a local that shadows a file-scope name is emitted under a
      // distinct C identifier. Without this, `sizeof(cfg.x)` measured the
      // GLOBAL `cfg` -- a wrong number, compiling clean.
      return `sizeof(${state.emittedLocalName(firstName)}.${memberName})`;
    }
 
    // Check if first identifier is a parameter (struct parameter)
    const paramInfo = state.currentParameters.get(firstName);
    if (paramInfo) {
      const sep = paramInfo.isStruct ? "->" : ".";
      return `sizeof(${firstName}${sep}${memberName})`;
    }
 
    // Check if first identifier is a global variable
    // If not a scope or enum, it's likely a global struct variable
    if (!state.isKnownScope(firstName) && !state.isKnownEnum(firstName)) {
      return `sizeof(${firstName}.${memberName})`;
    }
 
    // It's an actual type reference (Scope.Type), return null to fall through
    return null;
  }
 
  /**
   * Handle sizeof(identifier) - could be variable or type name
   */
  private static sizeofUserType(
    varName: string,
    state: TranspileState,
  ): string {
    // Check if it's a known parameter
    const paramInfo = state.currentParameters.get(varName);
    if (paramInfo) {
      return this.sizeofParameter(varName, paramInfo);
    }
 
    // Check if it's a known local variable, struct type, or enum type
    // For all these cases, generate sizeof(name) directly
    // Unknown identifiers are also treated as variables for safety
    //
    // ADR-057: emittedLocalName is a no-op for type names and for locals that
    // shadow nothing -- a rename exists only for a local of this exact name in
    // this function. Without it `sizeof(arr)` measured the GLOBAL array: 16
    // bytes where 8 was correct, with no diagnostic and a clean compile.
    return `sizeof(${state.emittedLocalName(varName)})`;
  }
 
  /**
   * Handle sizeof on a parameter - validates and generates appropriate code
   */
  private static sizeofParameter(
    varName: string,
    paramInfo: { isArray?: boolean; isCallback?: boolean; isStruct?: boolean },
  ): string {
    // E0601: Array parameters decay to pointers
    if (paramInfo.isArray) {
      this.throwArrayParamSizeofError(varName);
    }
    // For pass-by-reference parameters (non-array, non-callback, non-struct),
    // use pointer dereference
    if (!paramInfo.isCallback && !paramInfo.isStruct) {
      return `sizeof(*${varName})`;
    }
    return `sizeof(${varName})`;
  }
 
  /**
   * #1322: E0601 is a pass-2.1 diagnostic. What remains is the assertion that
   * it ran -- and it also fixes the advice: the old message pointed at
   * `.length`, which ADR-058 deprecated and E0886 now rejects, so following it
   * produced a second error.
   */
  private static throwArrayParamSizeofError(varName: string): never {
    invariant(
      false,
      `sizeof() is not applied to an array parameter ('${varName}') -- E0601 rejects this in pass 2.1, before this runs`,
    );
  }
 
  /**
   * Handle sizeof(expression) with validation
   */
  private static sizeofExpression(
    operand: Extract<TSizeofOperand, { kind: "expression" }>,
    state: TranspileState,
  ): string {
    // E0601: Check if expression is an array parameter
    if (operand.simpleIdentifier !== null) {
      const paramInfo = state.currentParameters.get(operand.simpleIdentifier);
      Eif (paramInfo?.isArray) {
        this.throwArrayParamSizeofError(operand.simpleIdentifier);
      }
    }
 
    // #1322: MISRA C:2012 Rule 13.6 is E0602 in pass 2.1, which asks the tree
    // for a call. The predicate behind this also tested the operand's TEXT for
    // eleven assignment operators, none of which can appear in an expression --
    // assignment is a statement in this grammar.
    invariant(
      !operand.hasSideEffects,
      "sizeof()'s operand has no side effects -- E0602 rejects this in pass 2.1, before this runs",
    );
 
    return `sizeof(${operand.code})`;
  }
}