-
Notifications
You must be signed in to change notification settings - Fork 25
[TS] Extend reachability analysis test suite #334
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
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
36c11f1
Add more test samples for reachability analysis
Lipen 0fb2fce
Approximate Math.floor
Lipen c881da7
Fix test with tree structure traversal
Lipen cfc87cd
Refine one test structure
Lipen b3a41e1
Cleanup tests
Lipen a342d8d
Fix return stmt indices in linear cfg
Lipen 285e3bc
Disable some tests
Lipen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
296 changes: 296 additions & 0 deletions
296
usvm-ts/src/test/kotlin/org/usvm/reachability/AsyncReachabilityTest.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
package org.usvm.reachability | ||
|
||
import org.jacodb.ets.model.EtsIfStmt | ||
import org.jacodb.ets.model.EtsReturnStmt | ||
import org.jacodb.ets.model.EtsScene | ||
import org.jacodb.ets.utils.loadEtsFileAutoConvert | ||
import org.junit.jupiter.api.Disabled | ||
import org.usvm.PathSelectionStrategy | ||
import org.usvm.SolverType | ||
import org.usvm.UMachineOptions | ||
import org.usvm.api.targets.ReachabilityObserver | ||
import org.usvm.api.targets.TsReachabilityTarget | ||
import org.usvm.api.targets.TsTarget | ||
import org.usvm.machine.TsMachine | ||
import org.usvm.machine.TsOptions | ||
import org.usvm.util.getResourcePath | ||
import kotlin.test.Test | ||
import kotlin.test.assertTrue | ||
import kotlin.time.Duration | ||
|
||
/** | ||
* Tests for asynchronous programming reachability scenarios. | ||
* Verifies reachability analysis through async patterns and error handling. | ||
*/ | ||
class AsyncReachabilityTest { | ||
|
||
private val scene = run { | ||
val path = "/reachability/AsyncReachability.ts" | ||
val res = getResourcePath(path) | ||
val file = loadEtsFileAutoConvert(res) | ||
EtsScene(listOf(file)) | ||
} | ||
|
||
private val options = UMachineOptions( | ||
pathSelectionStrategies = listOf(PathSelectionStrategy.TARGETED), | ||
exceptionsPropagation = true, | ||
stopOnTargetsReached = true, | ||
timeout = Duration.INFINITE, | ||
stepsFromLastCovered = 3500L, | ||
solverType = SolverType.YICES, | ||
solverTimeout = Duration.INFINITE, | ||
typeOperationsTimeout = Duration.INFINITE, | ||
) | ||
|
||
private val tsOptions = TsOptions() | ||
|
||
@Test | ||
fun testAsyncAwaitReachable() { | ||
// Test reachability through async function logic: | ||
// const result = delay * 2 -> if (result > 50) -> if (result < 100) -> return 1 | ||
val machine = TsMachine(scene, options, tsOptions, machineObserver = ReachabilityObserver()) | ||
val method = scene.projectClasses | ||
.flatMap { it.methods } | ||
.single { it.name == "asyncAwaitReachable" } | ||
|
||
val initialTarget = TsReachabilityTarget.InitialPoint(method.cfg.stmts.first()) | ||
var target: TsTarget = initialTarget | ||
|
||
// if (result > 50) | ||
val minCheck = method.cfg.stmts.filterIsInstance<EtsIfStmt>()[0] | ||
target = target.addChild(TsReachabilityTarget.IntermediatePoint(minCheck)) | ||
|
||
// if (result < 100) | ||
val maxCheck = method.cfg.stmts.filterIsInstance<EtsIfStmt>()[1] | ||
target = target.addChild(TsReachabilityTarget.IntermediatePoint(maxCheck)) | ||
|
||
// return 1 | ||
val returnStmt = method.cfg.stmts.filterIsInstance<EtsReturnStmt>()[0] | ||
target.addChild(TsReachabilityTarget.FinalPoint(returnStmt)) | ||
|
||
val results = machine.analyze(listOf(method), listOf(initialTarget)) | ||
assertTrue( | ||
results.isNotEmpty(), | ||
"Expected at least one result for async/await reachability" | ||
) | ||
|
||
val reachedStatements = results.flatMap { it.pathNode.allStatements }.toSet() | ||
assertTrue( | ||
returnStmt in reachedStatements, | ||
"Expected return statement to be reached when delay * 2 is between 50-100" | ||
) | ||
} | ||
|
||
@Test | ||
fun testPromiseChainReachable() { | ||
// Test reachability through Promise chain: | ||
// const doubled = value * 2 -> if (doubled === 20) -> return Promise.resolve(1) | ||
val machine = TsMachine(scene, options, tsOptions, machineObserver = ReachabilityObserver()) | ||
val method = scene.projectClasses | ||
.flatMap { it.methods } | ||
.single { it.name == "promiseChainReachable" } | ||
|
||
val initialTarget = TsReachabilityTarget.InitialPoint(method.cfg.stmts.first()) | ||
var target: TsTarget = initialTarget | ||
|
||
// if (doubled === 20) | ||
val doubledCheck = method.cfg.stmts.filterIsInstance<EtsIfStmt>()[0] | ||
target = target.addChild(TsReachabilityTarget.IntermediatePoint(doubledCheck)) | ||
|
||
// return Promise.resolve(1) | ||
val returnStmt = method.cfg.stmts.filterIsInstance<EtsReturnStmt>()[0] | ||
target.addChild(TsReachabilityTarget.FinalPoint(returnStmt)) | ||
|
||
val results = machine.analyze(listOf(method), listOf(initialTarget)) | ||
assertTrue( | ||
results.isNotEmpty(), | ||
"Expected at least one result for Promise chain reachability" | ||
) | ||
|
||
val reachedStatements = results.flatMap { it.pathNode.allStatements }.toSet() | ||
assertTrue( | ||
returnStmt in reachedStatements, | ||
"Expected return statement to be reached when input value is 10" | ||
) | ||
} | ||
|
||
@Disabled("No ::map method") | ||
@Test | ||
fun testPromiseAllReachable() { | ||
// Test reachability through Promise.all pattern: | ||
// const results = values.map(v => v * 2) -> if (results.length === 3) -> if (results[1] === 40) -> return 1 | ||
val machine = TsMachine(scene, options, tsOptions, machineObserver = ReachabilityObserver()) | ||
val method = scene.projectClasses | ||
.flatMap { it.methods } | ||
.single { it.name == "promiseAllReachable" } | ||
|
||
val initialTarget = TsReachabilityTarget.InitialPoint(method.cfg.stmts.first()) | ||
var target: TsTarget = initialTarget | ||
|
||
// if (results.length === 3) | ||
val lengthCheck = method.cfg.stmts.filterIsInstance<EtsIfStmt>()[0] | ||
target = target.addChild(TsReachabilityTarget.IntermediatePoint(lengthCheck)) | ||
|
||
// if (results[1] === 40) | ||
val valueCheck = method.cfg.stmts.filterIsInstance<EtsIfStmt>()[1] | ||
target = target.addChild(TsReachabilityTarget.IntermediatePoint(valueCheck)) | ||
|
||
// return 1 | ||
val returnStmt = method.cfg.stmts.filterIsInstance<EtsReturnStmt>()[0] | ||
target.addChild(TsReachabilityTarget.FinalPoint(returnStmt)) | ||
|
||
val results = machine.analyze(listOf(method), listOf(initialTarget)) | ||
assertTrue( | ||
results.isNotEmpty(), | ||
"Expected at least one result for Promise.all reachability" | ||
) | ||
|
||
val reachedStatements = results.flatMap { it.pathNode.allStatements }.toSet() | ||
assertTrue( | ||
returnStmt in reachedStatements, | ||
"Expected return statement to be reached when values[1] is 20 (doubled to 40)" | ||
) | ||
} | ||
|
||
@Disabled("Function types are not supported in EtsHierarchy") | ||
@Test | ||
fun testCallbackReachable() { | ||
// Test reachability through callback pattern: | ||
// const processed = value + 10 -> if (processed > 25) -> callback(processed) -> return 1 | ||
val machine = TsMachine(scene, options, tsOptions, machineObserver = ReachabilityObserver()) | ||
val method = scene.projectClasses | ||
.flatMap { it.methods } | ||
.single { it.name == "callbackReachable" } | ||
|
||
val initialTarget = TsReachabilityTarget.InitialPoint(method.cfg.stmts.first()) | ||
var target: TsTarget = initialTarget | ||
|
||
// if (processed > 25) | ||
val processedCheck = method.cfg.stmts.filterIsInstance<EtsIfStmt>()[0] | ||
target = target.addChild(TsReachabilityTarget.IntermediatePoint(processedCheck)) | ||
|
||
// return 1 | ||
val returnStmt = method.cfg.stmts.filterIsInstance<EtsReturnStmt>()[0] | ||
target.addChild(TsReachabilityTarget.FinalPoint(returnStmt)) | ||
|
||
val results = machine.analyze(listOf(method), listOf(initialTarget)) | ||
assertTrue( | ||
results.isNotEmpty(), | ||
"Expected at least one result for callback reachability" | ||
) | ||
|
||
val reachedStatements = results.flatMap { it.pathNode.allStatements }.toSet() | ||
assertTrue( | ||
returnStmt in reachedStatements, | ||
"Expected return statement to be reached when value > 15 (processed > 25)" | ||
) | ||
} | ||
|
||
@Test | ||
fun testErrorHandlingReachable() { | ||
// Test reachability through try-catch error handling: | ||
// try { if (shouldThrow) throw Error -> return 1 } catch { return -1 } | ||
val machine = TsMachine(scene, options, tsOptions, machineObserver = ReachabilityObserver()) | ||
val method = scene.projectClasses | ||
.flatMap { it.methods } | ||
.single { it.name == "errorHandlingReachable" } | ||
|
||
val initialTarget = TsReachabilityTarget.InitialPoint(method.cfg.stmts.first()) | ||
var target: TsTarget = initialTarget | ||
|
||
// if (shouldThrow) | ||
val throwCheck = method.cfg.stmts.filterIsInstance<EtsIfStmt>()[0] | ||
target = target.addChild(TsReachabilityTarget.IntermediatePoint(throwCheck)) | ||
|
||
// Both return paths should be reachable | ||
val returnStmts = method.cfg.stmts.filterIsInstance<EtsReturnStmt>() | ||
returnStmts.forEach { returnStmt -> | ||
target.addChild(TsReachabilityTarget.FinalPoint(returnStmt)) | ||
} | ||
|
||
val results = machine.analyze(listOf(method), listOf(initialTarget)) | ||
assertTrue( | ||
results.isNotEmpty(), | ||
"Expected at least one result for error handling reachability" | ||
) | ||
|
||
val reachedStatements = results.flatMap { it.pathNode.allStatements }.toSet() | ||
assertTrue( | ||
returnStmts.any { it in reachedStatements }, | ||
"Expected at least one return statement to be reached in try-catch" | ||
) | ||
} | ||
|
||
@Test | ||
fun testConditionalAsyncReachable() { | ||
// Test reachability through conditional async pattern: | ||
// if (useSync) result = value * 2 else result = value + 10 -> if (result === 20) -> return 1 | ||
val machine = TsMachine(scene, options, tsOptions, machineObserver = ReachabilityObserver()) | ||
val method = scene.projectClasses | ||
.flatMap { it.methods } | ||
.single { it.name == "conditionalAsyncReachable" } | ||
|
||
val initialTarget = TsReachabilityTarget.InitialPoint(method.cfg.stmts.first()) | ||
var target: TsTarget = initialTarget | ||
|
||
// if (useSync) | ||
val useSyncCheck = method.cfg.stmts.filterIsInstance<EtsIfStmt>()[0] | ||
target = target.addChild(TsReachabilityTarget.IntermediatePoint(useSyncCheck)) | ||
|
||
// if (result === 20) | ||
val resultCheck = method.cfg.stmts.filterIsInstance<EtsIfStmt>()[1] | ||
target = target.addChild(TsReachabilityTarget.IntermediatePoint(resultCheck)) | ||
|
||
// return 1 | ||
val returnStmt = method.cfg.stmts.filterIsInstance<EtsReturnStmt>()[0] | ||
target.addChild(TsReachabilityTarget.FinalPoint(returnStmt)) | ||
|
||
val results = machine.analyze(listOf(method), listOf(initialTarget)) | ||
assertTrue( | ||
results.isNotEmpty(), | ||
"Expected at least one result for conditional async reachability" | ||
) | ||
|
||
val reachedStatements = results.flatMap { it.pathNode.allStatements }.toSet() | ||
assertTrue( | ||
returnStmt in reachedStatements, | ||
"Expected return statement to be reached through conditional branches" | ||
) | ||
} | ||
|
||
@Test | ||
fun testPromiseCreationReachable() { | ||
// Test reachability through Promise creation logic: | ||
// if (shouldResolve) -> if (value > 5) -> return 1 | ||
// else -> if (value < 0) -> return -1 | ||
val machine = TsMachine(scene, options, tsOptions, machineObserver = ReachabilityObserver()) | ||
val method = scene.projectClasses | ||
.flatMap { it.methods } | ||
.single { it.name == "promiseCreationReachable" } | ||
|
||
val initialTarget = TsReachabilityTarget.InitialPoint(method.cfg.stmts.first()) | ||
var target: TsTarget = initialTarget | ||
|
||
// if (shouldResolve) | ||
val shouldResolveCheck = method.cfg.stmts.filterIsInstance<EtsIfStmt>()[0] | ||
target = target.addChild(TsReachabilityTarget.IntermediatePoint(shouldResolveCheck)) | ||
|
||
// Multiple return paths | ||
val returnStmts = method.cfg.stmts.filterIsInstance<EtsReturnStmt>() | ||
returnStmts.forEach { returnStmt -> | ||
target.addChild(TsReachabilityTarget.FinalPoint(returnStmt)) | ||
} | ||
|
||
val results = machine.analyze(listOf(method), listOf(initialTarget)) | ||
assertTrue( | ||
results.isNotEmpty(), | ||
"Expected at least one result for Promise creation reachability" | ||
) | ||
|
||
val reachedStatements = results.flatMap { it.pathNode.allStatements }.toSet() | ||
assertTrue( | ||
returnStmts.any { it in reachedStatements }, | ||
"Expected at least one return statement to be reached in Promise creation" | ||
) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / detekt
Braces do not comply with the specified policy Warning