[FIR] Don't recalculate argumentVariables for the same node

`knownVariables` can't be moved to the outer
function, because it's specific to the current
path: we want to check that the contract
condition is approved along the current path.

For example, in the
`FirPsiDiagnosticTestGenerated.testPropertyGetter`
test when analyzing the function, we want
 to check if we can derive the `d1 == Any`
 type statement (where `d1` is the receiver).
 If we can't, the flow won't contain `d1`
 among `knownVariables`
This commit is contained in:
Nikolay Lunyak
2023-09-01 10:16:05 +03:00
committed by Space Team
parent 1c446151e7
commit bb8d3a0f80
@@ -54,11 +54,25 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
if (function !is FirContractDescriptionOwner || function.contractDescription.source == null) return if (function !is FirContractDescriptionOwner || function.contractDescription.source == null) return
val effects = function.contractDescription.effects ?: return val effects = function.contractDescription.effects ?: return
val dataFlowInfo = function.controlFlowGraphReference?.dataFlowInfo ?: return val dataFlowInfo = function.controlFlowGraphReference?.dataFlowInfo ?: return
val argumentIdentifiers = Array(function.valueParameters.size + 1) { i ->
val parameterSymbol = if (i > 0) {
function.valueParameters[i - 1].symbol
} else {
if (function.symbol is FirPropertyAccessorSymbol) {
context.containingProperty?.symbol
} else {
null
} ?: function.symbol
}
Identifier(parameterSymbol, null, null)
}
for (firEffect in effects) { for (firEffect in effects) {
val coneEffect = firEffect.effect as? ConeConditionalEffectDeclaration ?: continue val coneEffect = firEffect.effect as? ConeConditionalEffectDeclaration ?: continue
val returnValue = coneEffect.effect as? ConeReturnsEffectDeclaration ?: continue val returnValue = coneEffect.effect as? ConeReturnsEffectDeclaration ?: continue
val wrongCondition = graph.exitNode.previousCfgNodes.any { val wrongCondition = graph.exitNode.previousCfgNodes.any {
isWrongConditionOnNode(it, coneEffect, returnValue, function, logicSystem, dataFlowInfo, context) isWrongConditionOnNode(it, coneEffect, returnValue, function, logicSystem, dataFlowInfo, argumentIdentifiers, context)
} }
if (wrongCondition) { if (wrongCondition) {
// TODO, KT-59813: reportOn(firEffect.source, ...) // TODO, KT-59813: reportOn(firEffect.source, ...)
@@ -74,6 +88,7 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
function: FirFunction, function: FirFunction,
logicSystem: LogicSystem, logicSystem: LogicSystem,
dataFlowInfo: DataFlowInfo, dataFlowInfo: DataFlowInfo,
argumentIdentifiers: Array<Identifier>,
context: CheckerContext context: CheckerContext
): Boolean { ): Boolean {
val builtinTypes = context.session.builtinTypes val builtinTypes = context.session.builtinTypes
@@ -87,7 +102,7 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
if (isReturn && resultExpression is FirWhenExpression) { if (isReturn && resultExpression is FirWhenExpression) {
return node.collectBranchExits().any { return node.collectBranchExits().any {
isWrongConditionOnNode(it, effectDeclaration, effect, function, logicSystem, dataFlowInfo, context) isWrongConditionOnNode(it, effectDeclaration, effect, function, logicSystem, dataFlowInfo, argumentIdentifiers, context)
} }
} }
@@ -113,18 +128,8 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
// TODO, KT-59814: if this is not a top-level function, `FirDataFlowAnalyzer` has erased its value parameters // TODO, KT-59814: if this is not a top-level function, `FirDataFlowAnalyzer` has erased its value parameters
// from `dataFlowInfo.variableStorage` for some reason, so its `getLocalVariable` doesn't work. // from `dataFlowInfo.variableStorage` for some reason, so its `getLocalVariable` doesn't work.
val knownVariables = flow.knownVariables.associateBy { it.identifier } val knownVariables = flow.knownVariables.associateBy { it.identifier }
// TODO, KT-59815: these should be the same on all return paths, so maybe don't recompute them every time? val argumentVariables = Array(argumentIdentifiers.size) { i ->
val argumentVariables = Array(function.valueParameters.size + 1) { i -> val identifier = argumentIdentifiers[i]
val parameterSymbol = if (i > 0) {
function.valueParameters[i - 1].symbol
} else {
if (function.symbol is FirPropertyAccessorSymbol) {
context.containingProperty?.symbol
} else {
null
} ?: function.symbol
}
val identifier = Identifier(parameterSymbol, null, null)
// Might be unknown if there are no statements made about that parameter, but it's still possible that trivial // Might be unknown if there are no statements made about that parameter, but it's still possible that trivial
// contracts are valid. E.g. `returns() implies (x is String)` when `x`'s *original type* is already `String`. // contracts are valid. E.g. `returns() implies (x is String)` when `x`'s *original type* is already `String`.
knownVariables[identifier] ?: RealVariable(identifier, i == 0, null, i, PropertyStability.STABLE_VALUE) knownVariables[identifier] ?: RealVariable(identifier, i == 0, null, i, PropertyStability.STABLE_VALUE)