Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic USVM TS type system with type coercion #215

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fe1d03b
TSTypeSystem + TSTopTypeStream implementation
zishkaz Aug 12, 2024
471d31d
Implement basic type coercion
zishkaz Aug 22, 2024
bcb0c1e
Type coercion tests working
zishkaz Aug 27, 2024
344de3f
Add type coercion test
zishkaz Aug 30, 2024
08178af
Dev sync
zishkaz Sep 2, 2024
c3082ee
Dev sync 2
zishkaz Sep 4, 2024
5015c31
TSTypeSystem + TSTopTypeStream implementation
zishkaz Aug 12, 2024
8808008
Implement basic type coercion
zishkaz Aug 22, 2024
7984e94
Type coercion tests working
zishkaz Aug 27, 2024
aa5dccc
Add type coercion test
zishkaz Aug 30, 2024
83f848f
Dev sync
zishkaz Sep 2, 2024
f297099
Dev sync 2
zishkaz Sep 4, 2024
fc54035
Merge remote-tracking branch 'origin/sergeyl/ts_typecoercion' into se…
zishkaz Sep 4, 2024
b4ea3ae
Correct transitiveCoercionNoTypes test (needs cleanup)
zishkaz Sep 11, 2024
6cc6036
Hotfix
zishkaz Sep 12, 2024
064703f
Minor refactoring
zishkaz Sep 12, 2024
7470894
Support transitiveCoercionNoTypes test
zishkaz Sep 18, 2024
bb320ac
Refactoring + code documentation
zishkaz Sep 23, 2024
f87cd38
Merge branch 'refs/heads/main' into sergeyl/ts_typecoercion
zishkaz Sep 23, 2024
6abe719
Post-merge fix
zishkaz Sep 23, 2024
cbd2070
boolToFpSort bug fix
zishkaz Sep 27, 2024
2db7013
Minor refactoring
zishkaz Sep 30, 2024
54ebc12
JAcoDB version update
zishkaz Sep 30, 2024
942af48
Minor fix
zishkaz Oct 2, 2024
bc8e331
Run CI with --scan gradle flag
zishkaz Oct 2, 2024
b49f420
Update arkanalyzer version in CI
zishkaz Oct 2, 2024
ce54887
Revert --scan changes
zishkaz Oct 2, 2024
6e81b3c
Set arkanalyzer version the same as jacodb/neo
zishkaz Oct 7, 2024
1dc45cf
Extract common API + comment
zishkaz Oct 7, 2024
8bf5943
Fix comment
zishkaz Oct 7, 2024
7e7cc1c
Refactoring + code documentation
zishkaz Oct 8, 2024
a75a753
TSTypeStorage commentary
zishkaz Oct 8, 2024
9b7b106
Detekt fix + code documentation
zishkaz Oct 8, 2024
e1d755c
Hotfix
zishkaz Oct 8, 2024
b55b3e4
Disable tests
zishkaz Oct 8, 2024
e85df2b
Remove TSTypeStorage
zishkaz Oct 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/build-and-run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,24 @@ jobs:
DEST_DIR="arkanalyzer"
MAX_RETRIES=10
RETRY_DELAY=3 # Delay between retries in seconds
BRANCH="neo/2024-08-07"
zishkaz marked this conversation as resolved.
Show resolved Hide resolved

BRANCH="neo/2024-08-16"
for ((i=1; i<=MAX_RETRIES; i++)); do
git clone --depth=1 --branch $BRANCH $REPO_URL $DEST_DIR && break
echo "Clone failed, retrying in $RETRY_DELAY seconds..."
sleep "$RETRY_DELAY"
done

if [[ $i -gt $MAX_RETRIES ]]; then
echo "Failed to clone the repository after $MAX_RETRIES attempts."
exit 1
else
echo "Repository cloned successfully."
fi

echo "ARKANALYZER_DIR=$(realpath $DEST_DIR)" >> $GITHUB_ENV
cd $DEST_DIR

npm install
npm run build

Expand All @@ -70,7 +70,7 @@ jobs:

- name: Build and run tests
run: |
./gradlew build --no-daemon -PcpythonActivated=true
./gradlew build --no-daemon -PcpythonActivated=true --scan

- name: Run Detekt
run: |
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import org.gradle.plugin.use.PluginDependenciesSpec
object Versions {
const val detekt = "1.18.1"
const val ini4j = "0.5.4"
const val jacodb = "ae2716b3f8"
const val jacodb = "3377c0cb88"
const val juliet = "1.3.2"
const val junit = "5.9.3"
const val kotlin = "1.9.20"
Expand Down
11 changes: 11 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,14 @@ pluginManagement {
}
}
}

plugins {
`gradle-enterprise`
}

gradleEnterprise {
buildScan {
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
}
}
66 changes: 35 additions & 31 deletions usvm-core/src/main/kotlin/org/usvm/StateForker.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.usvm

import org.usvm.StateForker.Companion.splitModelsByCondition
import org.usvm.collections.immutable.internal.MutabilityOwnership
import org.usvm.model.UModelBase
import org.usvm.solver.USatResult
Expand All @@ -11,7 +12,39 @@
private const val ForkedState = true
private const val OriginalState = false

sealed interface StateForker {
interface StateForker {
zishkaz marked this conversation as resolved.
Show resolved Hide resolved

companion object {
/**
* Splits the passed [models] with this [condition] to the three categories:
* - models that satisfy this [condition];
* - models that are in contradiction with this [condition];
* - models that can not evaluate this [condition].
*/
fun <Type> splitModelsByCondition(
models: List<UModelBase<Type>>,
condition: UBoolExpr,
): SplittedModels<Type> {
val trueModels = mutableListOf<UModelBase<Type>>()
val falseModels = mutableListOf<UModelBase<Type>>()
val unknownModels = mutableListOf<UModelBase<Type>>()

models.forEach { model ->
val holdsInModel = model.eval(condition)

when {
holdsInModel.isTrue -> trueModels += model
holdsInModel.isFalse -> falseModels += model
// Sometimes we cannot evaluate the condition – for example, a result for a division by symbolic expression

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.
// that is evaluated to 0 is unknown
else -> unknownModels += model
}
}

return SplittedModels(trueModels, falseModels, unknownModels)
}
}

/**
* Implements symbolic branching.
* Checks if [condition] and ![condition] are satisfiable within [state].
Expand Down Expand Up @@ -254,41 +287,12 @@
}
}

/**
* Splits the passed [models] with this [condition] to the three categories:
* - models that satisfy this [condition];
* - models that are in contradiction with this [condition];
* - models that can not evaluate this [condition].
*/
private fun <Type> splitModelsByCondition(
models: List<UModelBase<Type>>,
condition: UBoolExpr,
): SplittedModels<Type> {
val trueModels = mutableListOf<UModelBase<Type>>()
val falseModels = mutableListOf<UModelBase<Type>>()
val unknownModels = mutableListOf<UModelBase<Type>>()

models.forEach { model ->
val holdsInModel = model.eval(condition)

when {
holdsInModel.isTrue -> trueModels += model
holdsInModel.isFalse -> falseModels += model
// Sometimes we cannot evaluate the condition – for example, a result for a division by symbolic expression
// that is evaluated to 0 is unknown
else -> unknownModels += model
}
}

return SplittedModels(trueModels, falseModels, unknownModels)
}

data class ForkResult<T>(
val positiveState: T?,
val negativeState: T?,
)

private data class SplittedModels<Type>(
data class SplittedModels<Type>(
zishkaz marked this conversation as resolved.
Show resolved Hide resolved
val trueModels: List<UModelBase<Type>>,
val falseModels: List<UModelBase<Type>>,
val unknownModels: List<UModelBase<Type>>,
Expand Down
4 changes: 2 additions & 2 deletions usvm-ts/src/main/kotlin/org/usvm/TSApplicationGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package org.usvm

import org.jacodb.ets.base.EtsStmt
import org.jacodb.ets.graph.EtsApplicationGraphImpl
import org.jacodb.ets.model.EtsFile
import org.jacodb.ets.model.EtsMethod
import org.jacodb.ets.model.EtsScene
import org.usvm.statistics.ApplicationGraph

class TSApplicationGraph(project: EtsFile) : ApplicationGraph<EtsMethod, EtsStmt> {
class TSApplicationGraph(project: EtsScene) : ApplicationGraph<EtsMethod, EtsStmt> {
private val applicationGraph = EtsApplicationGraphImpl(project)

override fun predecessors(node: EtsStmt): Sequence<EtsStmt> =
Expand Down
75 changes: 68 additions & 7 deletions usvm-ts/src/main/kotlin/org/usvm/TSBinaryOperator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,93 @@
val onBool: TSContext.(UExpr<UBoolSort>, UExpr<UBoolSort>) -> UExpr<out USort> = shouldNotBeCalled,
val onBv: TSContext.(UExpr<UBvSort>, UExpr<UBvSort>) -> UExpr<out USort> = shouldNotBeCalled,
val onFp: TSContext.(UExpr<UFpSort>, UExpr<UFpSort>) -> UExpr<out USort> = shouldNotBeCalled,
val onRef: TSContext.(UExpr<UAddressSort>, UExpr<UAddressSort>) -> UExpr<out USort> = shouldNotBeCalled,
zishkaz marked this conversation as resolved.
Show resolved Hide resolved
val desiredSort: TSContext.(USort, USort) -> USort = { _, _ -> error("Should not be called") },
val banSorts: TSContext.(UExpr<out USort>, UExpr<out USort>) -> Set<USort> = {_, _ -> emptySet() },
Fixed Show fixed Hide fixed
) {

object Eq : TSBinaryOperator(
onBool = UContext<TSSizeSort>::mkEq,
onBv = UContext<TSSizeSort>::mkEq,
onFp = UContext<TSSizeSort>::mkFpEqualExpr,
onRef = UContext<TSSizeSort>::mkEq,
zishkaz marked this conversation as resolved.
Show resolved Hide resolved
desiredSort = { lhs, _ -> lhs }
)

// Neq must not be applied to a pair of expressions generated while initial Neq application.
// For example,
// "a (untyped arg) != 1.0 (fp64 number)"
// can't yield
// "a (bool reg reading) != true", since "1.0.toBool() = true" is a new value for 1.0.
//
// So, that's the reason why banSorts in Neq throws out all primitive types except one of the expressions' one.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't understand what it means

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enriched explanation

object Neq : TSBinaryOperator(
onBool = { lhs, rhs -> lhs.neq(rhs) },
onBv = { lhs, rhs -> lhs.neq(rhs) },
onFp = { lhs, rhs -> mkFpEqualExpr(lhs, rhs).not() },
onRef = { lhs, rhs -> lhs.neq(rhs) },
zishkaz marked this conversation as resolved.
Show resolved Hide resolved
desiredSort = { lhs, _ -> lhs },
banSorts = { lhs, rhs ->
when {
lhs is TSWrappedValue ->
// rhs.sort == addressSort is a mock not to cause undefined
// behaviour with support of new language features.
if (rhs is TSWrappedValue || rhs.sort == addressSort) emptySet() else

Check warning

Code scanning / detekt

Detects multiline if-else statements without braces Warning

Missing { ... }
zishkaz marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why addresses are not supported? I didn't get it. If some feature is not implemented it should be marked as "TODO", not to work in incorrect way

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enriched explanation

TSTypeSystem.primitiveTypes

Check warning

Code scanning / detekt

Detects multiline if-else statements without braces Warning

Missing { ... }
.map(::typeToSort).toSet()

Check warning

Code scanning / detekt

Reports mis-indented code Warning

Unexpected indentation (24) (should be 28)
.minus(rhs.sort)

Check warning

Code scanning / detekt

Reports mis-indented code Warning

Unexpected indentation (24) (should be 28)
rhs is TSWrappedValue ->
// lhs.sort == addressSort explained as above.
if (lhs.sort == addressSort) emptySet() else

Check warning

Code scanning / detekt

Detects multiline if-else statements without braces Warning

Missing { ... }
TSTypeSystem.primitiveTypes

Check warning

Code scanning / detekt

Detects multiline if-else statements without braces Warning

Missing { ... }
.map(::typeToSort).toSet()

Check warning

Code scanning / detekt

Reports mis-indented code Warning

Unexpected indentation (24) (should be 28)
.minus(lhs.sort)

Check warning

Code scanning / detekt

Reports mis-indented code Warning

Unexpected indentation (24) (should be 28)
else -> emptySet()
}
}
)

object Add : TSBinaryOperator(
onFp = { lhs, rhs -> mkFpAddExpr(fpRoundingModeSortDefaultValue(), lhs, rhs) },
onBv = UContext<TSSizeSort>::mkBvAddExpr,
desiredSort = { _, _ -> fp64Sort },
zishkaz marked this conversation as resolved.
Show resolved Hide resolved
)

internal operator fun invoke(lhs: UExpr<out USort>, rhs: UExpr<out USort>): UExpr<out USort> {
object And : TSBinaryOperator(
onBool = UContext<TSSizeSort>::mkAnd,
desiredSort = { _, _ -> boolSort },
)

internal operator fun invoke(lhs: UExpr<out USort>, rhs: UExpr<out USort>, scope: TSStepScope): UExpr<out USort> {
val bannedSorts = lhs.tctx.banSorts(lhs, rhs)

fun apply(lhs: UExpr<out USort>, rhs: UExpr<out USort>): UExpr<out USort>? {
val ctx = lhs.tctx
val lhsSort = lhs.sort
val rhsSort = rhs.sort
assert(lhsSort == rhsSort)
zishkaz marked this conversation as resolved.
Show resolved Hide resolved

if (lhsSort in bannedSorts) return null
if (ctx.desiredSort(lhsSort, rhsSort) != lhsSort) return null
zishkaz marked this conversation as resolved.
Show resolved Hide resolved

return when (lhs.sort) {
is UBoolSort -> ctx.onBool(lhs.cast(), rhs.cast())
is UBvSort -> ctx.onBv(lhs.cast(), rhs.cast())
is UFpSort -> ctx.onFp(lhs.cast(), rhs.cast())
is UAddressSort -> ctx.onRef(lhs.cast(), rhs.cast())
else -> error("Unexpected sorts: $lhsSort, $rhsSort")
}
}

val lhsSort = lhs.sort
val rhsSort = rhs.sort

if (lhsSort != rhsSort) TODO("Implement type coercion")
val ctx = lhs.tctx
val sort = ctx.desiredSort(lhsSort, rhsSort)

return when {
zishkaz marked this conversation as resolved.
Show resolved Hide resolved
lhsSort is UBoolSort -> lhs.tctx.onBool(lhs.cast(), rhs.cast())
lhsSort is UBvSort -> lhs.tctx.onBv(lhs.cast(), rhs.cast())
lhsSort is UFpSort -> lhs.tctx.onFp(lhs.cast(), rhs.cast())

else -> error("Unexpected sorts: $lhsSort, $rhsSort")
lhs is TSWrappedValue -> lhs.coerceWithSort(rhs, ::apply, sort)
else -> TSWrappedValue(ctx, lhs, scope).coerceWithSort(rhs, ::apply, sort)
}
}

Expand Down
3 changes: 3 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/TSComponents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.ksmt.symfpu.solver.KSymFpuSolver
import org.jacodb.ets.base.EtsType
import org.usvm.solver.USolverBase
import org.usvm.solver.UTypeSolver
import org.usvm.state.TSStateForker
import org.usvm.types.UTypeSystem

class TSComponents(
Expand Down Expand Up @@ -40,6 +41,8 @@ class TSComponents(
return USolverBase(ctx, smtSolver, typeSolver, translator, decoder, options.solverTimeout)
}

override fun mkStatesForkProvider(): StateForker = TSStateForker

fun close() {
closeableResources.forEach(AutoCloseable::close)
}
Expand Down
2 changes: 2 additions & 0 deletions usvm-ts/src/main/kotlin/org/usvm/TSContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.jacodb.ets.base.EtsBooleanType
import org.jacodb.ets.base.EtsNumberType
import org.jacodb.ets.base.EtsRefType
import org.jacodb.ets.base.EtsType
import org.jacodb.ets.base.EtsUnknownType

typealias TSSizeSort = UBv32Sort

Expand All @@ -17,6 +18,7 @@ class TSContext(components: TSComponents) : UContext<TSSizeSort>(components) {
is EtsBooleanType -> boolSort
is EtsNumberType -> fp64Sort
is EtsRefType -> addressSort
is EtsUnknownType -> addressSort
else -> TODO("Support all JacoDB types")
}

Expand Down
Loading
Loading