Skip to content

Commit 5e8518f

Browse files
14.12.25 Release
14.12.25 Release 75d6c9b97dff2712b7bce2b623446fe2d2df7832
2 parents 6c493e0 + b24db83 commit 5e8518f

File tree

183 files changed

+4690
-4457
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

183 files changed

+4690
-4457
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "scripts/graphcore"]
2+
path = scripts/graphcore
3+
url = git@github.com:Certora/graphcore.git

Public/CITests/testCertoraClient.py

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,6 @@ def test_valid_runs(self) -> None:
230230
run_flags=[f"{_p('A.sol')}:B", '--verify', f"B:{_p('spec1.spec')}"])
231231
suite.expect_success(description='multiple files single assert',
232232
run_flags=[_p('A.sol'), _p('B.sol'), '--verify', f"A:{_p('spec1.spec')}"])
233-
suite.expect_success(description='file is repeated',
234-
run_flags=[f"{_p('A.sol')}:B", f"{_p('A.sol')}:B", '--verify', f"B:{_p('spec1.spec')}"])
235233
suite.expect_success(description='2 contracts in a single file',
236234
run_flags=[f"{_p('A.sol')}:B", _p('A.sol'), '--verify', f"B:{_p('spec1.spec')}"])
237235
suite.expect_success(description='--verify with spec file',
@@ -355,8 +353,6 @@ def test_valid_runs(self) -> None:
355353
suite.expect_success(description='valid --struct_link multiple entries',
356354
run_flags=[_p('A.sol'), _p('B.sol'), '--verify', f"A:{_p('spec1.spec')}", '--struct_link',
357355
'A:1=B', 'A:2=B', 'A:1009=A'])
358-
suite.expect_success(description='valid --struct_link duplicate entries',
359-
run_flags=[_p('A.sol'), '--verify', f"A:{_p('spec1.spec')}", '--struct_link', 'A:0XC=A', 'A:0XC=A'])
360356
suite.expect_success(description='valid --build_only',
361357
run_flags=[_p('A.sol'), '--verify', f"A:{_p('spec1.spec')}", '--build_only'])
362358
suite.expect_success(description='valid --build_only duplicate entries',
@@ -548,6 +544,10 @@ def test_invalid_runs(self) -> None:
548544
run_flags=[_p('A.sol'), '--verify', f"A:{_p('spec1.spec')}", '--compilation_steps_only',
549545
'--build_only'],
550546
expected="cannot use both 'compilation_steps_only' and 'build_only'")
547+
suite.expect_failure(description='valid --struct_link duplicate entries',
548+
run_flags=[_p('A.sol'), '--verify', f"A:{_p('spec1.spec')}",
549+
'--struct_link', 'A:0XC=A', 'A:0XC=A'],
550+
expected="The value of attribute 'struct_link' contains duplicate entries")
551551
suite.expect_failure(description="illegal struct_link",
552552
run_flags=[_p('tac_file.conf'), '--struct_link', 'tac_file:0=tac_file'],
553553
expected="'struct_link' argument tac_file:0=tac_file is illegal")
@@ -585,6 +585,9 @@ def test_invalid_runs(self) -> None:
585585
suite.expect_failure(description="other files with conf file",
586586
run_flags=[_p('tac_file.conf'), _p('empty.tac')],
587587
expected="No other files are allowed when using a config file")
588+
suite.expect_failure(description='file is repeated',
589+
run_flags=[f"{_p('A.sol')}:B", f"{_p('A.sol')}:B", '--verify', f"B:{_p('spec1.spec')}"],
590+
expected="The value of attribute 'files' contains duplicate entries")
588591
suite.expect_failure(description="illegal contract in verify",
589592
run_flags=[_p('empty.tac'), '--verify', f"A:{_p('spec1.spec')}"],
590593
expected="'verify' argument, A, doesn't match any contract name")
@@ -817,7 +820,7 @@ def test_package_file(self) -> None:
817820
def check_run(expect: List[str]) -> None:
818821
packages_attr = getattr(result, 'packages', None)
819822
assert packages_attr, f"{description}: package is None"
820-
got = sorted([dep.split('=')[0] for dep in packages_attr])
823+
got = sorted([dep.split('=')[0].rstrip('/') for dep in packages_attr])
821824
assert expect == got, f"{description}. Expected: {expect}. Got: {got}"
822825

823826
suite = TestUtil.ProverTestSuite(conf_file_template=_p('mutation_conf_top_level.conf'),
@@ -830,37 +833,44 @@ def check_run(expect: List[str]) -> None:
830833
assert not packages, f"expected 'packages' to be None. Got: {result.packages}"
831834

832835
remapping = "a=lib\nb=lib"
833-
with open(Util.REMAPPINGS_FILE, "w") as file:
834-
file.write(remapping)
835-
description = f"running with {Util.REMAPPINGS_FILE}"
836-
result = suite.expect_checkpoint(description=description)
837-
check_run(['a', 'b'])
838-
839-
remapping = '{"dependencies": {"c": "^3.4.1"},"devDependencies": {"d": "^5.0.8"}}'
840-
with open(Util.PACKAGE_FILE, "w") as file:
841-
file.write(remapping)
842-
description = f"running with {Util.REMAPPINGS_FILE} and {Util.PACKAGE_FILE}"
843-
result = suite.expect_checkpoint(description=description)
844-
check_run(['a', 'b', 'c', 'd'])
836+
try:
837+
with open(Util.REMAPPINGS_FILE, "w") as file:
838+
file.write(remapping)
839+
description = f"running with {Util.REMAPPINGS_FILE}"
840+
result = suite.expect_checkpoint(description=description)
841+
check_run(['a', 'b'])
842+
843+
remapping = '{"dependencies": {"c": "^3.4.1"},"devDependencies": {"d": "^5.0.8"}}'
844+
with open(Util.PACKAGE_FILE, "w") as file:
845+
file.write(remapping)
846+
description = f"running with {Util.REMAPPINGS_FILE} and {Util.PACKAGE_FILE}"
847+
result = suite.expect_checkpoint(description=description)
848+
check_run(['a', 'b', 'c', 'd'])
849+
finally:
850+
Path(Util.REMAPPINGS_FILE).unlink(missing_ok=True)
845851

846852
description = "--packages - ignore files"
847853
result = suite.expect_checkpoint(description=description, run_flags=['--packages', 'd=lib'])
848854
check_run(['d'])
849855

850856
# duplications
851857
remapping = "a=lib\na=lib"
852-
with open(Util.REMAPPINGS_FILE, "w") as file:
853-
file.write(remapping)
854-
description = f"identical duplicates in {Util.REMAPPINGS_FILE}"
855-
suite.expect_success(description=description)
856-
Path(Util.REMAPPINGS_FILE).unlink(missing_ok=True)
858+
try:
859+
with open(Util.REMAPPINGS_FILE, "w") as file:
860+
file.write(remapping)
861+
description = f"identical duplicates in {Util.REMAPPINGS_FILE}"
862+
suite.expect_success(description=description)
863+
finally:
864+
Path(Util.REMAPPINGS_FILE).unlink(missing_ok=True)
857865

858866
remapping = "a=lib\na=lib2"
859-
with open(Util.REMAPPINGS_FILE, "w") as file:
860-
file.write(remapping)
861-
description = f"non-identical duplicates in {Util.REMAPPINGS_FILE}"
862-
suite.expect_failure(description=description, expected="Conflicting values in remappings.txt for key 'a'")
863-
Path(Util.REMAPPINGS_FILE).unlink(missing_ok=True)
867+
try:
868+
with open(Util.REMAPPINGS_FILE, "w") as file:
869+
file.write(remapping)
870+
description = f"non-identical duplicates in {Util.REMAPPINGS_FILE}"
871+
suite.expect_failure(description=description, expected="Conflicting values in remappings.txt for key 'a'")
872+
finally:
873+
Path(Util.REMAPPINGS_FILE).unlink(missing_ok=True)
864874

865875
remapping = '{"dependencies": {"c": "^3.4.1"},"devDependencies": {"c": "^5.0.8"}}'
866876
with open(Util.PACKAGE_FILE, "w") as file:
@@ -870,24 +880,28 @@ def check_run(expect: List[str]) -> None:
870880
Path(Util.PACKAGE_FILE).unlink(missing_ok=True)
871881

872882
remapping = "a=lib\nb=lib"
873-
with open(Util.REMAPPINGS_FILE, "w") as file:
874-
file.write(remapping)
875-
remapping = '{"dependencies": {"b": "^3.4.1"},"devDependencies": {"c": "^5.0.8"}}'
876-
with open(Util.PACKAGE_FILE, "w") as file:
877-
file.write(remapping)
878-
description = f"duplicates in {Util.REMAPPINGS_FILE} and Util.PACKAGE_FILE"
879-
suite.expect_failure(description=description, expected="package.json and remappings.txt include duplicated")
880-
Path(Util.REMAPPINGS_FILE).unlink(missing_ok=True)
881-
Path(Util.PACKAGE_FILE).unlink(missing_ok=True)
883+
try:
884+
with open(Util.REMAPPINGS_FILE, "w") as file:
885+
file.write(remapping)
886+
remapping = '{"dependencies": {"b": "^3.4.1"},"devDependencies": {"c": "^5.0.8"}}'
887+
with open(Util.PACKAGE_FILE, "w") as file:
888+
file.write(remapping)
889+
description = f"duplicates in {Util.REMAPPINGS_FILE} and Util.PACKAGE_FILE"
890+
suite.expect_failure(description=description, expected="package.json and remappings.txt include duplicated")
891+
finally:
892+
Path(Util.REMAPPINGS_FILE).unlink(missing_ok=True)
893+
Path(Util.PACKAGE_FILE).unlink(missing_ok=True)
882894

883895

884896
remapping = "a=lib\nb=lib=lib2"
885-
with open(Util.REMAPPINGS_FILE, "w") as file:
886-
file.write(remapping)
897+
try:
898+
with open(Util.REMAPPINGS_FILE, "w") as file:
899+
file.write(remapping)
887900

888-
description = f"remappings.txt with bad format"
889-
suite.expect_failure(description=description, expected="Invalid remapping in remappings.txt")
890-
Path(Util.REMAPPINGS_FILE).unlink(missing_ok=True)
901+
description = f"remappings.txt with bad format"
902+
suite.expect_failure(description=description, expected="Invalid remapping in remappings.txt")
903+
finally:
904+
Path(Util.REMAPPINGS_FILE).unlink(missing_ok=True)
891905

892906
def test_solc_args(self) -> None:
893907

Public/TestEVM/CVLCompilation/Preserved/C.spec

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ invariant DInitializedBeforeC()
3131
currentContract.timestamp > 0 => d.initialized_at() > 0
3232
{
3333
preserved constructor() {
34-
// TODO(CERT-9671) This is a SANITY_FAIL for now because D's storage is zeroed. It will become a success.
3534
require d.initialized_at() != 0, "assume D was initialized already when calling Cs constructor";
3635
}
3736
preserved foo(uint i) with (env e) {

Public/TestEVM/CVLCompilation/Preserved/expectedDefault.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
},
88
"holdsWithParamRestrictions": "FAIL",
99
"holdsWithEnvRestrictions": "SUCCESS",
10-
"DInitializedBeforeC": "SANITY_FAIL"
10+
"DInitializedBeforeC": "SUCCESS"
1111
}
1212
}

Public/TestEVM/Fallback/FallbackAndReceive.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ contract C is FallbackAndReceive {
4848

4949
contract Caller {
5050
FallbackAndReceive a;
51+
constructor(FallbackAndReceive _a) {
52+
a = _a;
53+
require(a.n() == 0 &&
54+
a.last_called_fallback() == false &&
55+
a.last_called_receive() == false &&
56+
a.last_called_nameclash() == false);
57+
}
5158
function noSuchFun() external {}
5259
function should_call_fallback() external {
5360
address(a).call(abi.encodeWithSignature("noSuchFun()"));

lib/GeneralUtils/src/main/kotlin/utils/Either.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,9 @@ fun <U, T: U, R> Either<T, R>.leftOrElse(default: (R) -> U): U = when (this) {
137137
is Either.Left -> this.d
138138
is Either.Right -> default(this.d)
139139
}
140+
141+
/* special case for when both "sides" are the same type */
142+
fun <T> Either<T, T>.leftOrRight() = when (this) {
143+
is Either.Left -> this.d
144+
is Either.Right -> this.d
145+
}

lib/Shared/src/main/kotlin/com/certora/certoraprover/cvl/formatter/Comments.kt

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@ package com.certora.certoraprover.cvl.formatter
2020
import com.certora.certoraprover.cvl.formatter.util.TerminalId
2121
import com.certora.certoraprover.cvl.formatter.util.ensure
2222
import com.certora.certoraprover.cvl.formatter.util.foundOrNull
23-
import com.certora.certoraprover.cvl.formatter.util.invariantBroken
2423
import com.certora.certoraprover.cvl.sym
25-
import datastructures.NonEmptyList
2624
import datastructures.stdcollections.*
27-
import datastructures.toNonEmptyList
2825
import utils.Range
2926
import utils.SourcePosition
3027

@@ -47,8 +44,8 @@ internal data class Comments(
4744
}
4845

4946
fun bindToToken(prevEmitted: Entry.Emitted?, nextEmitted: Entry.Emitted): Binding {
50-
fun beforeNext() = Binding(Association.Before, nextEmitted, this)
51-
fun afterPrev() = Binding(Association.After, prevEmitted!!, this)
47+
fun beforeNext() = Binding(Association.Before, prev = prevEmitted, next = nextEmitted, this)
48+
fun afterPrev() = Binding(Association.After, prev = prevEmitted, next = nextEmitted, this)
5249

5350
return when {
5451
prevEmitted == null -> beforeNext()
@@ -104,37 +101,89 @@ internal fun Comments(buffer: List<Entry.NotEmitted>): Comments {
104101

105102
internal enum class Association { Before, After }
106103

107-
internal data class Binding(val association: Association, val token: Entry.Emitted, val comments: Comments)
108-
109-
private class CommentRunBuffer(private val buffer: MutableList<Entry.NotEmitted>, var inProgress: Boolean) {
110-
fun add(entry: Entry.NotEmitted) {
111-
this.buffer.add(entry)
112-
}
104+
internal data class Binding(
105+
val association: Association,
106+
val prev: Entry.Emitted?,
107+
val next: Entry.Emitted?,
108+
val comments: Comments,
109+
) {
110+
// technically, this makes storing the association redundant (can just do object comparison.)
111+
val boundToken: Entry.Emitted = when (this.association) {
112+
Association.Before -> this.next
113+
Association.After -> this.prev
114+
}.let(::requireNotNull)
115+
116+
fun expand(): List<Token> {
117+
val comments = this@Binding.comments
118+
119+
val content = comments
120+
.concatenate()
121+
.flatMap {
122+
when (it) {
123+
is Entry.NotEmitted.Comment -> Token.Terminal(str = it.content, id = null, space = Space.TT).asList()
124+
is Entry.NotEmitted.LineBreaks -> {
125+
List(it.count) { Token.LineBreak.Hard }
126+
}
127+
is Entry.NotEmitted.Whitespace -> {
128+
// we discard the user's whitespace and just use the current indent.
129+
// actually, I have no idea if this is going to work.
130+
emptyList()
131+
}
132+
}
133+
}
113134

114-
fun takeBuffer(): NonEmptyList<Entry.NotEmitted>? {
115-
val buffer = if (this.inProgress) {
116-
this.buffer.toNonEmptyList() ?: invariantBroken("we added a comment when we transitioned to in-progress")
135+
val before = if (this.prev == null || comments.before.isEmpty()) {
136+
emptyList()
117137
} else {
118-
null
138+
when (lineDifference(prev.range, comments.range)) {
139+
0 -> Token.SingleWhiteSpace.asList()
140+
1 -> Token.LineBreak.Soft.asList()
141+
else -> listOf(Token.LineBreak.Soft, Token.LineBreak.Hard)
142+
}
119143
}
120144

121-
this.buffer.clear()
122-
this.inProgress = false
145+
val after = if (this.next == null || comments.after.isEmpty()) {
146+
emptyList()
147+
} else {
148+
when (lineDifference(comments.range, next.range)) {
149+
0 -> Token.SingleWhiteSpace.asList()
150+
1 -> Token.LineBreak.Soft.asList()
151+
else -> listOf(Token.LineBreak.Soft, Token.LineBreak.Hard)
152+
}
153+
}
123154

124-
return buffer
155+
return flatListOf(before, content, after)
125156
}
126157
}
127158

128159
internal class CommentsBuilder {
129-
private val currentRun: CommentRunBuffer = CommentRunBuffer(mutableListOf(), inProgress = false)
130-
131160
private var prevEmitted: Entry.Emitted? = null
132161
private val bindings: MutableList<Binding> = mutableListOf()
133162

163+
private val buffer: MutableList<Entry.NotEmitted> = mutableListOf()
164+
private var bufferValid: Boolean = false
165+
166+
fun takeBuffer(): List<Entry.NotEmitted>? {
167+
val buffer = if (this.bufferValid) {
168+
val bufferClone = this.buffer.toMutableList()
169+
170+
ensure(!bufferClone.isEmpty(), "we added a comment when we transitioned to in-progress")
171+
this.bufferValid = false
172+
173+
bufferClone
174+
} else {
175+
null
176+
}
177+
178+
this.buffer.clear()
179+
180+
return buffer
181+
}
182+
134183
fun register(entry: Entry) {
135184
when (entry) {
136185
is Entry.Emitted -> {
137-
val buffer: List<Entry.NotEmitted>? = this.currentRun.takeBuffer()
186+
val buffer = this.takeBuffer()
138187

139188
if (buffer != null) {
140189
val binding = Comments(buffer).bindToToken(this.prevEmitted, nextEmitted = entry)
@@ -145,16 +194,20 @@ internal class CommentsBuilder {
145194
}
146195

147196
is Entry.NotEmitted.Comment -> {
148-
this.currentRun.inProgress = true
149-
this.currentRun.add(entry)
197+
this.bufferValid = true
198+
buffer.add(entry)
199+
}
200+
is Entry.NotEmitted.LineBreaks -> {
201+
buffer.add(entry)
202+
}
203+
is Entry.NotEmitted.Whitespace -> {
204+
buffer.add(entry)
150205
}
151-
is Entry.NotEmitted.LineBreaks -> this.currentRun.add(entry)
152-
is Entry.NotEmitted.Whitespace -> this.currentRun.add(entry)
153206
}
154207
}
155208

156209
fun build(lastTokenOfFile: Entry.Emitted?): List<Binding> {
157-
val buffer = this.currentRun.takeBuffer()
210+
val buffer = this.takeBuffer()
158211
if (buffer != null) {
159212
val token = if (lastTokenOfFile != null) {
160213
lastTokenOfFile
@@ -169,7 +222,7 @@ internal class CommentsBuilder {
169222
zeroSizedEntry(fileName)
170223
}
171224

172-
val trailingComments = Binding(Association.After, token, Comments(buffer))
225+
val trailingComments = Binding(Association.After, prev = token, next = null, Comments(buffer))
173226
this.bindings.add(trailingComments)
174227
}
175228

0 commit comments

Comments
 (0)