mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-05 08:06:56 +07:00
[uast] IDEA-265918 handle conditional assignments
GitOrigin-RevId: 8299beb4088552902e473682f49a59820c907499
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a130a3e6a7
commit
f8c5165105
@@ -17,7 +17,7 @@ internal class DependencyGraphBuilder private constructor(
|
||||
val dependencies: MutableMap<UElement, MutableSet<Dependency>> = mutableMapOf(),
|
||||
) : AbstractUastVisitor() {
|
||||
|
||||
constructor(): this(currentDepth = 0)
|
||||
constructor() : this(currentDepth = 0)
|
||||
|
||||
private val elementsProcessedAsReceiver: MutableSet<UExpression> = HashSet()
|
||||
|
||||
@@ -128,13 +128,21 @@ internal class DependencyGraphBuilder private constructor(
|
||||
if (node.uastParent is UReferenceExpression && (node.uastParent as? UQualifiedReferenceExpression)?.receiver != node)
|
||||
return@checkedDepthCall true
|
||||
|
||||
val referenceInfo = DependencyOfReference.ReferenceInfo(node.identifier, currentScope.getReferencedValues(node.identifier))
|
||||
|
||||
currentScope[node.identifier]?.let {
|
||||
registerDependency(Dependent.CommonDependent(node), Dependency.BranchingDependency(it).unwrapIfSingle())
|
||||
registerDependency(
|
||||
Dependent.CommonDependent(node),
|
||||
Dependency.BranchingDependency(
|
||||
it,
|
||||
referenceInfo
|
||||
).unwrapIfSingle()
|
||||
)
|
||||
}
|
||||
|
||||
val potentialDependenciesCandidates = currentScope.getLastPotentialUpdate(node.identifier)
|
||||
if (potentialDependenciesCandidates != null) {
|
||||
registerDependency(Dependent.CommonDependent(node), Dependency.PotentialSideEffectDependency(potentialDependenciesCandidates))
|
||||
registerDependency(Dependent.CommonDependent(node), Dependency.PotentialSideEffectDependency(node, potentialDependenciesCandidates, referenceInfo))
|
||||
}
|
||||
return@checkedDepthCall super.visitSimpleNameReferenceExpression(node)
|
||||
}
|
||||
@@ -309,7 +317,7 @@ internal class DependencyGraphBuilder private constructor(
|
||||
override fun visitField(node: UField): Boolean = true
|
||||
|
||||
private fun updatePotentialEqualReferences(name: String, initElements: Set<UElement>) {
|
||||
currentScope.clearPotentialReferences(name)
|
||||
currentScope.clearPotentialReferences(TEMP_VAR_NAME)
|
||||
val potentialEqualReferences = initElements
|
||||
.mapNotNull {
|
||||
when (it) {
|
||||
@@ -319,9 +327,11 @@ internal class DependencyGraphBuilder private constructor(
|
||||
}
|
||||
}
|
||||
for ((potentialEqualReference, evidence) in potentialEqualReferences) {
|
||||
currentScope.setPotentialEquality(name, potentialEqualReference,
|
||||
DependencyEvidence(potentialEqualReferences.size == 1, evidence, potentialEqualReference))
|
||||
currentScope.setPotentialEquality(TEMP_VAR_NAME, potentialEqualReference, DependencyEvidence(evidence))
|
||||
}
|
||||
currentScope.clearPotentialReferences(name)
|
||||
currentScope.setPotentialEquality(name, TEMP_VAR_NAME, DependencyEvidence())
|
||||
currentScope.clearPotentialReferences(TEMP_VAR_NAME)
|
||||
}
|
||||
|
||||
private fun registerDependency(dependent: Dependent, dependency: Dependency) {
|
||||
@@ -344,7 +354,7 @@ private typealias SideEffectChangeCandidate = Dependency.PotentialSideEffectDepe
|
||||
private typealias DependencyEvidence = Dependency.PotentialSideEffectDependency.DependencyEvidence
|
||||
private typealias CandidatesTree = Dependency.PotentialSideEffectDependency.CandidatesTree
|
||||
|
||||
private class LocalScopeContext(private val parent: LocalScopeContext?) {
|
||||
class LocalScopeContext(private val parent: LocalScopeContext?) {
|
||||
private val definedInScopeVariables = HashSet<UElement>()
|
||||
private val definedInScopeVariablesNames = HashSet<String>()
|
||||
|
||||
@@ -371,6 +381,7 @@ private class LocalScopeContext(private val parent: LocalScopeContext?) {
|
||||
definedInScopeVariables.add(variable)
|
||||
variable.name?.let {
|
||||
definedInScopeVariablesNames.add(it)
|
||||
referencesModel.assignValueIfNotAssigned(it)
|
||||
lastDeclarationOf[it] = variable
|
||||
}
|
||||
}
|
||||
@@ -387,9 +398,13 @@ private class LocalScopeContext(private val parent: LocalScopeContext?) {
|
||||
fun createChild() = LocalScopeContext(this)
|
||||
|
||||
fun setLastPotentialUpdate(variable: String, updateElement: UElement) {
|
||||
lastPotentialUpdatesOf[variable] = CandidatesTree.fromCandidate(SideEffectChangeCandidate(updateElement, DependencyEvidence(true)))
|
||||
for ((reference, evidence) in getAllPotentialEqualReferences(variable)) {
|
||||
val newCandidate = SideEffectChangeCandidate(updateElement, evidence)
|
||||
lastPotentialUpdatesOf[variable] = CandidatesTree.fromCandidate(
|
||||
SideEffectChangeCandidate(updateElement, DependencyEvidence(),
|
||||
dependencyWitnessValues = referencesModel.getAllTargetsForReference(variable))
|
||||
)
|
||||
for ((reference, evidenceAndWitness) in referencesModel.getAllPossiblyEqualReferences(variable)) {
|
||||
val (evidence, witness) = evidenceAndWitness
|
||||
val newCandidate = SideEffectChangeCandidate(updateElement, evidence, witness)
|
||||
val candidatesForReference = lastPotentialUpdatesOf[reference]
|
||||
lastPotentialUpdatesOf[reference] = candidatesForReference?.addToBegin(newCandidate) ?: CandidatesTree.fromCandidate(newCandidate)
|
||||
}
|
||||
@@ -398,12 +413,17 @@ private class LocalScopeContext(private val parent: LocalScopeContext?) {
|
||||
fun setLastPotentialUpdateAsAssignment(variable: String, updateElements: Collection<UElement>) {
|
||||
if (updateElements.size == 1) {
|
||||
lastPotentialUpdatesOf[variable] = CandidatesTree.fromCandidate(
|
||||
SideEffectChangeCandidate(updateElements.first(), DependencyEvidence(true))
|
||||
SideEffectChangeCandidate(
|
||||
updateElements.first(),
|
||||
DependencyEvidence(),
|
||||
dependencyWitnessValues = referencesModel.getAllTargetsForReference(variable))
|
||||
)
|
||||
}
|
||||
else {
|
||||
lastPotentialUpdatesOf[variable] = CandidatesTree.fromCandidates(
|
||||
updateElements.mapTo(mutableSetOf()) { SideEffectChangeCandidate(it, DependencyEvidence(true)) }
|
||||
updateElements.mapTo(mutableSetOf()) {
|
||||
SideEffectChangeCandidate(it, DependencyEvidence(), dependencyWitnessValues = referencesModel.getAllTargetsForReference(variable))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -419,10 +439,6 @@ private class LocalScopeContext(private val parent: LocalScopeContext?) {
|
||||
referencesModel.clearReference(reference)
|
||||
}
|
||||
|
||||
fun getAllPotentialEqualReferences(reference: String): Map<String, DependencyEvidence> {
|
||||
return referencesModel.getAllPossiblyEqualReferences(reference)
|
||||
}
|
||||
|
||||
val variables: Iterable<UElement>
|
||||
get() {
|
||||
return generateSequence(this) { it.parent }
|
||||
@@ -451,7 +467,7 @@ private class LocalScopeContext(private val parent: LocalScopeContext?) {
|
||||
}
|
||||
}
|
||||
}.takeUnless { it.isEmpty() }?.let { candidates ->
|
||||
lastPotentialUpdatesOf[variableName] = CandidatesTree.merge(candidates)
|
||||
lastPotentialUpdatesOf[variableName] = CandidatesTree.merge(candidates)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -466,35 +482,50 @@ private class LocalScopeContext(private val parent: LocalScopeContext?) {
|
||||
}
|
||||
}
|
||||
|
||||
fun getReferencedValues(identifier: String): Collection<UValueMark> {
|
||||
return referencesModel.getAllTargetsForReference(identifier)
|
||||
}
|
||||
|
||||
private class ReferencesModel(private val parent: ReferencesModel?) {
|
||||
private class Target
|
||||
private val referencesTargets = mutableMapOf<String, MutableMap<UValueMark, DependencyEvidence>>()
|
||||
private val targetsReferences = mutableMapOf<UValueMark, MutableMap<String, DependencyEvidence>>()
|
||||
|
||||
private val referencesTargets = mutableMapOf<String, MutableMap<Target, DependencyEvidence>>()
|
||||
private val targetsReferences = mutableMapOf<Target, MutableSet<String>>()
|
||||
private fun getAllReferences(referencedValue: UValueMark): Map<String, DependencyEvidence> =
|
||||
parent?.getAllReferences(referencedValue).orEmpty() + targetsReferences[referencedValue].orEmpty()
|
||||
|
||||
private fun getAllReferences(target: Target): Set<String> =
|
||||
parent?.getAllReferences(target).orEmpty() + targetsReferences[target].orEmpty()
|
||||
|
||||
private fun getAllTargets(reference: String): Map<Target, DependencyEvidence> =
|
||||
private fun getAllTargets(reference: String): Map<UValueMark, DependencyEvidence> =
|
||||
listOfNotNull(referencesTargets[reference], parent?.getAllTargets(reference)).fold(emptyMap()) { result, current ->
|
||||
(result.keys + current.keys).associateWith { (result[it] ?: current[it])!! }
|
||||
}
|
||||
|
||||
fun assignValueIfNotAssigned(reference: String) {
|
||||
if (getAllTargets(reference).isNotEmpty()) return
|
||||
|
||||
val evidence = DependencyEvidence()
|
||||
val newTarget = UValueMark()
|
||||
referencesTargets[reference] = mutableMapOf(newTarget to evidence)
|
||||
targetsReferences[newTarget] = mutableMapOf(reference to evidence)
|
||||
}
|
||||
|
||||
fun setPossibleEquality(assigneeReference: String, targetReference: String, evidence: DependencyEvidence) {
|
||||
val targets = getAllTargets(targetReference).toMutableMap()
|
||||
if (targets.isEmpty()) {
|
||||
val newTarget = Target()
|
||||
referencesTargets[targetReference] = mutableMapOf(newTarget to DependencyEvidence(true)) // equal by default
|
||||
val newTarget = UValueMark()
|
||||
val targetEvidence = DependencyEvidence()
|
||||
referencesTargets[targetReference] = mutableMapOf(newTarget to targetEvidence) // equal by default
|
||||
referencesTargets.getOrPut(assigneeReference) { mutableMapOf() }[newTarget] = evidence
|
||||
|
||||
targetsReferences[newTarget] = mutableSetOf(assigneeReference, targetReference)
|
||||
targetsReferences[newTarget] = mutableMapOf(
|
||||
assigneeReference to evidence,
|
||||
targetReference to targetEvidence
|
||||
)
|
||||
return
|
||||
}
|
||||
referencesTargets.getOrPut(assigneeReference) { mutableMapOf() }
|
||||
.putAll(targets.mapValues { (_, evidenceForTarget) -> evidence.copy(requires = evidenceForTarget) })
|
||||
.putAll(targets.mapValues { (_, evidenceForTarget) -> evidence.copy(requires = listOf(evidenceForTarget)) })
|
||||
|
||||
for (target in targets.keys) {
|
||||
targetsReferences.getOrPut(target) { mutableSetOf() }.add(assigneeReference)
|
||||
targetsReferences.getOrPut(target) { mutableMapOf() }[assigneeReference] = evidence
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,14 +543,29 @@ private class LocalScopeContext(private val parent: LocalScopeContext?) {
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllPossiblyEqualReferences(reference: String): Map<String, DependencyEvidence> =
|
||||
getAllTargets(reference)
|
||||
.map { (target, evidence) -> getAllReferences(target).map { it to evidence } }
|
||||
fun getAllPossiblyEqualReferences(reference: String): Map<String, Pair<DependencyEvidence, Collection<UValueMark>>> {
|
||||
val allTargets = getAllTargets(reference)
|
||||
return allTargets
|
||||
.map { (target, evidence) ->
|
||||
getAllReferences(target).map { (currentReference, referenceEvidence) ->
|
||||
currentReference to (combineEvidences(evidence, referenceEvidence) to allTargets.keys)
|
||||
}
|
||||
}
|
||||
.flatten()
|
||||
.filter { it.first != reference }
|
||||
.toMap()
|
||||
}
|
||||
|
||||
fun getAllTargetsForReference(reference: String): Collection<UValueMark> {
|
||||
return getAllTargets(reference).keys
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private const val UAST_KT_ELVIS_NAME = "elvis"
|
||||
private fun combineEvidences(ownEvidence: DependencyEvidence, otherEvidence: DependencyEvidence): DependencyEvidence =
|
||||
ownEvidence.copy(requires = ownEvidence.requires + otherEvidence)
|
||||
|
||||
private const val UAST_KT_ELVIS_NAME = "elvis"
|
||||
|
||||
private const val TEMP_VAR_NAME = "@$,()"
|
||||
@@ -16,6 +16,7 @@ import com.intellij.util.Plow
|
||||
import com.intellij.util.containers.addIfNotNull
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.uast.*
|
||||
import org.jetbrains.uast.analysis.Dependency.PotentialSideEffectDependency.*
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor
|
||||
|
||||
@ApiStatus.Experimental
|
||||
@@ -79,7 +80,7 @@ class UStringEvaluator {
|
||||
}
|
||||
val builderEvaluator = configuration.getBuilderEvaluatorForCall(element)
|
||||
if (builderEvaluator != null) {
|
||||
return calculateBuilder(graph, element, configuration, builderEvaluator)
|
||||
return calculateBuilder(graph, element, configuration, builderEvaluator, null, null)
|
||||
}
|
||||
if (element.resolve()?.let { configuration.methodsToAnalyzePattern.accepts(it) } == true) {
|
||||
return PartiallyKnownString(
|
||||
@@ -177,48 +178,93 @@ class UStringEvaluator {
|
||||
graph: UastLocalUsageDependencyGraph,
|
||||
element: UElement,
|
||||
configuration: Configuration,
|
||||
builderEvaluator: BuilderLikeExpressionEvaluator<PartiallyKnownString?>
|
||||
builderEvaluator: BuilderLikeExpressionEvaluator<PartiallyKnownString?>,
|
||||
currentObjectsToAnalyze: Collection<UValueMark>?,
|
||||
originalObjectsToAnalyze: Collection<UValueMark>?
|
||||
): PartiallyKnownString? {
|
||||
if (element is UCallExpression) {
|
||||
val methodEvaluator = builderEvaluator.methodDescriptions.entries.firstOrNull { (pattern, _) -> pattern.accepts(element.resolve()) }
|
||||
if (methodEvaluator != null) {
|
||||
val dependencies = graph.dependencies[element].orEmpty()
|
||||
return when (val dependency = dependencies.firstOrNull { it !is Dependency.PotentialSideEffectDependency && it !is Dependency.ArgumentDependency }) {
|
||||
val isStrict = currentObjectsToAnalyze == originalObjectsToAnalyze
|
||||
return when (
|
||||
val dependency = dependencies
|
||||
.firstOrNull { it !is Dependency.PotentialSideEffectDependency && it !is Dependency.ArgumentDependency }
|
||||
) {
|
||||
is Dependency.BranchingDependency -> {
|
||||
val branchResult = dependency.elements.mapNotNull { calculateBuilder(graph, it, configuration, builderEvaluator) }.collapse(
|
||||
element)
|
||||
methodEvaluator.value(element, branchResult, this, configuration)
|
||||
val branchResult = dependency.elements.mapNotNull {
|
||||
calculateBuilder(graph, it, configuration, builderEvaluator, currentObjectsToAnalyze, originalObjectsToAnalyze)
|
||||
}.collapse(element)
|
||||
methodEvaluator.value(element, branchResult, this, configuration, isStrict)
|
||||
}
|
||||
is Dependency.CommonDependency -> {
|
||||
val result = calculateBuilder(graph, dependency.element, configuration, builderEvaluator)
|
||||
methodEvaluator.value(element, result, this, configuration)
|
||||
val result = calculateBuilder(
|
||||
graph,
|
||||
dependency.element,
|
||||
configuration,
|
||||
builderEvaluator,
|
||||
currentObjectsToAnalyze,
|
||||
originalObjectsToAnalyze
|
||||
)
|
||||
methodEvaluator.value(element, result, this, configuration, isStrict)
|
||||
}
|
||||
is Dependency.PotentialSideEffectDependency -> null // there should not be anything
|
||||
else -> methodEvaluator.value(element, null, this, configuration)
|
||||
else -> methodEvaluator.value(element, null, this, configuration, isStrict)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dependencies = graph.dependencies[element].orEmpty()
|
||||
val dependency = (dependencies.firstOrNull { it is Dependency.PotentialSideEffectDependency }.takeIf { builderEvaluator.allowSideEffects }
|
||||
?: dependencies.firstOrNull { it !is Dependency.PotentialSideEffectDependency && it !is Dependency.ArgumentDependency })
|
||||
val (dependency, candidates) = selectDependency(dependencies, builderEvaluator) {
|
||||
(originalObjectsToAnalyze == null ||
|
||||
it.dependencyWitnessValues.intersect(originalObjectsToAnalyze).isNotEmpty()) &&
|
||||
provePossibleDependency(it.dependencyEvidence, builderEvaluator)
|
||||
}
|
||||
|
||||
if (
|
||||
dependency is DependencyOfReference &&
|
||||
originalObjectsToAnalyze != null &&
|
||||
dependency.referenceInfo?.possibleReferencedValues?.intersect(originalObjectsToAnalyze)?.isEmpty() == true
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return when (dependency) {
|
||||
is Dependency.BranchingDependency -> {
|
||||
PartiallyKnownString(StringEntry.Unknown(
|
||||
element.sourcePsi!!, element.ownTextRange,
|
||||
dependency.elements.mapNotNull {
|
||||
calculateBuilder(graph, it, configuration, builderEvaluator)
|
||||
}.takeUnless { it.isEmpty() }
|
||||
))
|
||||
val variants = dependency.elements.mapNotNull {
|
||||
calculateBuilder(graph, it, configuration, builderEvaluator, currentObjectsToAnalyze, originalObjectsToAnalyze)
|
||||
}.takeUnless { it.isEmpty() }
|
||||
|
||||
if (variants?.size == 1) {
|
||||
variants.single()
|
||||
} else {
|
||||
PartiallyKnownString(StringEntry.Unknown(
|
||||
element.sourcePsi!!,
|
||||
element.ownTextRange,
|
||||
variants
|
||||
))
|
||||
}
|
||||
}
|
||||
is Dependency.CommonDependency -> calculateBuilder(graph, dependency.element, configuration, builderEvaluator)
|
||||
is Dependency.CommonDependency -> calculateBuilder(
|
||||
graph,
|
||||
dependency.element,
|
||||
configuration,
|
||||
builderEvaluator,
|
||||
currentObjectsToAnalyze,
|
||||
originalObjectsToAnalyze
|
||||
)
|
||||
is Dependency.PotentialSideEffectDependency -> if (!builderEvaluator.allowSideEffects) null
|
||||
else {
|
||||
dependency.candidates
|
||||
.selectPotentialCandidates { provePossibleDependency(it, builderEvaluator) }
|
||||
candidates
|
||||
.mapNotNull { candidate ->
|
||||
calculateBuilder(graph, candidate.updateElement, configuration, builderEvaluator)
|
||||
calculateBuilder(
|
||||
graph,
|
||||
candidate.updateElement,
|
||||
configuration,
|
||||
builderEvaluator,
|
||||
candidate.dependencyWitnessValues,
|
||||
originalObjectsToAnalyze ?: dependency.referenceInfo?.possibleReferencedValues
|
||||
)
|
||||
}
|
||||
.collapse(element)
|
||||
}
|
||||
@@ -227,13 +273,38 @@ class UStringEvaluator {
|
||||
}
|
||||
|
||||
private fun provePossibleDependency(
|
||||
evidence: Dependency.PotentialSideEffectDependency.DependencyEvidence,
|
||||
builderEvaluator: BuilderLikeExpressionEvaluator<PartiallyKnownString?>
|
||||
evidence: DependencyEvidence,
|
||||
builderEvaluator: BuilderLikeExpressionEvaluator<PartiallyKnownString?>,
|
||||
visitedEvidences: MutableSet<DependencyEvidence> = mutableSetOf()
|
||||
): Boolean {
|
||||
return (evidence.evidenceElement == null || builderEvaluator.isExpressionReturnSelf(evidence.evidenceElement)) &&
|
||||
(evidence.requires == null || provePossibleDependency(evidence.requires, builderEvaluator))
|
||||
if (evidence in visitedEvidences) return false // Cyclic evidence
|
||||
visitedEvidences += evidence
|
||||
|
||||
val result = (evidence.evidenceElement == null || builderEvaluator.isExpressionReturnSelf(evidence.evidenceElement)) &&
|
||||
(evidence.requires.isEmpty() || evidence.requires.all { provePossibleDependency(it, builderEvaluator, visitedEvidences) })
|
||||
|
||||
visitedEvidences -= evidence
|
||||
return result
|
||||
}
|
||||
|
||||
private fun selectDependency(
|
||||
dependencies: Collection<Dependency>,
|
||||
builderEvaluator: BuilderLikeExpressionEvaluator<PartiallyKnownString?>,
|
||||
candidateChecker: (SideEffectChangeCandidate) -> Boolean
|
||||
): Pair<Dependency?, Collection<SideEffectChangeCandidate>> =
|
||||
dependencies
|
||||
.firstOrNull { it is Dependency.PotentialSideEffectDependency }
|
||||
.takeIf { builderEvaluator.allowSideEffects }
|
||||
?.let { dependency ->
|
||||
(dependency as Dependency.PotentialSideEffectDependency).candidates
|
||||
.selectPotentialCandidates(candidateChecker)
|
||||
.takeUnless { it.isEmpty() }
|
||||
?.let { candidates ->
|
||||
dependency to candidates
|
||||
}
|
||||
}
|
||||
?: (dependencies.firstOrNull { it !is Dependency.PotentialSideEffectDependency && it !is Dependency.ArgumentDependency } to emptyList())
|
||||
|
||||
fun interface DeclarationValueProvider {
|
||||
fun provideValue(element: UDeclaration): PartiallyKnownString?
|
||||
}
|
||||
@@ -247,7 +318,7 @@ class UStringEvaluator {
|
||||
|
||||
val allowSideEffects: Boolean
|
||||
|
||||
val methodDescriptions: Map<ElementPattern<PsiMethod>, (UCallExpression, T, UStringEvaluator, Configuration) -> T>
|
||||
val methodDescriptions: Map<ElementPattern<PsiMethod>, (UCallExpression, T, UStringEvaluator, Configuration, isStrict: Boolean) -> T>
|
||||
|
||||
fun isExpressionReturnSelf(expression: UReferenceExpression): Boolean = false
|
||||
}
|
||||
@@ -289,10 +360,11 @@ private fun List<PartiallyKnownString>.collapse(element: UElement): PartiallyKno
|
||||
}
|
||||
|
||||
if (segments.size != maxIndex + 1) {
|
||||
segments.add(
|
||||
StringEntry.Unknown(element.sourcePsi!!, element.ownTextRange,
|
||||
map { PartiallyKnownString(it.segments.subList(segments.size, it.segments.size)) })
|
||||
)
|
||||
segments.add(StringEntry.Unknown(
|
||||
element.sourcePsi!!,
|
||||
element.ownTextRange,
|
||||
map { PartiallyKnownString(it.segments.subList(segments.size, it.segments.size)) }
|
||||
))
|
||||
}
|
||||
|
||||
PartiallyKnownString(segments)
|
||||
|
||||
@@ -199,6 +199,11 @@ private fun dumpDependencies(dependencies: Map<UElement, Set<Dependency>>): Stri
|
||||
for ((dependency, depIndex) in dependencyToID) {
|
||||
append("object ")
|
||||
append(dependency.javaClass.simpleName).append("_").append(depIndex)
|
||||
if (dependency is DependencyOfReference) {
|
||||
append(" {\n")
|
||||
indent().append("values = ").append(dependency.referenceInfo?.possibleReferencedValues).append("\n")
|
||||
append("}")
|
||||
}
|
||||
append("\n")
|
||||
}
|
||||
|
||||
@@ -228,8 +233,15 @@ private fun dumpDependencies(dependencies: Map<UElement, Set<Dependency>>): Stri
|
||||
indent().indent().append("type = ").append(node.javaClass.simpleName).append("\n")
|
||||
if (node is Dependency.PotentialSideEffectDependency.CandidatesTree.Node.CandidateNode) {
|
||||
val candidate = node.candidate
|
||||
indent().indent().append("witness = ").append(candidate.dependencyWitnessValues).append("\n")
|
||||
indent().indent().append("evidence = ").append(candidate.dependencyEvidence.evidenceElement?.asRenderString()?.escape()).append("\n")
|
||||
indent().indent().append("strict = ").append(candidate.dependencyEvidence.strict).append("\n")
|
||||
|
||||
generateSequence(candidate.dependencyEvidence.requires) { reqs -> reqs.flatMap { req -> req.requires }.takeUnless { it.isEmpty() } }
|
||||
.flatten()
|
||||
.map { it.evidenceElement?.asRenderString()?.escape() }
|
||||
.toSet()
|
||||
.takeUnless { it.isEmpty() }
|
||||
?.joinTo(this, prefix = "$INDENT${INDENT}require = ", postfix = "\n", separator = " && ")
|
||||
}
|
||||
indent().append("}\n")
|
||||
if (node is Dependency.PotentialSideEffectDependency.CandidatesTree.Node.CandidateNode) {
|
||||
@@ -283,8 +295,10 @@ private fun StringBuilder.edge(begin: String, type: String, end: String) {
|
||||
append(begin).append(" ").append(type).append(" ").append(end).append("\n")
|
||||
}
|
||||
|
||||
private const val INDENT = " "
|
||||
|
||||
private fun StringBuilder.indent(): StringBuilder {
|
||||
return append(" ")
|
||||
return append(INDENT)
|
||||
}
|
||||
|
||||
private fun String.escape() =
|
||||
|
||||
@@ -26,21 +26,27 @@ object UStringBuilderEvaluator : UStringEvaluator.BuilderLikeExpressionEvaluator
|
||||
return qualifiedChain.firstOrNull() is USimpleNameReferenceExpression && qualifiedChain.drop(1).all { callPattern.accepts(it) }
|
||||
}
|
||||
|
||||
override val methodDescriptions: Map<ElementPattern<PsiMethod>, (UCallExpression, PartiallyKnownString?, UStringEvaluator, UStringEvaluator.Configuration) -> PartiallyKnownString?>
|
||||
override val methodDescriptions: Map<ElementPattern<PsiMethod>, (UCallExpression, PartiallyKnownString?, UStringEvaluator, UStringEvaluator.Configuration, Boolean) -> PartiallyKnownString?>
|
||||
get() = mapOf(
|
||||
PsiJavaPatterns.psiMethod().withName("append").definedInClass("java.lang.StringBuilder") to { call, currentResult, stringEvaluator, config ->
|
||||
val entries = currentResult?.segments?.toMutableList() ?: mutableListOf<StringEntry>(
|
||||
StringEntry.Unknown(call.sourcePsi!!, TextRange(0, 1)))
|
||||
val argument = call.getArgumentForParameter(0)?.let { argument ->
|
||||
stringEvaluator.calculateValue(argument, config)
|
||||
}
|
||||
PsiJavaPatterns.psiMethod().withName("append").definedInClass("java.lang.StringBuilder") to { call, currentResult, stringEvaluator, config, isStrict ->
|
||||
val entries = currentResult?.segments?.toMutableList()
|
||||
?: mutableListOf<StringEntry>(StringEntry.Unknown(call.sourcePsi!!, TextRange(0, 1)))
|
||||
val argument = call.getArgumentForParameter(0)?.let { argument -> stringEvaluator.calculateValue(argument, config) }
|
||||
if (argument != null) {
|
||||
entries.addAll(argument.segments)
|
||||
if (isStrict) {
|
||||
entries.addAll(argument.segments)
|
||||
} else {
|
||||
entries.add(StringEntry.Unknown(
|
||||
call.sourcePsi!!,
|
||||
TextRange(0, call.sourcePsi!!.textLength),
|
||||
possibleValues = listOf(PartiallyKnownString(argument.segments), PartiallyKnownString(""))
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
PartiallyKnownString(entries)
|
||||
},
|
||||
PsiJavaPatterns.psiMethod().definedInClass("java.lang.StringBuilder").constructor(true) to { call, _, stringEvaluator, config ->
|
||||
PsiJavaPatterns.psiMethod().definedInClass("java.lang.StringBuilder").constructor(true) to { call, _, stringEvaluator, config, _ ->
|
||||
call.getArgumentForParameter(0)?.let { argument ->
|
||||
stringEvaluator.calculateValue(argument, config)
|
||||
}
|
||||
|
||||
@@ -26,10 +26,14 @@ sealed class Dependent : UserDataHolderBase() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed class Dependency : UserDataHolderBase() {
|
||||
abstract val elements: Set<UElement>
|
||||
|
||||
data class CommonDependency(val element: UElement) : Dependency() {
|
||||
data class CommonDependency(
|
||||
val element: UElement,
|
||||
override val referenceInfo: DependencyOfReference.ReferenceInfo? = null
|
||||
) : Dependency(), DependencyOfReference {
|
||||
override val elements = setOf(element)
|
||||
}
|
||||
|
||||
@@ -37,10 +41,13 @@ sealed class Dependency : UserDataHolderBase() {
|
||||
override val elements = setOf(element)
|
||||
}
|
||||
|
||||
data class BranchingDependency(override val elements: Set<UElement>) : Dependency() {
|
||||
data class BranchingDependency(
|
||||
override val elements: Set<UElement>,
|
||||
override val referenceInfo: DependencyOfReference.ReferenceInfo? = null
|
||||
) : Dependency(), DependencyOfReference {
|
||||
fun unwrapIfSingle(): Dependency =
|
||||
if (elements.size == 1) {
|
||||
CommonDependency(elements.single())
|
||||
CommonDependency(elements.single(), referenceInfo)
|
||||
}
|
||||
else {
|
||||
this
|
||||
@@ -62,17 +69,20 @@ sealed class Dependency : UserDataHolderBase() {
|
||||
get() = dependencyFromConnectedGraph.elements
|
||||
}
|
||||
|
||||
data class PotentialSideEffectDependency(val candidates: CandidatesTree) : Dependency() {
|
||||
data class PotentialSideEffectDependency(
|
||||
val currentReference: USimpleNameReferenceExpression,
|
||||
val candidates: CandidatesTree,
|
||||
override val referenceInfo: DependencyOfReference.ReferenceInfo? = null
|
||||
) : Dependency(), DependencyOfReference {
|
||||
data class SideEffectChangeCandidate(
|
||||
val updateElement: UElement,
|
||||
val dependencyEvidence: DependencyEvidence
|
||||
val dependencyEvidence: DependencyEvidence,
|
||||
val dependencyWitnessValues: Collection<UValueMark> = emptyList(),
|
||||
)
|
||||
|
||||
data class DependencyEvidence(
|
||||
val strict: Boolean,
|
||||
val evidenceElement: UReferenceExpression? = null,
|
||||
val dependencyWitnessIdentifier: String? = null,
|
||||
val requires: DependencyEvidence? = null
|
||||
val requires: Collection<DependencyEvidence> = emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -91,8 +101,8 @@ sealed class Dependency : UserDataHolderBase() {
|
||||
/**
|
||||
* Select first proven candidate on each path to leaf.
|
||||
*/
|
||||
fun selectPotentialCandidates(evidenceChecker: (DependencyEvidence) -> Boolean): Collection<SideEffectChangeCandidate> {
|
||||
return selectFromBranches(root, evidenceChecker)
|
||||
fun selectPotentialCandidates(candidateChecker: (SideEffectChangeCandidate) -> Boolean): Collection<SideEffectChangeCandidate> {
|
||||
return selectFromBranches(root, candidateChecker)
|
||||
}
|
||||
|
||||
fun addToBegin(candidate: SideEffectChangeCandidate): CandidatesTree {
|
||||
@@ -109,8 +119,9 @@ sealed class Dependency : UserDataHolderBase() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun selectFromBranches(node: Node, evidenceChecker: (DependencyEvidence) -> Boolean): Collection<SideEffectChangeCandidate> {
|
||||
return if (node is Node.CandidateNode && evidenceChecker(node.candidate.dependencyEvidence)) {
|
||||
private fun selectFromBranches(node: Node,
|
||||
evidenceChecker: (SideEffectChangeCandidate) -> Boolean): Collection<SideEffectChangeCandidate> {
|
||||
return if (node is Node.CandidateNode && evidenceChecker(node.candidate)) {
|
||||
listOf(node.candidate)
|
||||
}
|
||||
else {
|
||||
@@ -139,4 +150,16 @@ sealed class Dependency : UserDataHolderBase() {
|
||||
fun and(other: Dependency): Dependency {
|
||||
return BranchingDependency(elements + other.elements)
|
||||
}
|
||||
}
|
||||
|
||||
interface DependencyOfReference {
|
||||
val referenceInfo: ReferenceInfo?
|
||||
|
||||
data class ReferenceInfo(val identifier: String, val possibleReferencedValues: Collection<UValueMark>)
|
||||
}
|
||||
|
||||
class UValueMark {
|
||||
override fun toString(): String {
|
||||
return "UValueMark@${System.identityHashCode(this)}"
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import org.jetbrains.uast.analysis.UStringEvaluator
|
||||
import org.jetbrains.uast.test.java.AbstractJavaUastLightTest
|
||||
import org.jetbrains.uast.toUElement
|
||||
|
||||
open class AbstractStringEvaluatorTest : AbstractJavaUastLightTest() {
|
||||
abstract class AbstractStringEvaluatorTest : AbstractJavaUastLightTest() {
|
||||
protected val PartiallyKnownString.debugConcatenation: String
|
||||
get() = buildString {
|
||||
for (segment in segments) {
|
||||
|
||||
@@ -101,8 +101,7 @@ class UStringEvaluatorWithSideEffectsTest : AbstractStringEvaluatorTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun `ignore test StringBuilder change through possible reference`() {
|
||||
fun `test StringBuilder change through possible reference`() {
|
||||
doTest(
|
||||
"""
|
||||
class MyFile {
|
||||
@@ -112,12 +111,14 @@ class UStringEvaluatorWithSideEffectsTest : AbstractStringEvaluatorTest() {
|
||||
|
||||
StringBuilder sb2 = param ? sb : sb1; // ignore sb1 because sb1 != sb
|
||||
|
||||
s1.append("-");
|
||||
sb1.append("-");
|
||||
|
||||
StringBuilder sb3 = sb2;
|
||||
|
||||
sb3.append("c"); // add because of equality (strict = true)
|
||||
|
||||
sb.append("\\m/");
|
||||
|
||||
sb1.append("d"); // ignore (strict = false, witness incorrect)
|
||||
|
||||
sb2.append("e"); // add "c" as optional (strict = false, witness correct)
|
||||
@@ -128,7 +129,7 @@ class UStringEvaluatorWithSideEffectsTest : AbstractStringEvaluatorTest() {
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
"""'a'{''|'c'}""",
|
||||
"""'a'{''|'c'}'\m/'{''|'e'}""",
|
||||
configuration = {
|
||||
UStringEvaluator.Configuration(
|
||||
builderEvaluators = listOf(UStringBuilderEvaluator)
|
||||
@@ -391,10 +392,60 @@ class UStringEvaluatorWithSideEffectsTest : AbstractStringEvaluatorTest() {
|
||||
}
|
||||
)
|
||||
|
||||
fun `test StringBuilder reassignment to new value`() = doTest(
|
||||
"""
|
||||
class MyFile {
|
||||
String falseEvidenceInIf() {
|
||||
StringBuilder sb = new StringBuilder("a");
|
||||
sb.toString();
|
||||
|
||||
sb = new StringBuilder("b");
|
||||
sb.append("c");
|
||||
|
||||
return sb.toSt<caret>ring();
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
"'b''c'",
|
||||
retrieveElement = UElement?::getUCallExpression,
|
||||
configuration = {
|
||||
UStringEvaluator.Configuration(
|
||||
builderEvaluators = listOf(CloneAwareStringBuilderEvaluator())
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
fun `test strange one line change`() = doTest(
|
||||
"""
|
||||
class MyFile {
|
||||
String a() {
|
||||
StringBuilder sb = new StringBuilder("0");
|
||||
sb.append("aaa")
|
||||
.append("bbb")
|
||||
.clone()
|
||||
.append("ccc")
|
||||
.clone()
|
||||
.append("ddd");
|
||||
|
||||
sb.append("d");
|
||||
|
||||
return sb.toSt<caret>ring();
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
"'0''aaa''bbb''d'",
|
||||
retrieveElement = UElement?::getUCallExpression,
|
||||
configuration = {
|
||||
UStringEvaluator.Configuration(
|
||||
builderEvaluators = listOf(CloneAwareStringBuilderEvaluator())
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
private class CloneAwareStringBuilderEvaluator : UStringEvaluator.BuilderLikeExpressionEvaluator<PartiallyKnownString?> by UStringBuilderEvaluator {
|
||||
override val methodDescriptions: Map<ElementPattern<PsiMethod>, (UCallExpression, PartiallyKnownString?, UStringEvaluator, UStringEvaluator.Configuration) -> PartiallyKnownString?>
|
||||
override val methodDescriptions: Map<ElementPattern<PsiMethod>, (UCallExpression, PartiallyKnownString?, UStringEvaluator, UStringEvaluator.Configuration, Boolean) -> PartiallyKnownString?>
|
||||
get() = UStringBuilderEvaluator.methodDescriptions + mapOf(
|
||||
PsiJavaPatterns.psiMethod().withName("clone") to { _, partiallyKnownString, _, _ ->
|
||||
PsiJavaPatterns.psiMethod().withName("clone") to { _, partiallyKnownString, _, _, _ ->
|
||||
partiallyKnownString
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user