Skip to content

Socket connection in python fuzzing #1844

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 38 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
991aa1b
Work on sockets
tamarinvs19 Feb 15, 2023
3fd7906
Work on sockets
tamarinvs19 Feb 15, 2023
e606b2d
Added new execution version with sockets
Feb 20, 2023
7ff12cd
Update socket version
Feb 22, 2023
18a7768
Update requirements.txt
Feb 22, 2023
08dd945
Work on sockets
tamarinvs19 Feb 15, 2023
40c1454
Work on sockets
tamarinvs19 Feb 15, 2023
6088f9f
Added new execution version with sockets
Feb 20, 2023
f5df9e7
Update socket version
Feb 22, 2023
4cec02c
Update requirements.txt
Feb 22, 2023
c965458
Fix rebase
Feb 22, 2023
bdb5436
Merge remote-tracking branch 'origin/tamarinvs19/utbot-python-sockets…
Feb 22, 2023
58018cd
Merge branch 'main' into tamarinvs19/utbot-python-sockets
tamarinvs19 Feb 23, 2023
609e094
Merge branch 'main' into tamarinvs19/utbot-python-add-sockets
Feb 27, 2023
a1c3f9a
Fixed merge bugs
tamarinvs19 Feb 27, 2023
9f92d64
Fixed data size bug
tamarinvs19 Feb 27, 2023
bdbe677
Update socket execution logic
tamarinvs19 Feb 27, 2023
045f4f5
Merge branch 'main' into tamarinvs19/utbot-python-sockets
tamarinvs19 Feb 27, 2023
1c7b07a
Add feedback from execution, INVALID_EXECUTION_LIMIT and debug in python
tamarinvs19 Feb 27, 2023
6cbdeef
Update constants and requirements.txt
tamarinvs19 Feb 27, 2023
634b697
Change string constants and add encoding in socket streams
tamarinvs19 Mar 1, 2023
17c2344
fuzzed value memorization + new type inference heuristic
tochilinak Mar 1, 2023
80817de
Fix method import paths, collecting modules to globals, string repres…
tamarinvs19 Mar 1, 2023
99c16cb
Set cache size limit + fixed PythonFeedback
tochilinak Mar 2, 2023
e866d2c
Fixed cache selector
tochilinak Mar 2, 2023
323d88a
Use chooseOne function from utbot-fuzzing
tochilinak Mar 2, 2023
323cc37
Added PythonWorkerManager to handle socket exceptions
tamarinvs19 Mar 3, 2023
0a3abd8
Merge branch 'main' into tamarinvs19/utbot-python-sockets
tochilinak Mar 3, 2023
a5bde75
Clear preprocessed_values.json
tamarinvs19 Mar 3, 2023
0da909d
Update string constants
tamarinvs19 Mar 3, 2023
9a039cb
Update requirements
tamarinvs19 Mar 3, 2023
573733d
Update requirements
tamarinvs19 Mar 3, 2023
2f576b6
Better handling of utbot_executor exceptions
tochilinak Mar 3, 2023
db50188
Memorization of recursive objects
tochilinak Mar 3, 2023
52cd8e9
More accurate timeout handling
tochilinak Mar 3, 2023
ec7c754
Moved evaluation cache to separate class
tochilinak Mar 3, 2023
a1a6638
Added seed in type inference; get timeoutForRun from settings
tochilinak Mar 3, 2023
aea190c
Merge branch 'main' into tamarinvs19/utbot-python-sockets
tochilinak Mar 3, 2023
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
2 changes: 1 addition & 1 deletion utbot-python/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ tasks {
}

dependencies {
api(project(":utbot-fuzzers"))
api(project(":utbot-fuzzing"))
api(project(":utbot-framework"))
api(project(":utbot-python-parser"))
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
Expand Down
2 changes: 1 addition & 1 deletion utbot-python/samples/easy_samples/annotation_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class A(Generic[XXX]):
self_: XXX

def f(self, a, b: A[int]):
def f(self, a, b: 'A'[int]):
self.y = b
self.self_.x = b
pass
Expand Down
2 changes: 1 addition & 1 deletion utbot-python/samples/easy_samples/boruvka.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def boruvka(self) -> None:
component_size = []
mst_weight = 0

minimum_weight_edge: list[Any] = [-1] * self.m_num_of_nodes
minimum_weight_edge: list = [-1] * self.m_num_of_nodes

# A list of components (initialized to all of the nodes)
for node in range(self.m_num_of_nodes):
Expand Down
240 changes: 134 additions & 106 deletions utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,27 @@ package org.utbot.python
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import mu.KotlinLogging
import org.utbot.framework.plugin.api.DocRegularStmt
import org.utbot.framework.plugin.api.EnvironmentModels
import org.utbot.framework.plugin.api.Instruction
import org.utbot.framework.plugin.api.UtError
import org.utbot.framework.plugin.api.UtExecutionResult
import org.utbot.framework.plugin.api.UtExecutionSuccess
import org.utbot.framework.plugin.api.UtExplicitlyThrownException
import org.utbot.framework.plugin.api.UtModel
import org.utbot.fuzzer.FuzzedValue
import org.utbot.framework.plugin.api.*
import org.utbot.fuzzer.UtFuzzedExecution
import org.utbot.fuzzing.Control
import org.utbot.fuzzing.fuzz
import org.utbot.fuzzing.utils.Trie
import org.utbot.python.code.MemoryDump
import org.utbot.python.code.toPythonTree
import org.utbot.python.evaluation.PythonCodeExecutor
import org.utbot.python.evaluation.PythonCodeExecutorImpl
import org.utbot.python.evaluation.PythonEvaluationError
import org.utbot.python.evaluation.PythonEvaluationSuccess
import org.utbot.python.evaluation.PythonEvaluationTimeout
import org.utbot.python.evaluation.*
import org.utbot.python.evaluation.serialiation.MemoryDump
import org.utbot.python.evaluation.serialiation.toPythonTree
import org.utbot.python.framework.api.python.PythonTreeModel
import org.utbot.python.fuzzing.PythonFeedback
import org.utbot.python.fuzzing.PythonFuzzedConcreteValue
import org.utbot.python.fuzzing.PythonFuzzedValue
import org.utbot.python.fuzzing.PythonFuzzing
import org.utbot.python.fuzzing.PythonMethodDescription
import org.utbot.python.framework.api.python.PythonTreeWrapper
import org.utbot.python.fuzzing.*
import org.utbot.python.newtyping.PythonTypeStorage
import org.utbot.python.newtyping.general.Type
import org.utbot.python.newtyping.pythonModules
import org.utbot.python.newtyping.pythonTypeRepresentation
import org.utbot.python.utils.camelToSnakeCase
import org.utbot.summary.fuzzer.names.TestSuggestedInfo
import java.net.ServerSocket

private val logger = KotlinLogging.logger {}

sealed interface FuzzingExecutionFeedback
class ValidExecution(val utFuzzedExecution: UtFuzzedExecution): FuzzingExecutionFeedback
class InvalidExecution(val utError: UtError): FuzzingExecutionFeedback
class TypeErrorFeedback(val message: String) : FuzzingExecutionFeedback
class ArgumentsTypeErrorFeedback(val message: String) : FuzzingExecutionFeedback

class PythonEngine(
private val methodUnderTest: PythonMethod,
private val directoriesForSysPath: Set<String>,
Expand All @@ -55,6 +35,8 @@ class PythonEngine(
private val pythonTypeStorage: PythonTypeStorage,
) {

private val cache = EvaluationCache()

private fun suggestExecutionName(
description: PythonMethodDescription,
executionResult: UtExecutionResult
Expand Down Expand Up @@ -146,109 +128,155 @@ class PythonEngine(
return ValidExecution(utFuzzedExecution)
}

private fun constructEvaluationInput(arguments: List<PythonFuzzedValue>, additionalModules: List<String>): PythonCodeExecutor {
val argumentValues = arguments.map { PythonTreeModel(it.tree, it.tree.type) }

val (thisObject, modelList) =
if (methodUnderTest.hasThisArgument)
Pair(argumentValues[0], argumentValues.drop(1))
else
Pair(null, argumentValues)

val argumentModules = argumentValues
.flatMap { it.allContainingClassIds }
.map { it.moduleName }
.filterNot { it.startsWith(moduleToImport) }
val localAdditionalModules = (additionalModules + argumentModules + moduleToImport).toSet()

return PythonCodeExecutorImpl(
private fun constructEvaluationInput(pythonWorker: PythonWorker): PythonCodeExecutor {
return PythonCodeSocketExecutor(
methodUnderTest,
FunctionArguments(thisObject, methodUnderTest.thisObjectName, modelList, methodUnderTest.argumentsNames),
argumentValues.map { FuzzedValue(it) },
moduleToImport,
localAdditionalModules,
pythonPath,
directoriesForSysPath,
timeoutForRun,
pythonWorker,
)
}

fun fuzzing(parameters: List<Type>, isCancelled: () -> Boolean, until: Long): Flow<FuzzingExecutionFeedback> = flow {
val additionalModules = parameters.flatMap { it.pythonModules() }
val coveredLines = initialCoveredLines.toMutableSet()

suspend fun fuzzingResultHandler(description: PythonMethodDescription, arguments: List<PythonFuzzedValue>): PythonFeedback {
val codeExecutor = constructEvaluationInput(arguments, additionalModules)
return when (val evaluationResult = codeExecutor.run()) {
is PythonEvaluationError -> {
val utError = UtError(
"Error evaluation: ${evaluationResult.status}, ${evaluationResult.message}",
Throwable(evaluationResult.stackTrace.joinToString("\n"))
)
logger.debug(evaluationResult.stackTrace.joinToString("\n"))
emit(InvalidExecution(utError))
PythonFeedback(control = Control.PASS)
}
ServerSocket(0).use { serverSocket ->
logger.info { "Server port: ${serverSocket.localPort}" }
val manager = PythonWorkerManager(
serverSocket,
pythonPath,
until,
{ constructEvaluationInput(it) },
timeoutForRun.toInt()
)
logger.info { "Executor manager was created successfully" }

is PythonEvaluationTimeout -> {
val utError = UtError(evaluationResult.message, Throwable())
emit(InvalidExecution(utError))
PythonFeedback(control = Control.PASS)
}
fun fuzzingResultHandler(
description: PythonMethodDescription,
arguments: List<PythonFuzzedValue>
): PythonExecutionResult? {
val argumentValues = arguments.map { PythonTreeModel(it.tree, it.tree.type) }
logger.debug(argumentValues.map { it.tree } .toString())
val argumentModules = argumentValues
.flatMap { it.allContainingClassIds }
.map { it.moduleName }
.filterNot { it.startsWith(moduleToImport) }
val localAdditionalModules = (additionalModules + argumentModules + moduleToImport).toSet()

is PythonEvaluationSuccess -> {
val coveredInstructions = evaluationResult.coverage.coveredInstructions
coveredInstructions.forEach { coveredLines.add(it.lineNumber) }
val (thisObject, modelList) =
if (methodUnderTest.hasThisArgument)
Pair(argumentValues[0], argumentValues.drop(1))
else
Pair(null, argumentValues)
val functionArguments = FunctionArguments(
thisObject,
methodUnderTest.thisObjectName,
modelList,
methodUnderTest.argumentsNames
)
try {
return when (val evaluationResult = manager.run(functionArguments, localAdditionalModules)) {
is PythonEvaluationError -> {
val utError = UtError(
"Error evaluation: ${evaluationResult.status}, ${evaluationResult.message}",
Throwable(evaluationResult.stackTrace.joinToString("\n"))
)
logger.debug(evaluationResult.stackTrace.joinToString("\n"))
PythonExecutionResult(InvalidExecution(utError), PythonFeedback(control = Control.PASS))
}

val summary = arguments
.zip(methodUnderTest.arguments)
.mapNotNull { it.first.summary?.replace("%var%", it.second.name) }
is PythonEvaluationTimeout -> {
val utError = UtError(evaluationResult.message, Throwable())
PythonExecutionResult(InvalidExecution(utError), PythonFeedback(control = Control.PASS))
}

val hasThisObject = codeExecutor.methodArguments.thisObject != null
is PythonEvaluationSuccess -> {
val coveredInstructions = evaluationResult.coverage.coveredInstructions
coveredInstructions.forEach { coveredLines.add(it.lineNumber) }

when (val result = handleSuccessResult(parameters, evaluationResult, description, hasThisObject, summary)) {
is ValidExecution -> {
logger.debug { arguments }
val trieNode: Trie.Node<Instruction> = description.tracer.add(coveredInstructions)
emit(result)
PythonFeedback(control = Control.CONTINUE, result = trieNode)
}
is ArgumentsTypeErrorFeedback, is TypeErrorFeedback -> {
emit(result)
PythonFeedback(control = Control.PASS)
}
is InvalidExecution -> {
emit(result)
PythonFeedback(control = Control.CONTINUE)
val summary = arguments
.zip(methodUnderTest.arguments)
.mapNotNull { it.first.summary?.replace("%var%", it.second.name) }

val hasThisObject = methodUnderTest.hasThisArgument

when (val result = handleSuccessResult(
parameters,
evaluationResult,
description,
hasThisObject,
summary
)) {
is ValidExecution -> {
val trieNode: Trie.Node<Instruction> = description.tracer.add(coveredInstructions)
PythonExecutionResult(
result,
PythonFeedback(control = Control.CONTINUE, result = trieNode)
)
}

is ArgumentsTypeErrorFeedback, is TypeErrorFeedback -> {
PythonExecutionResult(result, PythonFeedback(control = Control.PASS))
}

is InvalidExecution -> {
PythonExecutionResult(result, PythonFeedback(control = Control.CONTINUE))
}
}
}
}
} catch (_: TimeoutException) {
return null
}
}
}

val pmd = PythonMethodDescription(
methodUnderTest.name,
parameters,
fuzzedConcreteValues,
pythonTypeStorage,
Trie(Instruction::id)
)
val pmd = PythonMethodDescription(
methodUnderTest.name,
parameters,
fuzzedConcreteValues,
pythonTypeStorage,
Trie(Instruction::id)
)

if (parameters.isEmpty()) {
fuzzingResultHandler(pmd, emptyList())
} else {
PythonFuzzing(pmd.pythonTypeStorage) { description, arguments ->
if (isCancelled()) {
logger.info { "Fuzzing process was interrupted" }
return@PythonFuzzing PythonFeedback(control = Control.STOP)
}
if (System.currentTimeMillis() >= until) {
logger.info { "Fuzzing process was interrupted by timeout" }
return@PythonFuzzing PythonFeedback(control = Control.STOP)
}
if (parameters.isEmpty()) {
fuzzingResultHandler(pmd, emptyList())
manager.disconnect()
} else {
PythonFuzzing(pmd.pythonTypeStorage) { description, arguments ->
if (isCancelled()) {
logger.info { "Fuzzing process was interrupted" }
manager.disconnect()
return@PythonFuzzing PythonFeedback(control = Control.STOP)
}
if (System.currentTimeMillis() >= until) {
logger.info { "Fuzzing process was interrupted by timeout" }
manager.disconnect()
return@PythonFuzzing PythonFeedback(control = Control.STOP)
}

return@PythonFuzzing fuzzingResultHandler(description, arguments)
}.fuzz(pmd)
val pair = Pair(description, arguments.map { PythonTreeWrapper(it.tree) })
val mem = cache.get(pair)
if (mem != null) {
logger.debug("Repeat in fuzzing")
emit(mem.fuzzingExecutionFeedback)
return@PythonFuzzing mem.fuzzingPlatformFeedback
}
val result = fuzzingResultHandler(description, arguments)
if (result == null) { // timeout
logger.info { "Fuzzing process was interrupted by timeout" }
manager.disconnect()
return@PythonFuzzing PythonFeedback(control = Control.STOP)
}

cache.add(pair, result)
emit(result.fuzzingExecutionFeedback)
return@PythonFuzzing result.fuzzingPlatformFeedback

}.fuzz(pmd)
}
}
}
}
Loading