Skip to content

Commit

Permalink
Split class details into separate object.
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 15, 2024
1 parent 52bd7af commit 6e2aba7
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 180 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ProgramConverter(val session: FirSession, override val config: PluginConfi
ProgramConversionContext {
private val methods: MutableMap<MangledName, FunctionEmbedding> = SpecialKotlinFunctions.byName.toMutableMap()
private val classes: MutableMap<MangledName, ClassTypeEmbedding> = mutableMapOf()
private val classDetails: MutableMap<ClassTypeEmbedding, ClassEmbeddingDetails> = mutableMapOf()
private val properties: MutableMap<MangledName, PropertyEmbedding> = mutableMapOf()
private val fields: MutableMap<MangledName, FieldEmbedding> = mutableMapOf()

Expand Down Expand Up @@ -67,7 +68,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 = classDetails.values.flatMap { listOf(it.sharedPredicate, it.uniquePredicate) }
)

fun registerForVerification(declaration: FirSimpleFunction) {
Expand Down Expand Up @@ -113,38 +114,46 @@ 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, symbol.classKind == ClassKind.INTERFACE)
}
if (embedding.hasDetails) return embedding

classDetails[embedding]?.let {
embedding.initDetails(it)
return embedding
}

val newDetails = ClassEmbeddingDetails(embedding)
classDetails[embedding] = newDetails
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 +212,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 +334,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) : 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.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 6e2aba7

Please sign in to comment.