[Swift Export] Create infrastructure for SIR validation

This commit is contained in:
Sergey Bogolepov
2023-12-07 20:22:41 +02:00
committed by Space Team
parent fc1d7df0bc
commit 9fde7e0d35
5 changed files with 268 additions and 0 deletions
+2
View File
@@ -27,6 +27,8 @@ sourceSets {
"test" { projectDefault() }
}
testsJar()
projectTest(jUnitMode = JUnitMode.JUnit5) {
workingDir = rootDir
useJUnitPlatform { }
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.sir.passes.utility
import org.jetbrains.kotlin.sir.SirDeclaration
import org.jetbrains.kotlin.sir.SirDeclarationParent
import org.jetbrains.kotlin.sir.SirElement
import org.jetbrains.kotlin.sir.SirModule
import org.jetbrains.kotlin.sir.visitors.SirVisitor
import java.util.*
// Copy of DeclarationParentVisitor from Kotlin Backend IR.
internal abstract class DeclarationParentVisitor : SirVisitor<Unit, Nothing?>() {
protected val declarationParentStack = ArrayDeque<SirDeclarationParent>()
protected abstract fun handleParent(declaration: SirDeclaration, parent: SirDeclarationParent)
override fun visitElement(element: SirElement, data: Nothing?) {
element.acceptChildren(this, data)
}
override fun visitModule(module: SirModule, data: Nothing?) {
declarationParentStack.push(module)
super.visitModule(module, data)
declarationParentStack.pop()
}
override fun visitDeclaration(declaration: SirDeclaration, data: Nothing?) {
handleParent(declaration, declarationParentStack.peekFirst())
if (declaration is SirDeclarationParent) {
declarationParentStack.push(declaration)
}
super.visitDeclaration(declaration, data)
if (declaration is SirDeclarationParent) {
declarationParentStack.pop()
}
}
}
/**
* Fixes parents of declarations.
*
* Copy of PatchDeclarationParentVisitor from Backend IR.
*/
internal class PatchDeclarationParentVisitor() : DeclarationParentVisitor() {
constructor(containingDeclaration: SirDeclarationParent) : this() {
declarationParentStack.push(containingDeclaration)
}
override fun handleParent(declaration: SirDeclaration, parent: SirDeclarationParent) {
declaration.parent = parent
}
}
@@ -0,0 +1,92 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.sir.passes.utility
import org.jetbrains.kotlin.sir.*
import org.jetbrains.kotlin.sir.visitors.SirVisitor
import org.jetbrains.sir.passes.SirPass
import org.jetbrains.sir.passes.utility.ValidationError.WrongForeignDeclarationOrigin
import org.jetbrains.sir.passes.utility.ValidationError.WrongParent
import kotlin.collections.plusAssign
/**
* Collection of errors that might arise during SIR validation.
*/
public sealed interface ValidationError {
public class WrongParent(public val declaration: SirDeclaration, public val expectedParent: SirDeclarationParent) : ValidationError
public class WrongForeignDeclarationOrigin(public val declaration: SirForeignDeclaration) : ValidationError
}
public class SirValidatorConfig(
public val checkParents: Boolean = true,
public val checkForeignDeclarations: Boolean = true
)
/**
* Validate various invariants about Swift IR.
*/
public fun validate(sirElement: SirElement, config: SirValidatorConfig): List<ValidationError> {
return SirValidator(config).run(sirElement, null)
}
/**
* Run full set of SIR validators, and fail if any error encountered.
*/
public fun SirElement.assertValid() {
val errors = validate(this, SirValidatorConfig())
if (errors.isNotEmpty()) {
val messages = errors.map {
when (it) {
// TODO: better rendering of SIR elements.
is WrongForeignDeclarationOrigin -> "Wrong foreign declaration origin: ${it.declaration}"
is WrongParent -> "Wrong declaration parent of ${it.declaration}. Expected: ${it.expectedParent}. Got: ${it.declaration.parent}"
}
}
error("SIR is invalid:\n" + messages.joinToString(separator = "\n"))
}
}
private fun interface ErrorHandler {
fun handle(error: ValidationError)
}
internal class SirValidator(private val config: SirValidatorConfig) : SirPass<SirElement, Nothing?, List<ValidationError>> {
override fun run(element: SirElement, data: Nothing?): List<ValidationError> {
val errors = mutableListOf<ValidationError>()
val handler = ErrorHandler {
errors += it
}
if (config.checkParents) {
element.accept(ParentValidator(handler), data)
}
if (config.checkForeignDeclarations) {
element.accept(ForeignDeclarationValidator(handler), data)
}
return errors
}
}
private class ParentValidator(private val errorHandler: ErrorHandler) : DeclarationParentVisitor() {
override fun handleParent(declaration: SirDeclaration, parent: SirDeclarationParent) {
if (declaration.parent != parent) {
errorHandler.handle(WrongParent(declaration, parent))
}
}
}
private class ForeignDeclarationValidator(private val errorHandler: ErrorHandler) : SirVisitor<Unit, Nothing?>() {
override fun visitElement(element: SirElement, data: Nothing?) {
element.acceptChildren(this, data)
}
override fun visitForeignFunction(foreignFunction: SirForeignFunction, data: Nothing?) {
super.visitForeignFunction(foreignFunction, data)
if (foreignFunction.origin !is SirOrigin.Foreign) {
errorHandler.handle(WrongForeignDeclarationOrigin(foreignFunction))
}
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.sir.passes
import org.jetbrains.kotlin.sir.SirNominalType
import org.jetbrains.kotlin.sir.builder.buildEnum
import org.jetbrains.kotlin.sir.builder.buildFunction
import org.jetbrains.kotlin.sir.builder.buildModule
import org.jetbrains.kotlin.sir.util.SirSwiftModule
import org.jetbrains.sir.passes.utility.PatchDeclarationParentVisitor
import org.jetbrains.sir.passes.utility.SirValidatorConfig
import org.jetbrains.sir.passes.utility.ValidationError
import org.jetbrains.sir.passes.utility.validate
import kotlin.test.Test
import kotlin.test.assertTrue
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
class SirParentPatcherTests {
@Test
fun `parent patcher should patch parents`() {
val wrongEnum = buildEnum { name = "wrongEnum" }
val function = buildFunction {
name = "foo"
returnType = SirNominalType(SirSwiftModule.bool)
}
function.parent = wrongEnum
val wrongModule = buildModule { name = "wrongModule" }
val enum = buildEnum {
name = "e"
declarations += function
}
enum.parent = wrongModule
val module = buildModule {
name = "MyModule"
declarations += enum
}
val validationErrorsBefore = validate(module, SirValidatorConfig())
assertTrue(validationErrorsBefore.filterIsInstance<ValidationError.WrongParent>().count() == 2)
module.accept(PatchDeclarationParentVisitor(), null)
val validationErrorsAfter = validate(module, SirValidatorConfig())
assertTrue(validationErrorsAfter.filterIsInstance<ValidationError.WrongParent>().isEmpty())
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.sir.passes
import org.jetbrains.kotlin.sir.SirOrigin
import org.jetbrains.kotlin.sir.builder.buildForeignFunction
import org.jetbrains.kotlin.sir.builder.buildModule
import org.jetbrains.kotlin.sir.mock.MockFunction
import org.jetbrains.kotlin.sir.mock.MockKotlinType
import org.jetbrains.sir.passes.utility.PatchDeclarationParentVisitor
import org.jetbrains.sir.passes.utility.SirValidatorConfig
import org.jetbrains.sir.passes.utility.ValidationError
import org.jetbrains.sir.passes.utility.validate
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class SirValidatorTests {
@Test
fun `validator should check foreign origins`() {
val module = buildModule {
name = "MyModule"
declarations += buildForeignFunction {
}
}
val config = SirValidatorConfig(checkParents = false)
val error = validate(module, config).first()
assertTrue(error is ValidationError.WrongForeignDeclarationOrigin)
assertEquals(module.declarations.first(), error.declaration)
}
@Test
fun `validatior should check declaration parents`() {
val wrongModule = buildModule {
name = "WrongModule"
}
val foreignFunction = buildForeignFunction {
val kotlinEntity = MockFunction(
fqName = listOf("foo"),
parameters = emptyList(),
returnType = MockKotlinType("kotlin/Byte")
)
origin = kotlinEntity
}
foreignFunction.parent = wrongModule
val module = buildModule {
name = "MyModule"
declarations += foreignFunction
}
val config = SirValidatorConfig(
checkForeignDeclarations = false
)
val error = validate(module, config).first()
assertTrue(error is ValidationError.WrongParent)
assertEquals(foreignFunction, error.declaration)
assertEquals(module, error.expectedParent)
}
}