[FIR] Checkers performance improvement and some refactoring

This commit is contained in:
vldf
2020-08-05 17:39:43 +03:00
parent 5a45cc7290
commit 638a0d3b82
15 changed files with 112 additions and 56 deletions
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSessionComponent
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirCfaPropertyAssignmentChecker
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
@@ -70,6 +71,8 @@ private class ComposedDeclarationCheckers : DeclarationCheckers() {
get() = _constructorCheckers
override val controlFlowAnalyserCheckers: List<FirControlFlowChecker>
get() = _controlFlowAnalyserCheckers
override val variableAssignmentCfaBasedCheckers: List<AbstractFirCfaPropertyAssignmentChecker>
get() = _variableAssignmentCfaBasedCheckers
private val _fileCheckers: MutableList<FirFileChecker> = mutableListOf()
private val _declarationCheckers: MutableList<FirBasicDeclarationChecker> = mutableListOf()
@@ -77,6 +80,7 @@ private class ComposedDeclarationCheckers : DeclarationCheckers() {
private val _regularClassCheckers: MutableList<FirRegularClassChecker> = mutableListOf()
private val _constructorCheckers: MutableList<FirConstructorChecker> = mutableListOf()
private val _controlFlowAnalyserCheckers: MutableList<FirControlFlowChecker> = mutableListOf()
private val _variableAssignmentCfaBasedCheckers: MutableList<AbstractFirCfaPropertyAssignmentChecker> = mutableListOf()
fun register(checkers: DeclarationCheckers) {
_fileCheckers += checkers.allFileCheckers
@@ -85,6 +89,7 @@ private class ComposedDeclarationCheckers : DeclarationCheckers() {
_regularClassCheckers += checkers.allRegularClassCheckers
_constructorCheckers += checkers.allConstructorCheckers
_controlFlowAnalyserCheckers += checkers.controlFlowAnalyserCheckers
_variableAssignmentCfaBasedCheckers += checkers.variableAssignmentCfaBasedCheckers
}
}
@@ -8,14 +8,18 @@ package org.jetbrains.kotlin.fir.analysis.cfa
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentMapOf
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
abstract class AbstractFirPropertyInitializationChecker : FirControlFlowChecker() {
abstract override fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter)
abstract class AbstractFirCfaPropertyAssignmentChecker {
abstract fun analyze(
graph: ControlFlowGraph,
reporter: DiagnosticReporter,
data: Map<CFGNode<*>, PropertyInitializationInfo>,
properties: Set<FirPropertySymbol>
)
class PropertyInitializationInfo(
map: PersistentMap<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
@@ -15,7 +15,8 @@ import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
class FirControlFlowAnalyzer(session: FirSession) {
private val checkers = session.checkersComponent.declarationCheckers.controlFlowAnalyserCheckers
private val cfaCheckers = session.checkersComponent.declarationCheckers.controlFlowAnalyserCheckers
private val variableAssignmentCheckers = session.checkersComponent.declarationCheckers.variableAssignmentCfaBasedCheckers
fun analyzeClassInitializer(klass: FirClass<*>, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
if (graph.owner != null) return
@@ -24,7 +25,14 @@ class FirControlFlowAnalyzer(session: FirSession) {
fun analyzeFunction(function: FirFunction<*>, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
if (graph.owner != null) return
checkers.forEach { it.analyze(graph, reporter) }
cfaCheckers.forEach { it.analyze(graph, reporter) }
val properties = AbstractFirCfaPropertyAssignmentChecker.LocalPropertyCollector.collect(graph)
if (properties.isEmpty()) return
val data = AbstractFirCfaPropertyAssignmentChecker.DataCollector(properties).getData(graph)
variableAssignmentCheckers.forEach { it.analyze(graph, reporter, data, properties) }
}
fun analyzePropertyInitializer(property: FirProperty, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
@@ -16,15 +16,24 @@ import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraphVisitorVoid
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.QualifiedAccessNode
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChecker() {
override fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter) {
val localProperties = LocalPropertyCollector.collect(graph)
// we want to analyze only properties without initializers
localProperties.retainAll { it.fir.initializer == null && it.fir.delegate == null }
if (localProperties.isEmpty()) return
val data = DataCollector(localProperties).getData(graph)
val reporterVisitor = UninitializedPropertyReporter(data, localProperties, reporter)
object FirPropertyInitializationAnalyzer : AbstractFirCfaPropertyAssignmentChecker() {
override fun analyze(
graph: ControlFlowGraph,
reporter: DiagnosticReporter,
data: Map<CFGNode<*>, PropertyInitializationInfo>,
properties: Set<FirPropertySymbol>
) {
// getting symbols of properties without initializers and non-variable symbols
val localData = data.filter {
val symbolFir = (it.key.fir as? FirVariableSymbol<*>)?.fir
symbolFir == null || symbolFir.initializer == null && symbolFir.delegate == null
}
val localProperties = properties.filter { it.fir.initializer == null && it.fir.delegate == null }.toSet()
val reporterVisitor = UninitializedPropertyReporter(localData, localProperties, reporter)
graph.traverse(TraverseDirection.Forward, reporterVisitor)
}
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtModifierList
import org.jetbrains.kotlin.psi.psiUtil.toVisibility
@@ -185,7 +184,7 @@ fun FirClassLikeDeclaration<*>.followAllAlias(session: FirSession): FirClassLike
* or null if no such item could be found.
*/
fun CheckerContext.findClosestClassOrObject(): FirClass<*>? {
for (it in containingDeclarations.reversed()) {
for (it in containingDeclarations.asReversed()) {
if (
it is FirRegularClass ||
it is FirAnonymousObject
@@ -237,20 +236,20 @@ fun FirClass<*>.modality(): Modality? {
/**
* returns implicit modality by FirMemberDeclaration
*/
fun FirMemberDeclaration.implicitModality(context: CheckerContext): KtModifierKeywordToken {
fun FirMemberDeclaration.implicitModality(context: CheckerContext): Modality {
if (this is FirRegularClass && (this.classKind == ClassKind.CLASS || this.classKind == ClassKind.OBJECT)) {
if (this.classKind == ClassKind.INTERFACE) return KtTokens.ABSTRACT_KEYWORD
return KtTokens.FINAL_KEYWORD
if (this.classKind == ClassKind.INTERFACE) return Modality.ABSTRACT
return Modality.FINAL
}
val klass = context.findClosestClassOrObject() ?: return KtTokens.FINAL_KEYWORD
val modifiers = this.modifierListOrNull() ?: return KtTokens.FINAL_KEYWORD
val klass = context.findClosestClassOrObject() ?: return Modality.FINAL
val modifiers = this.modifierListOrNull() ?: return Modality.FINAL
if (modifiers.hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
val klassModifiers = klass.modifierListOrNull()
if (klassModifiers != null && klassModifiers.run {
hasModifier(KtTokens.ABSTRACT_KEYWORD) || hasModifier(KtTokens.OPEN_KEYWORD) || hasModifier(KtTokens.SEALED_KEYWORD)
}) {
return KtTokens.OPEN_KEYWORD
return Modality.OPEN
}
}
@@ -259,10 +258,10 @@ fun FirMemberDeclaration.implicitModality(context: CheckerContext): KtModifierKe
&& klass.classKind == ClassKind.INTERFACE
&& !modifiers.hasModifier(KtTokens.PRIVATE_KEYWORD)
) {
return if (this.hasBody()) KtTokens.OPEN_KEYWORD else KtTokens.ABSTRACT_KEYWORD
return if (this.hasBody()) Modality.OPEN else Modality.ABSTRACT
}
return KtTokens.FINAL_KEYWORD
return Modality.FINAL
}
private fun FirDeclaration.modifierListOrNull() = (this.source.getModifierList() as? FirPsiModifierList)?.modifierList
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirCfaPropertyAssignmentChecker
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
abstract class DeclarationCheckers {
@@ -18,6 +19,7 @@ abstract class DeclarationCheckers {
open val regularClassCheckers: List<FirRegularClassChecker> = emptyList()
open val constructorCheckers: List<FirConstructorChecker> = emptyList()
open val controlFlowAnalyserCheckers: List<FirControlFlowChecker> = emptyList()
open val variableAssignmentCfaBasedCheckers: List<AbstractFirCfaPropertyAssignmentChecker> = emptyList()
internal val allFileCheckers: List<FirFileChecker> get() = fileCheckers + declarationCheckers
internal val allMemberDeclarationCheckers: List<FirMemberDeclarationChecker> get() = memberDeclarationCheckers + declarationCheckers
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirCfaPropertyAssignmentChecker
import org.jetbrains.kotlin.fir.analysis.checkers.extended.*
object ExtendedDeclarationCheckers : DeclarationCheckers() {
@@ -20,7 +20,7 @@ object ExtendedDeclarationCheckers : DeclarationCheckers() {
RedundantSetterParameterTypeChecker
)
override val controlFlowAnalyserCheckers: List<FirControlFlowChecker> = listOf(
override val variableAssignmentCfaBasedCheckers: List<AbstractFirCfaPropertyAssignmentChecker> = listOf(
VariableAssignmentChecker
)
}
@@ -22,16 +22,19 @@ import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtOperationReferenceExpression
object CanBeReplacedWithOperatorAssignmentChecker : FirExpressionChecker<FirVariableAssignment>() {
override fun check(functionCall: FirVariableAssignment, context: CheckerContext, reporter: DiagnosticReporter) {
val lValue = functionCall.lValue
override fun check(assignment: FirVariableAssignment, context: CheckerContext, reporter: DiagnosticReporter) {
val lValue = assignment.lValue
if (lValue !is FirResolvedNamedReference) return
val operator = functionCall.psi?.children?.getOrNull(1) ?: return
if (operator.text != "=") return
val operator = assignment.psi?.children?.getOrNull(1) ?: return
val operationSign = (operator as? KtOperationReferenceExpression)?.operationSignTokenType
if (operationSign != KtTokens.EQ) return
val lValuePsi = lValue.psi as? KtNameReferenceExpression ?: return
val rValue = functionCall.rValue as? FirFunctionCall ?: return
val rValue = assignment.rValue as? FirFunctionCall ?: return
val rValuePsi = rValue.psi as? KtBinaryExpression ?: return
val rValueClassId = rValue.explicitReceiver?.typeRef?.coneType?.classId
@@ -56,9 +59,12 @@ object CanBeReplacedWithOperatorAssignmentChecker : FirExpressionChecker<FirVari
val isLeftMatch = isHierarchicallyTrue(operationToken, leftExpression?.operationToken)
&& leftExpression?.matcher(variable) == true
if (isLeftMatch) return true
val isRightMatch = isHierarchicallyTrue(operationToken, rightExpression?.operationToken)
&& rightExpression?.matcher(variable) == true
isLeftMatch or isRightMatch
if (isRightMatch) return true
false
} else {
val leftExpression = left as? KtBinaryExpression
@@ -10,17 +10,17 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirBasicExpresionChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.scopes.impl.FirIntegerOperatorCall
object EmptyRangeChecker : FirBasicExpresionChecker() {
override fun check(functionCall: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
if (functionCall.source is FirFakeSourceElement<*>) return
if (functionCall !is FirFunctionCall) return
val range = functionCall.psi ?: return
val left = range.children.getOrNull(0)?.text?.toLongOrNull() ?: return
val right = range.children.getOrNull(2)?.text?.toLongOrNull() ?: return
val left = functionCall.rangeLeft ?: return
val right = functionCall.rangeRight ?: return
val needReport = when (functionCall.calleeReference.name.asString()) {
"rangeTo" -> {
@@ -41,4 +41,28 @@ object EmptyRangeChecker : FirBasicExpresionChecker() {
}
}
private val FirFunctionCall.rangeLeft: Long?
get() {
return if (explicitReceiver is FirIntegerOperatorCall) {
(explicitReceiver as? FirIntegerOperatorCall)?.asLong
} else {
(explicitReceiver as? FirConstExpression<*>)?.value as? Long
}
}
private val FirFunctionCall.rangeRight: Long?
get() {
val arg = argumentList.arguments.getOrNull(0)
return if (arg is FirIntegerOperatorCall) arg.asLong
else (arg as? FirConstExpression<*>)?.value as? Long
}
private val FirIntegerOperatorCall.asLong: Long?
get() {
val value = (dispatchReceiver as? FirConstExpression<*>)?.value as Long? ?: return null
if (this.calleeReference.name.asString() == "unaryMinus") {
return -value
}
return value
}
}
@@ -37,7 +37,7 @@ object RedundantCallOfConversionMethod : FirQualifiedAccessChecker() {
}
}
private val targetClassMap = mapOf(
private val targetClassMap = hashMapOf(
"toString" to StandardClassIds.String,
"toDouble" to StandardClassIds.Double,
"toFloat" to StandardClassIds.Float,
@@ -5,8 +5,8 @@
package org.jetbrains.kotlin.fir.analysis.checkers.extended
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirFakeSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirMemberDeclarationChecker
@@ -15,10 +15,9 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REDUNDANT_MODALITY_MODIFIER
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.declarations.modality
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.toFirPsiSourceElement
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.psiUtil.modalityModifier
@@ -26,10 +25,9 @@ object RedundantModalityModifierChecker : FirMemberDeclarationChecker() {
override fun check(declaration: FirMemberDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.source is FirFakeSourceElement<*>) return
val modalityModifier = (declaration.psi as? KtDeclaration)?.modalityModifier() ?: return
val modality = (modalityModifier as? LeafPsiElement)?.elementType as? KtModifierKeywordToken ?: return
val modality = declaration.modality ?: return
if (
modality == KtTokens.FINAL_KEYWORD
modality == Modality.FINAL
&& (context.containingDeclarations.last() as? FirClass<*>)?.classKind == ClassKind.INTERFACE
) return
@@ -37,6 +35,7 @@ object RedundantModalityModifierChecker : FirMemberDeclarationChecker() {
if (modality != implicitModality) return
val modalityModifier = (declaration.psi as? KtDeclaration)?.modalityModifier() ?: return
reporter.report(modalityModifier.toFirPsiSourceElement(), REDUNDANT_MODALITY_MODIFIER)
}
}
@@ -19,10 +19,9 @@ object RedundantReturnUnitType : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration !is FirSimpleFunction) return
if (declaration.body is FirSingleExpressionBlock) return
if (declaration.returnTypeRef.source == null) return
if (declaration.source is FirFakeSourceElement<*>) return
val returnType = declaration.returnTypeRef
if (returnType.source == null) return
if (declaration.source is FirFakeSourceElement<*>) return
if (returnType.annotations.isNotEmpty()) return
if (returnType.isUnit) {
@@ -21,6 +21,7 @@ object RedundantSetterParameterTypeChecker : FirMemberDeclarationChecker() {
val valueParameter = setter.valueParameters.firstOrNull() ?: return
val propertyTypeSource = declaration.returnTypeRef.source
val setterParameterTypeSource = valueParameter.returnTypeRef.source ?: return
if (setterParameterTypeSource != propertyTypeSource) {
reporter.report(setterParameterTypeSource, REDUNDANT_SETTER_PARAMETER_TYPE)
}
@@ -33,7 +33,6 @@ object RedundantSingleExpressionStringTemplateChecker : FirBasicExpresionChecker
private fun PsiElement.findStringParent(): KtStringTemplateExpression? {
if (this is KtStringTemplateExpression) return this
return if (this.parent != null) this.parent.findStringParent()
else null
return this.parent?.findStringParent()
}
}
@@ -9,7 +9,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.fir.FirFakeSourceElement
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirCfaPropertyAssignmentChecker
import org.jetbrains.kotlin.fir.analysis.cfa.TraverseDirection
import org.jetbrains.kotlin.fir.analysis.cfa.traverse
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
@@ -22,16 +22,17 @@ import org.jetbrains.kotlin.fir.toFirPsiSourceElement
import org.jetbrains.kotlin.psi.KtProperty
object VariableAssignmentChecker : AbstractFirPropertyInitializationChecker() {
override fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter) {
object VariableAssignmentChecker : AbstractFirCfaPropertyAssignmentChecker() {
override fun analyze(
graph: ControlFlowGraph,
reporter: DiagnosticReporter,
data: Map<CFGNode<*>, PropertyInitializationInfo>,
properties: Set<FirPropertySymbol>
) {
val unprocessedProperties = mutableSetOf<FirPropertySymbol>()
val propertiesCharacteristics = mutableMapOf<FirPropertySymbol, EventOccurrencesRange>()
val localProperties = LocalPropertyCollector.collect(graph)
if (localProperties.isEmpty()) return
val data = DataCollector(localProperties).getData(graph)
val reporterVisitor = UninitializedPropertyReporter(data, localProperties, unprocessedProperties, propertiesCharacteristics)
val reporterVisitor = UninitializedPropertyReporter(data, properties, unprocessedProperties, propertiesCharacteristics)
graph.traverse(TraverseDirection.Forward, reporterVisitor)
for (property in unprocessedProperties) {
@@ -63,7 +64,7 @@ object VariableAssignmentChecker : AbstractFirPropertyInitializationChecker() {
destructuringCanBeVal = false
}
lastDestructuredVariables--
} else if (canBeVal(symbol, value) && symbol.fir.delegate == null ) {
} else if (canBeVal(symbol, value) && symbol.fir.delegate == null) {
reporter.report(source, FirErrors.CAN_BE_VAL)
}
}