hu.bme.mit.theta.xcfa.cli.ExecuteConfig.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of theta-xcfa-cli Show documentation
Show all versions of theta-xcfa-cli Show documentation
Xcfa Cli subproject in the Theta model checking framework
The newest version!
/*
* Copyright 2024 Budapest University of Technology and Economics
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package hu.bme.mit.theta.xcfa.cli
import com.google.common.base.Stopwatch
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import hu.bme.mit.theta.analysis.Action
import hu.bme.mit.theta.analysis.EmptyCex
import hu.bme.mit.theta.analysis.State
import hu.bme.mit.theta.analysis.Trace
import hu.bme.mit.theta.analysis.algorithm.EmptyProof
import hu.bme.mit.theta.analysis.algorithm.SafetyResult
import hu.bme.mit.theta.analysis.algorithm.arg.ARG
import hu.bme.mit.theta.analysis.algorithm.arg.debug.ARGWebDebugger
import hu.bme.mit.theta.analysis.expl.ExplState
import hu.bme.mit.theta.analysis.ptr.PtrState
import hu.bme.mit.theta.analysis.utils.ArgVisualizer
import hu.bme.mit.theta.analysis.utils.TraceVisualizer
import hu.bme.mit.theta.c2xcfa.CMetaData
import hu.bme.mit.theta.cat.dsl.CatDslManager
import hu.bme.mit.theta.common.logging.Logger
import hu.bme.mit.theta.common.logging.Logger.Level.*
import hu.bme.mit.theta.common.visualization.Graph
import hu.bme.mit.theta.common.visualization.writer.GraphvizWriter
import hu.bme.mit.theta.common.visualization.writer.WebDebuggerLogger
import hu.bme.mit.theta.frontend.ParseContext
import hu.bme.mit.theta.graphsolver.patterns.constraints.MCM
import hu.bme.mit.theta.xcfa.analysis.ErrorDetection
import hu.bme.mit.theta.xcfa.analysis.XcfaAction
import hu.bme.mit.theta.xcfa.analysis.XcfaState
import hu.bme.mit.theta.xcfa.analysis.coi.ConeOfInfluence
import hu.bme.mit.theta.xcfa.analysis.coi.XcfaCoiMultiThread
import hu.bme.mit.theta.xcfa.analysis.coi.XcfaCoiSingleThread
import hu.bme.mit.theta.xcfa.analysis.por.XcfaDporLts
import hu.bme.mit.theta.xcfa.analysis.por.XcfaSporLts
import hu.bme.mit.theta.xcfa.cli.checkers.getChecker
import hu.bme.mit.theta.xcfa.cli.params.*
import hu.bme.mit.theta.xcfa.cli.utils.*
import hu.bme.mit.theta.xcfa.cli.witnesses.XcfaTraceConcretizer
import hu.bme.mit.theta.xcfa.getFlatLabels
import hu.bme.mit.theta.xcfa.model.XCFA
import hu.bme.mit.theta.xcfa.model.XcfaLabel
import hu.bme.mit.theta.xcfa.model.toDot
import hu.bme.mit.theta.xcfa.passes.*
import hu.bme.mit.theta.xcfa.toC
import hu.bme.mit.theta.xcfa2chc.toSMT2CHC
import java.io.File
import java.util.concurrent.TimeUnit
import kotlin.random.Random
fun runConfig(
config: XcfaConfig<*, *>,
logger: Logger,
uniqueLogger: Logger,
throwDontExit: Boolean,
): SafetyResult<*, *> {
propagateInputOptions(config, logger, uniqueLogger)
registerAllSolverManagers(config.backendConfig.solverHome, logger)
validateInputOptions(config, logger, uniqueLogger)
val (xcfa, mcm, parseContext) = frontend(config, logger, uniqueLogger)
preVerificationLogging(xcfa, mcm, parseContext, config, logger, uniqueLogger)
val result = backend(xcfa, mcm, parseContext, config, logger, uniqueLogger, throwDontExit)
postVerificationLogging(result, mcm, parseContext, config, logger, uniqueLogger)
return result
}
private fun propagateInputOptions(config: XcfaConfig<*, *>, logger: Logger, uniqueLogger: Logger) {
config.inputConfig.property = determineProperty(config, logger)
LbePass.level = config.frontendConfig.lbeLevel
StaticCoiPass.enabled = config.frontendConfig.staticCoi
if (config.backendConfig.backend == Backend.CEGAR) {
val cegarConfig = config.backendConfig.specConfig
cegarConfig as CegarConfig
val random = Random(cegarConfig.porRandomSeed)
XcfaSporLts.random = random
XcfaDporLts.random = random
}
if (
config.inputConfig.property == ErrorDetection.MEMSAFETY ||
config.inputConfig.property == ErrorDetection.MEMCLEANUP
) {
MemsafetyPass.NEED_CHECK = true
}
if (config.debugConfig.argToFile) {
WebDebuggerLogger.enableWebDebuggerLogger()
WebDebuggerLogger.getInstance().setTitle(config.inputConfig.input?.name)
}
LoopUnrollPass.UNROLL_LIMIT = config.frontendConfig.loopUnroll
LoopUnrollPass.FORCE_UNROLL_LIMIT = config.frontendConfig.forceUnroll
FetchExecuteWriteback.enabled = config.frontendConfig.enableFew
ARGWebDebugger.on = config.debugConfig.argdebug
}
private fun validateInputOptions(config: XcfaConfig<*, *>, logger: Logger, uniqueLogger: Logger) {
rule("NoCoiWhenDataRace") {
config.backendConfig.backend == Backend.CEGAR &&
(config.backendConfig.specConfig as? CegarConfig)?.coi != ConeOfInfluenceMode.NO_COI &&
config.inputConfig.property == ErrorDetection.DATA_RACE
}
rule("NoAaporWhenDataRace") {
(config.backendConfig.specConfig as? CegarConfig)?.porLevel?.isAbstractionAware == true &&
config.inputConfig.property == ErrorDetection.DATA_RACE
}
rule("DPORWithoutDFS") {
(config.backendConfig.specConfig as? CegarConfig)?.porLevel?.isDynamic == true &&
(config.backendConfig.specConfig as? CegarConfig)?.abstractorConfig?.search != Search.DFS
}
rule("SensibleLoopUnrollLimits") {
config.frontendConfig.loopUnroll != -1 &&
config.frontendConfig.loopUnroll < config.frontendConfig.forceUnroll
}
rule("NoPredSplitUntilFixed(https://github.com/ftsrg/theta/issues/267)") {
(config.backendConfig.specConfig as? CegarConfig)?.abstractorConfig?.domain == Domain.PRED_SPLIT
}
}
fun frontend(
config: XcfaConfig<*, *>,
logger: Logger,
uniqueLogger: Logger,
): Triple {
if (config.inputConfig.xcfaWCtx != null) {
val xcfa = config.inputConfig.xcfaWCtx!!.first
ConeOfInfluence =
if (config.inputConfig.xcfaWCtx!!.third.multiThreading) {
XcfaCoiMultiThread(xcfa)
} else {
XcfaCoiSingleThread(xcfa)
}
return config.inputConfig.xcfaWCtx!!
}
val stopwatch = Stopwatch.createStarted()
val input = config.inputConfig.input!!
logger.write(
Logger.Level.INFO,
"Parsing the input $input as ${config.frontendConfig.inputType}\n",
)
val parseContext = ParseContext()
if (config.frontendConfig.inputType == InputType.C) {
val cConfig = config.frontendConfig.specConfig
cConfig as CFrontendConfig
parseContext.arithmetic = cConfig.arithmetic
parseContext.architecture = cConfig.architecture
}
val xcfa = getXcfa(config, parseContext, logger, uniqueLogger)
val mcm =
if (config.inputConfig.catFile != null) {
CatDslManager.createMCM(config.inputConfig.catFile!!)
} else {
emptySet()
}
ConeOfInfluence =
if (parseContext.multiThreading) XcfaCoiMultiThread(xcfa) else XcfaCoiSingleThread(xcfa)
if (
parseContext.multiThreading &&
(config.backendConfig.specConfig as? CegarConfig)?.let {
it.abstractorConfig.search == Search.ERR
} == true
) {
val cConfig = config.backendConfig.specConfig as CegarConfig
cConfig.abstractorConfig.search = Search.DFS
uniqueLogger.write(INFO, "Multithreaded program found, using DFS instead of ERR.")
}
logger.write(
Logger.Level.INFO,
"Frontend finished: ${xcfa.name} (in ${
stopwatch.elapsed(TimeUnit.MILLISECONDS)
} ms)\n",
)
logger.write(RESULT, "ParsingResult Success\n")
logger.write(
RESULT,
"Alias graph size: ${xcfa.pointsToGraph.size} -> ${xcfa.pointsToGraph.values.map { it.size }.toList()}\n",
)
return Triple(xcfa, mcm, parseContext)
}
private fun backend(
xcfa: XCFA,
mcm: MCM,
parseContext: ParseContext,
config: XcfaConfig<*, *>,
logger: Logger,
uniqueLogger: Logger,
throwDontExit: Boolean,
): SafetyResult<*, *> =
if (config.backendConfig.backend == Backend.NONE) {
SafetyResult.unknown()
} else {
if (
xcfa.procedures.all {
it.errorLoc.isEmpty && config.inputConfig.property == ErrorDetection.ERROR_LOCATION
}
) {
val result = SafetyResult.safe(EmptyProof.getInstance())
logger.write(Logger.Level.INFO, "Input is trivially safe\n")
logger.write(RESULT, result.toString() + "\n")
result
} else {
val stopwatch = Stopwatch.createStarted()
logger.write(
Logger.Level.INFO,
"Starting verification of ${if (xcfa.name == "") "UnnamedXcfa" else xcfa.name} using ${config.backendConfig.backend}\n",
)
val checker = getChecker(xcfa, mcm, config, parseContext, logger, uniqueLogger)
val result =
exitOnError(config.debugConfig.stacktrace, config.debugConfig.debug || throwDontExit) {
checker.check()
}
.let ResultMapper@{ result ->
when {
result.isSafe && xcfa.unsafeUnrollUsed -> {
// cannot report safe if force unroll was used
logger.write(RESULT, "Incomplete loop unroll used: safe result is unreliable.\n")
if (config.outputConfig.acceptUnreliableSafe)
result // for comparison with BMC tools
else SafetyResult.unknown()
}
result.isUnsafe -> {
// need to determine what kind
val property =
when (config.inputConfig.property) {
ErrorDetection.MEMSAFETY,
ErrorDetection.MEMCLEANUP -> {
val trace = result.asUnsafe().cex as? Trace, XcfaAction>
trace
?.states
?.asReversed()
?.firstOrNull {
it.processes.values.any { it.locs.any { it.name.contains("__THETA_") } }
}
?.processes
?.values
?.firstOrNull { it.locs.any { it.name.contains("__THETA_") } }
?.locs
?.firstOrNull { it.name.contains("__THETA_") }
?.name
?.let {
when (it) {
"__THETA_bad_free" -> "valid-free"
"__THETA_bad_deref" -> "valid-deref"
"__THETA_lost" ->
if (config.inputConfig.property == ErrorDetection.MEMCLEANUP)
"valid-memcleanup"
else
"valid-memtrack"
.also { // this is not an exact check.
return@ResultMapper SafetyResult.unknown()
}
else ->
throw RuntimeException(
"Something went wrong; could not determine subproperty! Named location: $it"
)
}
}
}
ErrorDetection.DATA_RACE -> "no-data-race"
ErrorDetection.ERROR_LOCATION -> "unreach-call"
ErrorDetection.OVERFLOW -> "no-overflow"
ErrorDetection.NO_ERROR -> null
}
property?.also { logger.write(RESULT, "(Property %s)\n", it) }
result
}
else -> {
result
}
}
}
logger.write(
Logger.Level.INFO,
"Backend finished (in ${
stopwatch.elapsed(TimeUnit.MILLISECONDS)
} ms)\n",
)
logger.write(RESULT, result.toString() + "\n")
result
}
}
private fun preVerificationLogging(
xcfa: XCFA,
mcm: MCM,
parseContext: ParseContext,
config: XcfaConfig<*, *>,
logger: Logger,
uniqueLogger: Logger,
) {
if (config.outputConfig.enableOutput) {
try {
val resultFolder = config.outputConfig.resultFolder
resultFolder.mkdirs()
logger.write(
Logger.Level.INFO,
"Writing pre-verification artifacts to directory ${resultFolder.absolutePath}\n",
)
if (!config.outputConfig.chcOutputConfig.disable) {
xcfa.procedures.forEach {
try {
val chcFile = File(resultFolder, "xcfa-${it.name}.smt2")
chcFile.writeText(it.toSMT2CHC())
} catch (e: Exception) {
logger.write(INFO, "Could not write CHC file: " + e.stackTraceToString())
}
}
}
if (!config.outputConfig.xcfaOutputConfig.disable) {
val xcfaDotFile = File(resultFolder, "xcfa.dot")
xcfaDotFile.writeText(xcfa.toDot())
val xcfaJsonFile = File(resultFolder, "xcfa.json")
val uglyJson = getGson(xcfa).toJson(xcfa)
val create = GsonBuilder().setPrettyPrinting().create()
xcfaJsonFile.writeText(create.toJson(JsonParser.parseString(uglyJson)))
}
if (!config.outputConfig.cOutputConfig.disable) {
try {
val xcfaCFile = File(resultFolder, "xcfa.c")
xcfaCFile.writeText(
xcfa.toC(
parseContext,
config.outputConfig.cOutputConfig.useArr,
config.outputConfig.cOutputConfig.useExArr,
config.outputConfig.cOutputConfig.useRange,
)
)
} catch (e: Throwable) {
logger.write(Logger.Level.VERBOSE, "Could not emit C file\n")
}
}
} catch (e: Throwable) {
logger.write(Logger.Level.INFO, "Could not output files: ${e.stackTraceToString()}\n")
}
}
}
private fun postVerificationLogging(
safetyResult: SafetyResult<*, *>,
mcm: MCM,
parseContext: ParseContext,
config: XcfaConfig<*, *>,
logger: Logger,
uniqueLogger: Logger,
) {
if (config.outputConfig.enableOutput) {
try {
// we only want to log the files if the current configuration is not --in-process or portfolio
if (config.backendConfig.inProcess || config.backendConfig.backend == Backend.PORTFOLIO) {
return
}
val resultFolder = config.outputConfig.resultFolder
resultFolder.mkdirs()
logger.write(
Logger.Level.INFO,
"Writing post-verification artifacts to directory ${resultFolder.absolutePath}\n",
)
// TODO eliminate the need for the instanceof check
if (
!config.outputConfig.argConfig.disable && safetyResult.proof is ARG
) {
val argFile = File(resultFolder, "arg-${safetyResult.isSafe}.dot")
val g: Graph =
ArgVisualizer.getDefault().visualize(safetyResult.proof as ARG)
argFile.writeText(GraphvizWriter.getInstance().writeString(g))
}
if (!config.outputConfig.witnessConfig.disable) {
if (
safetyResult.isUnsafe &&
safetyResult.asUnsafe().cex != null &&
!config.outputConfig.witnessConfig.svcomp
) {
val concrTrace: Trace, XcfaAction> =
XcfaTraceConcretizer.concretize(
safetyResult.asUnsafe().cex as Trace>, XcfaAction>,
getSolver(
config.outputConfig.witnessConfig.concretizerSolver,
config.outputConfig.witnessConfig.validateConcretizerSolver,
),
parseContext,
)
val traceFile = File(resultFolder, "trace.dot")
val traceG: Graph = TraceVisualizer.getDefault().visualize(concrTrace)
traceFile.writeText(GraphvizWriter.getInstance().writeString(traceG))
val sequenceFile = File(resultFolder, "trace.plantuml")
writeSequenceTrace(
sequenceFile,
safetyResult.asUnsafe().cex as Trace, XcfaAction>,
) { (_, act) ->
act.label.getFlatLabels().map(XcfaLabel::toString)
}
val optSequenceFile = File(resultFolder, "trace-optimized.plantuml")
writeSequenceTrace(optSequenceFile, concrTrace) { (_, act) ->
act.label.getFlatLabels().map(XcfaLabel::toString)
}
val cSequenceFile = File(resultFolder, "trace-c.plantuml")
writeSequenceTrace(cSequenceFile, concrTrace) { (state, act) ->
val proc = state.processes[act.pid]
val loc = proc?.locs?.peek()
(loc?.metadata as? CMetaData)?.sourceText?.split("\n") ?: listOf("")
}
}
val witnessFile = File(resultFolder, "witness.graphml")
GraphmlWitnessWriter()
.writeWitness(
safetyResult,
config.inputConfig.input!!,
getSolver(
config.outputConfig.witnessConfig.concretizerSolver,
config.outputConfig.witnessConfig.validateConcretizerSolver,
),
parseContext,
witnessFile,
)
val yamlWitnessFile = File(resultFolder, "witness.yml")
YmlWitnessWriter()
.writeWitness(
safetyResult,
config.inputConfig.input!!,
config.inputConfig.property,
(config.frontendConfig.specConfig as? CFrontendConfig)?.architecture,
getSolver(
config.outputConfig.witnessConfig.concretizerSolver,
config.outputConfig.witnessConfig.validateConcretizerSolver,
),
parseContext,
yamlWitnessFile,
)
}
} catch (e: Throwable) {
logger.write(Logger.Level.INFO, "Could not output files: ${e.stackTraceToString()}\n")
}
}
}
private fun writeSequenceTrace(
sequenceFile: File,
trace: Trace, XcfaAction>,
printer: (Pair, XcfaAction>) -> List,
) {
sequenceFile.writeText("@startuml\n")
var maxWidth = 0
trace.actions.forEachIndexed { i, it ->
val stateBefore = trace.states[i]
sequenceFile.appendText("hnote over ${it.pid}\n")
val labelStrings = printer(Pair(stateBefore, it))
if (maxWidth < (labelStrings.maxOfOrNull { it.length } ?: 0)) {
maxWidth = labelStrings.maxOfOrNull { it.length } ?: 0
}
sequenceFile.appendText("${labelStrings.joinToString("\n")}\n")
sequenceFile.appendText("endhnote\n")
}
trace.actions
.map { it.pid }
.distinct()
.reduce { acc, current ->
sequenceFile.appendText("$acc --> $current: \"${" ".repeat(maxWidth)}\"\n")
current
}
sequenceFile.appendText("@enduml\n")
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy