Skip to content

Commit c4459c1

Browse files
18.12.25 Release
18.12.25 Release fcf493b367d992fa19a0aeaa1971a98f61e531d2
2 parents 5e8518f + 71c3fda commit c4459c1

File tree

16 files changed

+129
-582
lines changed

16 files changed

+129
-582
lines changed

lib/Shared/src/main/kotlin/config/Config.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3005,6 +3005,24 @@ object Config {
30053005
}"
30063006
}
30073007

3008+
val retryOnLeafTimeout: ConfigType.BooleanCmdLine = object : ConfigType.BooleanCmdLine(
3009+
false,
3010+
Option(
3011+
"smt_retryOnLeafTimeout",
3012+
true,
3013+
"Reruns a timeouting leaf with a broader portfolio of solvers."
3014+
)
3015+
), TransformationAgnosticConfig {}
3016+
3017+
val retryOnLeafTimeoutSolvers: ConfigType.SolverProgramCmdLine = object : ConfigType.SolverProgramCmdLine(
3018+
AllCommonAvailableSolversWithClOptions.toTypedArray(),
3019+
Option(
3020+
"smt_retryOnLeafTimeoutSolvers",
3021+
true,
3022+
"Solvers used for a rerun on timeouting leafs."
3023+
)
3024+
), TransformationAgnosticConfig {}
3025+
30083026
val NumOfUnsatCores: ConfigType.IntCmdLine = object : ConfigType.IntCmdLine(
30093027
1,
30103028
Option(

lib/Shared/src/main/kotlin/spec/cvlast/CVLRuleAndMethodChoiceValidation.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,27 @@ fun validateSimpleMethodChoices(knownFunctions: Set<String>): VoidResult<CVLErro
109109
* them, as this is a probable user input mistake.
110110
*/
111111
fun validateRuleChoices(cvlAst: CVLAst) : VoidResult<CVLError> {
112-
val allRules = cvlAst.rules.map { it.declarationId }.toSet() + cvlAst.invs.map { it.id }.toSet() +
112+
val allRules = cvlAst.rules.mapToSet { it.declarationId } + cvlAst.invs.mapToSet { it.id } +
113113
cvlAst.useDeclarations.builtInRulesInUse.map { it.id }
114-
return validateRuleChoices(allRules)
114+
val validationResult = validateRuleChoices(allRules)
115+
if(!validationResult.isResult()){
116+
return validationResult
117+
}
118+
119+
val invariantsNotInMainSpecFile = cvlAst.invs.filterNot { it.needsVerification }.mapToSet { it.id }
120+
/**
121+
* With [config.Config.getNonPatternRuleChoices] we only match against explicit includes of invariants,
122+
* once that do not include the wildcard (*). I.e. if one selects an invariant via --rule my_invariant*, and the main
123+
* spec file contains a my_invariant_foo and an imported spec file contains my_invariant_bar, no typechecking error will occur,
124+
* but my_invariant_bar is excluded.
125+
*/
126+
val invariantsChosenFromImportedSpecs = invariantsNotInMainSpecFile.filter { it in Config.getNonPatternRuleChoices().orEmpty() }
127+
return if(invariantsChosenFromImportedSpecs.isNotEmpty()) {
128+
CVLError.General(Range.Empty(), "A rule filter is applied (${Config.getNonPatternRuleChoices()}) and matches an invariant from an imported spec that is not used in the main spec. " +
129+
"To include the invariant in verification, try adding the following statement(s) to your main spec file: \n${invariantsChosenFromImportedSpecs.joinToString("\n") { "use invariant $it;" }}").asError()
130+
} else {
131+
ok
132+
}
115133
}
116134

117135
fun validateRuleChoices(allRules: Set<String>) : VoidResult<CVLError> {

scripts/CertoraProver/certoraApp.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
class CertoraApp(ABC):
2525
attr_class: Type[AttrUtil.Attributes] = Attrs.EvmProverAttributes
26+
ecosystem: str = "EVM"
27+
product: str = "PROVER"
2628

2729
class EvmAppClass(CertoraApp):
2830
pass
@@ -38,15 +40,21 @@ class MoveAppClass(CertoraApp):
3840

3941
class SolanaApp(RustAppClass):
4042
attr_class = Attrs.SolanaProverAttributes
43+
ecosystem: str = "SOLANA"
44+
4145

4246
class SorobanApp(RustAppClass):
4347
attr_class = Attrs.SorobanProverAttributes
48+
ecosystem: str = "SOROBAN"
4449

4550
class RangerApp(EvmAppClass):
4651
attr_class = Attrs.RangerAttributes
52+
product = "RANGER"
4753

4854
class ConcordApp(EvmAppClass):
4955
attr_class = Attrs.ConcordAttributes
56+
product = "CONCORD"
5057

5158
class SuiApp(MoveAppClass):
5259
attr_class = Attrs.SuiProverAttributes
60+
ecosystem: str = "SUI"

scripts/CertoraProver/certoraCloudIO.py

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,6 @@
6969

7070
FEATURES_REPORT_FILE = Path("featuresReport.json")
7171

72-
class EcoEnum(Util.NoValEnum):
73-
EVM = Util.auto()
74-
SOROBAN = Util.auto()
75-
SOLANA = Util.auto()
76-
SUI = Util.auto()
77-
78-
class ProductEnum(Util.NoValEnum):
79-
PROVER = Util.auto()
80-
RANGER = Util.auto()
81-
CONCORD = Util.auto()
82-
83-
8472
class TimeError(Exception):
8573
"""A custom exception used to report on time elapsed errors"""
8674

@@ -648,21 +636,8 @@ def paths_in_source_dir(attr_values: List[str]) -> str:
648636

649637
auth_data["useLatestFe"] = self.context.fe_version == str(Util.FeValue.LATEST)
650638

651-
if Attrs.is_solana_app():
652-
auth_data["ecosystem"] = EcoEnum.SOLANA.name
653-
elif Attrs.is_soroban_app():
654-
auth_data["ecosystem"] = EcoEnum.SOROBAN.name
655-
elif Attrs.is_sui_app():
656-
auth_data["ecosystem"] = EcoEnum.SUI.name
657-
else:
658-
auth_data["ecosystem"] = EcoEnum.EVM.name
659-
660-
if Attrs.is_ranger_app():
661-
auth_data["product"] = ProductEnum.RANGER.name
662-
elif Attrs.is_concord_app():
663-
auth_data["product"] = ProductEnum.CONCORD.name
664-
else:
665-
auth_data["product"] = ProductEnum.PROVER.name
639+
auth_data["ecosystem"] = self.context.app.ecosystem
640+
auth_data["product"] = self.context.app.product
666641

667642
if Attrs.is_evm_app() and self.context.cache is not None:
668643
auth_data["toolSceneCacheKey"] = self.context.cache

scripts/CertoraProver/certoraCollectRunMetadata.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class RunMetaData:
7474
conf_path -- the relative path form the cwd_relative to the configuration file
7575
group_id -- optional identifier for grouping this run
7676
java_version -- version of Java used during the run, if available
77+
ecosystem -- the ecosystem of the current run (EVM, SOLANA, SOROBAN, SUI, etc)
7778
default_solc_version -- version of default solc version on current machine, if available
7879
python_version -- version of Python running the process
7980
certora_ci_client -- name of the CI client if available, derived from environment
@@ -84,7 +85,7 @@ class RunMetaData:
8485
"""
8586
def __init__(self, raw_args: List[str], conf: Dict[str, Any], origin: str, revision: str,
8687
branch: str, cwd_relative: Path, dirty: bool, main_spec: Optional[str],
87-
conf_path: Optional[Path], group_id: Optional[str], java_version: str):
88+
conf_path: Optional[Path], group_id: Optional[str], java_version: str, ecosystem: str):
8889
self.raw_args = raw_args
8990
self.conf = conf
9091
self.origin = origin
@@ -97,6 +98,7 @@ def __init__(self, raw_args: List[str], conf: Dict[str, Any], origin: str, revis
9798
self.group_id = group_id
9899
self.python_version = ".".join(str(x) for x in sys.version_info[:3])
99100
self.java_version = java_version
101+
self.ecosystem = ecosystem
100102
self.default_solc_version = get_solc_version(self.conf)
101103
self.certora_ci_client = Utils.get_certora_ci_name()
102104
self.timestamp = str(datetime.now(timezone.utc).timestamp())
@@ -119,6 +121,7 @@ def __repr__(self) -> str:
119121
f" group_id: {self.group_id}\n"
120122
f" python_version: {self.python_version}\n"
121123
f" java_version: {self.java_version}\n"
124+
f" ecosystem: {self.ecosystem}\n"
122125
f" default_solc_version: {self.default_solc_version}\n"
123126
f" CertoraCI client: {self.certora_ci_client}\n"
124127
f" jar_flag_info: {self.jar_flag_info}\n"
@@ -198,7 +201,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
198201
main_spec=None,
199202
conf_path=None,
200203
group_id=None,
201-
java_version=context.java_version)
204+
java_version=context.java_version,
205+
ecosystem=context.app.ecosystem)
202206

203207
# collect information about current git snapshot
204208
cwd_abs = wd.absolute()
@@ -227,7 +231,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
227231
main_spec=get_main_spec(context),
228232
conf_path=conf_path,
229233
group_id=context.group_id,
230-
java_version=context.java_version)
234+
java_version=context.java_version,
235+
ecosystem=context.app.ecosystem)
231236

232237
try:
233238
sha_out = subprocess.run(['git', 'rev-parse', 'HEAD'], cwd=wd,
@@ -263,7 +268,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
263268
main_spec=get_main_spec(context),
264269
conf_path=conf_path,
265270
group_id=context.group_id,
266-
java_version=context.java_version)
271+
java_version=context.java_version,
272+
ecosystem=context.app.ecosystem)
267273

268274
metadata_logger.debug(f' collected data:\n{str(data)}')
269275

@@ -282,7 +288,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
282288
main_spec=get_main_spec(context),
283289
conf_path=conf_path,
284290
group_id=context.group_id,
285-
java_version=context.java_version)
291+
java_version=context.java_version,
292+
ecosystem=context.app.ecosystem)
286293

287294

288295
def get_solc_version(conf: Dict[str, Any]) -> Optional[str]:

src/main/kotlin/verifier/LExpVCStats.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ data class ResplitStatistic(
164164
@Serializable
165165
data class SplitStatistic(
166166
/** The location of the split in the split tree. -1 indicates a failed split */
167-
val address: List<Int>,
167+
val address: String,
168168
/** splitName exactly as it is in Split<CoreTACProgram>.name. Also referred to as vcName */
169169
val splitName: String,
170170
val finalResult: String? = null,

src/main/kotlin/verifier/TACVerifier.kt

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import smtlibutils.statistics.LearnedClausesFilterStatistics
5050
import smtlibutils.statistics.PreExecutionStatistics
5151
import solver.ConfigStatistics
5252
import solver.SMTCounterexampleModel
53+
import solver.SolverChoice
5354
import solver.SolverResult
5455
import spec.cvlast.CVLCmd
5556
import spec.cvlast.CVLExpDeclaredSymbolsCollector
@@ -184,7 +185,7 @@ class TACVerifier private constructor(
184185
) {
185186
updateSplitStatistics(
186187
SplitStatistic(
187-
address = address.asIntList,
188+
address = address.name(),
188189
vcFeatures = listOf(),
189190
solvingEvent = ResplitStatistic(resplitTime),
190191
timeToSolveSinceJarStart = TimeSinceStart(),
@@ -209,7 +210,7 @@ class TACVerifier private constructor(
209210

210211
val splitStats = SplitStatistic(
211212
splitName = vcName,
212-
address = address.asIntList,
213+
address = address.name(),
213214
finalResult = LExpVCStatsLogger.getResultStr(checkerResult.satResult),
214215
timeout = timeout,
215216
smtSolvingWallTime = smtSolvingWallTime,
@@ -623,7 +624,7 @@ class TACVerifier private constructor(
623624
vcGenStopWatch = MilliTimeElapserStopWatch(timeStatsRecorder, vcGenTimeTag),
624625
solverStopWatch,
625626
autoConfig,
626-
localSettings
627+
((subProblem as? SplitTree.NodeWithInfo<*>)?.info as? LocalSettings) ?: localSettings
627628
)
628629
val finalResult = verifierResult.finalResult
629630
subProblem.setRunInfo(finalResult, verifierResult.elapsedTimeSeconds.seconds, timeout)
@@ -671,6 +672,24 @@ class TACVerifier private constructor(
671672
problemQueue += subProblem.split(subProblemTAC)
672673
}
673674
continue
675+
} else if (Config.Smt.retryOnLeafTimeout.get() && (subProblem.address !is SplitAddress.Rerun)) {
676+
val sub = LazySubProgram(
677+
splitTree,
678+
SplitAddress.Rerun(subProblem.lazySub.address)
679+
)
680+
val alreadyTried = smtFormulaCheckerResult.subResultsFlattened
681+
.mapNotNull { it.solverInstanceInfo?.solverConfig }
682+
val settings = localSettings.copy(
683+
solverProgramChoice = SolverChoice(
684+
Config.Smt.retryOnLeafTimeoutSolvers.get()
685+
.filter { it.solverInfo.getOptionForRandomSeed(1).isNotEmpty() }
686+
.flatMap { listOf(1, 2, 3).map { seed -> it.copy(randomSeed = seed) } }
687+
.filter { it !in alreadyTried }
688+
.map { it.copy(canBeSkipped = { _,_ -> false }) }
689+
)
690+
)
691+
logger.info { "Timed out on leaf ${sub.name}, rerunning with random seeds" }
692+
problemQueue.add(splitTree.NodeWithInfo(sub, subProblem.sideScore, settings))
674693
} else {
675694
// we dump timeouting leaves.
676695
smtFormulaCheckerResult.let { last ->

src/main/kotlin/verifier/autoconfig/AutoConfigManager.kt

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ class AutoConfigManager(val ruleName: String) : Closeable {
7575
* their parent is also UNSAT. The propagation is recursive.
7676
*/
7777
private fun propagateUNSAT(address: SplitAddress): Unit = synchronized(splitStatAndSolverConfigLock) {
78-
if (address.asIntList.toString() !in stats.splitStatistics) {
78+
if (address.name() !in stats.splitStatistics) {
7979
warn("The split with the address $address should be first added to ${::stats.name} before we propagate its unsat result")
8080
return
8181
}
82-
if (!stats.splitStatistics[address.asIntList.toString()]!!.isEventuallyUNSAT()) {
82+
if (!stats.splitStatistics[address.name()]!!.isEventuallyUNSAT()) {
8383
warn("Trying to propagate an unregistered UNSAT result of the split $address")
8484
return
8585
}
@@ -90,10 +90,10 @@ class AutoConfigManager(val ruleName: String) : Closeable {
9090
if (address !is SplitAddress.Block) {
9191
return
9292
}
93-
val siblingAddressStr = address.sibling().toString()
93+
val siblingAddressStr = address.sibling().name()
9494
if (stats.splitStatistics[siblingAddressStr]?.isEventuallyUNSAT() == true) {
9595
val parentAddress = address.parent
96-
val parentAddressStr = parentAddress.toString()
96+
val parentAddressStr = parentAddress?.name() ?: "null"
9797
if (parentAddressStr !in stats.splitStatistics) {
9898
warn("The parent address $parentAddressStr is not registered in ${::stats.name}")
9999
return
@@ -110,12 +110,12 @@ class AutoConfigManager(val ruleName: String) : Closeable {
110110
* Sets the [finalResult] and [solvers] of the stats of [address] based on the stats of [matchingUnsat].
111111
*/
112112
private fun markMatchingUnsat(address: SplitAddress, matchingUnsat: BasicSplitStatistics) = synchronized(splitStatAndSolverConfigLock) {
113-
if(address.asIntList.toString() !in stats.splitStatistics) {
113+
if(address.name() !in stats.splitStatistics) {
114114
warn("Trying to mark an address UNSAT that is not in ${::stats.name}: $address")
115115
return
116116
}
117-
stats.splitStatistics[address.asIntList.toString()] =
118-
stats.splitStatistics[address.asIntList.toString()]!!.copy(
117+
stats.splitStatistics[address.name()] =
118+
stats.splitStatistics[address.name()]!!.copy(
119119
finalResult = matchingUnsat.finalResult,
120120
solvers = matchingUnsat.solvers
121121
)
@@ -126,27 +126,29 @@ class AutoConfigManager(val ruleName: String) : Closeable {
126126
* it gets computed and stored in [stats].
127127
*/
128128
private fun getDigest(address: SplitAddress, prog: CoreTACProgram): String = synchronized(splitStatAndSolverConfigLock) {
129-
require(address.asIntList.toString() in stats.splitStatistics) {
130-
"Trying to store a digest for a split ${address.asIntList} that is not registered yet"
129+
val addressName = address.name()
130+
require(addressName in stats.splitStatistics) {
131+
"Trying to store a digest for a split ${addressName} that is not registered yet"
131132
}
132-
if (stats.splitStatistics[address.asIntList.toString()]!!.tacStats.digest == null) {
133+
if (stats.splitStatistics[addressName]!!.tacStats.digest == null) {
133134
val digest = BasicTACStatistics.computeDigest(prog)
134-
stats.splitStatistics[address.asIntList.toString()] =
135-
stats.splitStatistics[address.asIntList.toString()]!!.let {
135+
stats.splitStatistics[addressName] =
136+
stats.splitStatistics[addressName]!!.let {
136137
it.copy(tacStats = it.tacStats.copy(digest = digest))
137138
}
138139
}
139-
return stats.splitStatistics[address.asIntList.toString()]!!.tacStats.digest!!
140+
return stats.splitStatistics[addressName]!!.tacStats.digest!!
140141
}
141142

142143
/**
143144
* Register the given [subProblem] in [stats] (storing just address, split name, and basic TAC statistics).
144145
*/
145146
fun registerSplit(address: SplitAddress, name: String, subProblemTAC: CoreTACProgram) = synchronized(splitStatAndSolverConfigLock) {
146-
if (address.asIntList.toString() !in stats.splitStatistics.keys) {
147-
info("registering split ${address.asIntList}")
148-
stats.splitStatistics[address.asIntList.toString()] = BasicSplitStatistics(
149-
address = address.asIntList.toString(),
147+
val addressName = address.name()
148+
if (addressName !in stats.splitStatistics.keys) {
149+
info("registering split ${address.name()}")
150+
stats.splitStatistics[addressName] = BasicSplitStatistics(
151+
address = addressName,
150152
splitName = name,
151153
tacStats = BasicTACStatistics.fromCoreTACProgram(subProblemTAC, Config.AutoconfigUseDigests.get()),
152154
)
@@ -164,7 +166,7 @@ class AutoConfigManager(val ruleName: String) : Closeable {
164166
timeoutForSubProblem: Duration,
165167
solvingDuration: Duration,
166168
) = synchronized(splitStatAndSolverConfigLock) {
167-
val address = splitAddress.asIntList.toString()
169+
val address = splitAddress.name()
168170
info("registering ${verifierResult.finalResult} result for split $address.")
169171
if(address !in stats.splitStatistics) {
170172
warn("Trying to register results for an unregistered split $address.")
@@ -262,7 +264,7 @@ class AutoConfigManager(val ruleName: String) : Closeable {
262264
val loadedMatches = loadedStats?.splitStatistics?.values?.filter { tacStats.match(it.tacStats) } ?: listOf()
263265
//Splits we have already solved in this CVT run and which are matching the current split.
264266
val newMatches: List<BasicSplitStatistics> = if (learnFromCurrent) {
265-
val splitAddressStr = splitAddress.asIntList.toString()
267+
val splitAddressStr = splitAddress.name()
266268
synchronized(splitStatAndSolverConfigLock) {
267269
stats.splitStatistics.values.filter { tacStats.match(it.tacStats) && it.address != splitAddressStr }
268270
}

0 commit comments

Comments
 (0)