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 | 877x 877x 889x 889x 866x 65x 65x 15x 65x 65x 65x 2x 63x 63x 11x 52x 63x 63x 52x 52x 37x 15x 15x | /**
* TransitiveModificationPropagator
*
* Performs fixed-point iteration to propagate parameter modifications
* transitively through a function call graph. If a parameter is passed to
* a function that modifies its corresponding parameter, then the caller's
* parameter is also considered modified.
*
* Issue #269: Extracted from CodeGenerator for improved testability.
*/
/**
* Call info entry in the function call graph.
* Represents a call from one function to another, tracking which parameter
* of the caller was passed to which parameter position of the callee.
*/
interface ICallInfo {
callee: string;
paramIndex: number;
argParamName: string;
}
class TransitiveModificationPropagator {
/**
* Propagate transitive parameter modifications through the call graph.
*
* Uses fixed-point iteration: if a parameter is passed to a function that
* modifies its corresponding param, then the caller's parameter is also
* considered modified. This continues until no more changes occur.
*
* @param functionCallGraph - Map of function name to call info array
* @param functionParamLists - Map of function name to parameter name list
* @param modifiedParameters - Map of function name to modified parameter set (mutated in place)
*/
static propagate(
functionCallGraph: ReadonlyMap<string, readonly ICallInfo[]>,
functionParamLists: ReadonlyMap<string, string[]>,
modifiedParameters: Map<string, Set<string>>,
): void {
let changed = true;
while (changed) {
changed = false;
for (const [funcName, calls] of functionCallGraph) {
for (const call of calls) {
const didPropagate = TransitiveModificationPropagator.propagateCall(
funcName,
call,
functionParamLists,
modifiedParameters,
);
if (didPropagate) {
changed = true;
}
}
}
}
}
/**
* Check if a single call propagates a modification from callee to caller.
* Returns true if a new modification was added.
*/
private static propagateCall(
callerName: string,
call: ICallInfo,
functionParamLists: ReadonlyMap<string, string[]>,
modifiedParameters: Map<string, Set<string>>,
): boolean {
const { callee, paramIndex, argParamName } = call;
// Get the callee's parameter list
const calleeParams = functionParamLists.get(callee);
if (!calleeParams || paramIndex >= calleeParams.length) {
return false;
}
const calleeParamName = calleeParams[paramIndex];
// Check if callee modifies this parameter
if (
!TransitiveModificationPropagator.isParamModified(
callee,
calleeParamName,
modifiedParameters,
)
) {
return false;
}
// Mark caller's parameter as modified if not already
return TransitiveModificationPropagator.markParamModified(
callerName,
argParamName,
modifiedParameters,
);
}
/**
* Check if a function's parameter is in the modified set.
*/
private static isParamModified(
funcName: string,
paramName: string,
modifiedParameters: ReadonlyMap<string, Set<string>>,
): boolean {
const modified = modifiedParameters.get(funcName);
return modified?.has(paramName) ?? false;
}
/**
* Mark a function's parameter as modified. Returns true if newly added.
*/
private static markParamModified(
funcName: string,
paramName: string,
modifiedParameters: Map<string, Set<string>>,
): boolean {
const modified = modifiedParameters.get(funcName);
if (!modified || modified.has(paramName)) {
return false;
}
modified.add(paramName);
return true;
}
}
export default TransitiveModificationPropagator;
|