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 | 1447x 1447x 1474x 1474x 1571x 98x 98x 30x 98x 98x 98x 38x 10x 28x 60x 60x 15x 45x 60x 60x 73x 73x 43x 30x 30x | /**
* 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>>,
resolveCalleeMayMutate: (
callerName: string,
callee: string,
paramIndex: number,
) => boolean,
): 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,
resolveCalleeMayMutate,
);
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>>,
resolveCalleeMayMutate: (
callerName: string,
callee: string,
paramIndex: number,
) => boolean,
): boolean {
const { callee, paramIndex, argParamName } = call;
// Get the callee's parameter list
const calleeParams = functionParamLists.get(callee);
if (!calleeParams || paramIndex >= calleeParams.length) {
// Issue #1178: this branch used to `return false`, which is the same
// answer as "the callee does not modify its argument". "I cannot resolve
// this callee" and "this callee is pure" are different facts, and
// collapsing them made the unsafe one the default: auto-const was applied
// on the strength of an absent answer.
//
// The callee is not a C-Next function in this build, so ask what it is.
// A C/C++ declaration answers definitively; anything still unknown fails
// safe by withholding auto-const, which costs a missed `const` rather
// than an incorrect one.
if (!resolveCalleeMayMutate(callerName, callee, paramIndex)) {
return false;
}
return TransitiveModificationPropagator.markParamModified(
callerName,
argParamName,
modifiedParameters,
);
}
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;
|