Skip to content

Commit

Permalink
Split class details into separate object. (#224)
Browse files Browse the repository at this point in the history
Right now, details like supertypes and properties are all included in
the `ClassTypeEmbedding`. This is unfortunate, because these are data
classes, and this breaks the equality definition on them. With this
commit, all the mutable data is split off into a separate object. The
class type embeddings still aren't quite interchangable, since some may
be missing this `details` object, but at least it's much easier to
detect now.
  • Loading branch information
jesyspa committed Jul 26, 2024
1 parent e56a52f commit 6265c08
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 182 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class ProgramConverter(val session: FirSession, override val config: PluginConfi
fields.values.distinctBy { it.name.mangled }.map { it.toViper() },
functions = SpecialFunctions.all,
methods = SpecialMethods.all + methods.values.mapNotNull { it.viperMethod }.distinctBy { it.name.mangled },
predicates = classes.values.flatMap { listOf(it.sharedPredicate, it.uniquePredicate) }
predicates = classes.values.flatMap { listOf(it.details.sharedPredicate, it.details.uniquePredicate) }
)

fun registerForVerification(declaration: FirSimpleFunction) {
Expand Down Expand Up @@ -113,38 +113,38 @@ class ProgramConverter(val session: FirSession, override val config: PluginConfi
UserFunctionEmbedding(callable)
}

/**
* Returns an embedding of the class type, with details set.
*/
private fun embedClass(symbol: FirRegularClassSymbol): ClassTypeEmbedding {
val className = symbol.classId.embedName()
return when (val existingEmbedding = classes[className]) {
null -> {
// The full class embedding is necessary to process the signatures of the properties of the class, since
// these take the class as a parameter. We thus do this in three phases:
// 1. Provide a class embedding in the `classes` map (necessary for embedType to not call this recursively).
// 2. Initialise the supertypes (including running this whole four-step process on each)
// 3. Initialise the fields
// 4. Process the properties of the class.
//
// With respect to the embedding, each phase is pure by itself, and only updates the class embedding at the end.
// This ensures the code never sees half-built supertype or field data. The phases can, however, modify the
// `ProgramConverter`.

// Phase 1
val newEmbedding = ClassTypeEmbedding(className, symbol.classKind == ClassKind.INTERFACE)
classes[className] = newEmbedding

// Phase 2
newEmbedding.initSuperTypes(symbol.resolvedSuperTypes.map(::embedType))

// Phase 3
val properties = symbol.propertySymbols
newEmbedding.initFields(properties.mapNotNull { processBackingField(it, symbol) }.toMap())

// Phase 4
properties.forEach { processProperty(it, newEmbedding) }
newEmbedding
}
else -> existingEmbedding
}
val embedding = classes.getOrPut(className) { ClassTypeEmbedding(className) }
if (embedding.hasDetails) return embedding

val newDetails = ClassEmbeddingDetails(embedding, symbol.classKind == ClassKind.INTERFACE)
embedding.initDetails(newDetails)

// The full class embedding is necessary to process the signatures of the properties of the class, since
// these take the class as a parameter. We thus do this in three phases:
// 1. Initialise the supertypes (including running this whole four-step process on each)
// 2. Initialise the fields
// 3. Process the properties of the class.
//
// With respect to the embedding, each phase is pure by itself, and only updates the class embedding at the end.
// This ensures the code never sees half-built supertype or field data. The phases can, however, modify the
// `ProgramConverter`.

// Phase 1
newDetails.initSuperTypes(symbol.resolvedSuperTypes.map(::embedType))

// Phase 2
val properties = symbol.propertySymbols
newDetails.initFields(properties.mapNotNull { processBackingField(it, symbol) }.toMap())

// Phase 3
properties.forEach { processProperty(it, newDetails) }

return embedding
}

override fun embedType(type: ConeKotlinType): TypeEmbedding = when {
Expand Down Expand Up @@ -203,7 +203,7 @@ class ProgramConverter(val session: FirSession, override val config: PluginConfi

return constructedClassSymbol.propertySymbols.mapNotNull { propertySymbol ->
propertySymbol.withConstructorParam { paramSymbol ->
constructedClass.findField(callableId.embedUnscopedPropertyName())?.let { paramSymbol to it }
constructedClass.details.findField(callableId.embedUnscopedPropertyName())?.let { paramSymbol to it }
}
}.toMap()
}
Expand Down Expand Up @@ -325,7 +325,7 @@ class ProgramConverter(val session: FirSession, override val config: PluginConfi
* Note that the property either has associated Viper field (and then it is used to access the value) or not (in this case methods are used).
* The field is only used for final properties with default getter and default setter (if any).
*/
private fun processProperty(symbol: FirPropertySymbol, embedding: ClassTypeEmbedding) {
private fun processProperty(symbol: FirPropertySymbol, embedding: ClassEmbeddingDetails) {
val unscopedName = symbol.callableId.embedUnscopedPropertyName()
val backingField = embedding.findField(unscopedName)
backingField?.let { fields.put(it.name, it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ class RuntimeTypeDomain(classes: List<ClassTypeEmbedding>) : BuiltinDomain(RUNTI
it.apply { injectionAxioms() }
}
classTypes.forEach { (typeEmbedding, typeFunction) ->
typeEmbedding.superTypes.forEach {
typeEmbedding.details.superTypes.forEach {
classTypes[it]?.let { supertypeFunction ->
axiom {
typeFunction() subtype supertypeFunction()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright 2010-2024 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.formver.embeddings

import org.jetbrains.kotlin.formver.names.ClassScope
import org.jetbrains.kotlin.formver.names.ScopedKotlinName
import org.jetbrains.kotlin.formver.names.SimpleKotlinName
import org.jetbrains.kotlin.formver.viper.MangledName
import org.jetbrains.kotlin.formver.viper.ast.PermExp
import org.jetbrains.kotlin.formver.viper.ast.Predicate
import org.jetbrains.kotlin.utils.addToStdlib.ifTrue

class ClassEmbeddingDetails(val type: ClassTypeEmbedding, val isInterface: Boolean) : TypeInvariantHolder {
private var _superTypes: List<TypeEmbedding>? = null
val superTypes: List<TypeEmbedding>
get() = _superTypes ?: error("Super types of ${type.className} have not been initialised yet.")

private val classSuperTypes: List<ClassTypeEmbedding>
get() = superTypes.filterIsInstance<ClassTypeEmbedding>()

fun initSuperTypes(newSuperTypes: List<TypeEmbedding>) {
check(_superTypes == null) { "Super types of ${type.className} are already initialised." }
_superTypes = newSuperTypes
}

private var _fields: Map<SimpleKotlinName, FieldEmbedding>? = null
private var _sharedPredicate: Predicate? = null
private var _uniquePredicate: Predicate? = null
val fields: Map<SimpleKotlinName, FieldEmbedding>
get() = _fields ?: error("Fields of ${type.className} have not been initialised yet.")
val sharedPredicate: Predicate
get() = _sharedPredicate ?: error("Predicate of ${type.className} has not been initialised yet.")
val uniquePredicate: Predicate
get() = _uniquePredicate ?: error("Unique Predicate of ${type.className} has not been initialised yet.")

fun initFields(newFields: Map<SimpleKotlinName, FieldEmbedding>) {
check(_fields == null) { "Fields of ${type.className} are already initialised." }
_fields = newFields
_sharedPredicate = ClassPredicateBuilder.build(this, type.name) {
forEachField {
if (isAlwaysReadable) {
addAccessPermissions(PermExp.WildcardPerm())
forType {
addAccessToSharedPredicate()
includeSubTypeInvariants()
}
}
}
forEachSuperType {
addAccessToSharedPredicate()
}
}
_uniquePredicate = ClassPredicateBuilder.build(this, uniquePredicateName) {
forEachField {
if (isAlwaysReadable) {
addAccessPermissions(PermExp.WildcardPerm())
} else {
addAccessPermissions(PermExp.FullPerm())
}
forType {
addAccessToSharedPredicate()
if (isUnique) {
addAccessToUniquePredicate()
}
includeSubTypeInvariants()
}
}
forEachSuperType {
addAccessToUniquePredicate()
}
}
}

private val uniquePredicateName = object : MangledName {
override val mangled: String = "Unique\$T_class_${type.className.mangled}"
}

/**
* Find an embedding of a backing field by this name amongst the ancestors of this type.
*
* While in Kotlin only classes can have backing fields, and so searching interface supertypes is not strictly necessary,
* due to the way we handle list size we need to search all types.
*/
fun findField(name: SimpleKotlinName): FieldEmbedding? = fields[name]

fun <R> flatMapFields(action: (SimpleKotlinName, FieldEmbedding) -> List<R>): List<R> =
classSuperTypes.flatMap { it.details.flatMapFields(action) } + fields.flatMap { (name, field) -> action(name, field) }

// We can't easily implement this by recursion on the supertype structure since some supertypes may be seen multiple times.
// TODO: figure out a nicer way to handle this.
override fun accessInvariants(): List<TypeInvariantEmbedding> =
flatMapUniqueFields { _, field -> field.accessInvariantsForParameter() }

// Note: this function will replace accessInvariants when nested unfold will be implemented
override fun sharedPredicateAccessInvariant() =
PredicateAccessTypeInvariantEmbedding(type.name, PermExp.WildcardPerm())

override fun uniquePredicateAccessInvariant() =
PredicateAccessTypeInvariantEmbedding(uniquePredicateName, PermExp.FullPerm())

// Returns the sequence of classes in a hierarchy that need to be unfolded in order to access the given field
fun hierarchyUnfoldPath(fieldName: MangledName): Sequence<ClassTypeEmbedding> = sequence {
if (fieldName is ScopedKotlinName && fieldName.scope is ClassScope) {
if (fieldName.scope.className == type.className.name) {
yield(this@ClassEmbeddingDetails.type)
} else {
val sup = superTypes.firstOrNull { it is ClassTypeEmbedding && !it.details.isInterface }
if (sup is ClassTypeEmbedding) {
yield(this@ClassEmbeddingDetails.type)
yieldAll(sup.details.hierarchyUnfoldPath(fieldName))
} else {
throw IllegalArgumentException("Reached top of the hierarchy without finding the field")
}
}
} else {
throw IllegalArgumentException("Cannot find hierarchy unfold path of a field with no class scope")
}
}

fun <R> flatMapUniqueFields(action: (SimpleKotlinName, FieldEmbedding) -> List<R>): List<R> {
val seenFields = mutableSetOf<SimpleKotlinName>()
return flatMapFields { name, field ->
seenFields.add(name).ifTrue {
action(name, field)
} ?: listOf()
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ import org.jetbrains.kotlin.formver.viper.ast.PermExp
import org.jetbrains.kotlin.formver.viper.ast.Predicate
import org.jetbrains.kotlin.utils.addIfNotNull

internal class ClassPredicateBuilder private constructor(private val type: ClassTypeEmbedding) {
private val subject = PlaceholderVariableEmbedding(ClassPredicateSubjectName, type)
internal class ClassPredicateBuilder private constructor(private val details: ClassEmbeddingDetails) {
private val subject = PlaceholderVariableEmbedding(ClassPredicateSubjectName, details.type)
private val body = mutableListOf<ExpEmbedding>()

companion object {
fun build(classType: ClassTypeEmbedding, predicateName: MangledName, action: ClassPredicateBuilder.() -> Unit): Predicate {
fun build(
classType: ClassEmbeddingDetails, predicateName: MangledName,
action: ClassPredicateBuilder.() -> Unit,
): Predicate {
val builder = ClassPredicateBuilder(classType)
builder.action()
return Predicate(
Expand All @@ -29,7 +32,7 @@ internal class ClassPredicateBuilder private constructor(private val type: Class
}

fun forEachField(action: FieldAssertionsBuilder.() -> Unit) =
type.fields.values
details.fields.values
.filterIsInstance<UserFieldEmbedding>()
.forEach { field ->
val builder = FieldAssertionsBuilder(subject, field)
Expand All @@ -38,7 +41,7 @@ internal class ClassPredicateBuilder private constructor(private val type: Class
}

fun forEachSuperType(action: TypeInvariantsBuilder.() -> Unit) =
type.superTypes.forEach { type ->
details.superTypes.forEach { type ->
val builder = TypeInvariantsBuilder(type)
builder.action()
body.addAll(builder.toInvariantsList().fillHoles(subject))
Expand Down
Loading

0 comments on commit 6265c08

Please sign in to comment.