mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
fix compiler crash on 'not' combined with or/and in one expression
cherry-picked from v12 maintenance b71787a929
This commit is contained in:
1 parent
fba4bb5272
commit
99ff5768c5
2 files changed
+66
-2
No files matched your search
@@ -1,5 +1,6 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||
@@ -974,4 +975,34 @@ main {
|
||||
errors.errors.size shouldBe 0
|
||||
errors.infos.none { "when statement" in it } shouldBe true
|
||||
}
|
||||
|
||||
test("boolean expressions mixing not with or/and compile") {
|
||||
// the 'not' simplification and the De Morgan rewrite of the same subtree used to both queue an
|
||||
// ast modification, which corrupted the parent links of the nodes they shared (compiler crash)
|
||||
val conditions = listOf(
|
||||
"a == b or not (a >= b)",
|
||||
"not (a >= b) or not (c >= b)",
|
||||
"not (a >= b) or c == b",
|
||||
"not (a == 0) and b == 0",
|
||||
"a == b and not (a >= b)",
|
||||
"(a == b) == 0 or not (c >= d)"
|
||||
)
|
||||
conditions.forEach { condition ->
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
uword a,b,c,d
|
||||
if $condition {
|
||||
a++
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target(), true, src, outputDir, errors, false) shouldNotBe null
|
||||
withClue(condition) {
|
||||
errors.errors.size shouldBe 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -3,7 +3,9 @@ package prog8.ast.walk
|
||||
import prog8.ast.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.statements.Enumeration
|
||||
import prog8.code.core.CommutativeOperators
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
@@ -26,6 +28,15 @@ open class AstModification {
|
||||
/** Nodes that will be affected when [perform] is called. */
|
||||
open val affectedNodes: List<Node> = emptyList()
|
||||
|
||||
/**
|
||||
* The node in which a child is modified by [perform], if there is one.
|
||||
* Modifications that target a node which is no longer part of the tree are skipped by
|
||||
* [AstWalker.applyModifications]: they would only change a subtree that has been detached
|
||||
* by another modification, and re-linking the parents of nodes that are still in use
|
||||
* elsewhere corrupts the tree.
|
||||
*/
|
||||
open val targetParent: Node? = null
|
||||
|
||||
/** Apply the modification to the AST. */
|
||||
open fun perform() {}
|
||||
}
|
||||
@@ -60,6 +71,7 @@ open class AstSetExpression(
|
||||
private val parent: Node
|
||||
) : AstModification() {
|
||||
override val affectedNodes = listOf(newExpr)
|
||||
override val targetParent: Node get() = parent
|
||||
|
||||
override fun perform() {
|
||||
setter(newExpr)
|
||||
@@ -113,6 +125,7 @@ open class AstReplaceNode(
|
||||
private val parent: Node
|
||||
) : AstModification() {
|
||||
override val affectedNodes = listOf(node, replacement)
|
||||
override val targetParent: Node get() = parent
|
||||
|
||||
override fun perform() {
|
||||
parent.replaceChildNode(node, replacement)
|
||||
@@ -273,6 +286,12 @@ abstract class AstWalker {
|
||||
protected val modifications = mutableListOf<Triple<AstModification, Node, Node>>()
|
||||
protected val affectedModules = mutableSetOf<Module>()
|
||||
|
||||
/**
|
||||
* The nodes that have been replaced or removed by a modification that was applied already,
|
||||
* and thus are no longer part of the tree.
|
||||
*/
|
||||
private val detachedNodes = Collections.newSetFromMap(IdentityHashMap<Node, Boolean>())
|
||||
|
||||
private fun track(mods: Iterable<AstModification>, node: Node, parent: Node) {
|
||||
if (mods.any()) {
|
||||
try {
|
||||
@@ -315,10 +334,24 @@ abstract class AstWalker {
|
||||
}
|
||||
}
|
||||
|
||||
detachedNodes.clear()
|
||||
var amount = 0
|
||||
modifications.forEach {
|
||||
it.first.perform()
|
||||
val mod = it.first
|
||||
if (mod.targetParent in detachedNodes) {
|
||||
// another modification of this batch already detached the node that this modification
|
||||
// would change, so applying it has no effect on the tree; skip it to avoid corrupting
|
||||
// the parent links of nodes that are still in use by the replacement.
|
||||
return@forEach
|
||||
}
|
||||
mod.perform()
|
||||
when (mod) {
|
||||
is AstReplaceNode -> detachedNodes += mod.node
|
||||
is AstRemove -> detachedNodes += mod.node
|
||||
else -> { }
|
||||
}
|
||||
amount++
|
||||
}
|
||||
val amount = modifications.size
|
||||
modifications.clear()
|
||||
return amount
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user