Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
54b60d6
backup
jcp19 May 27, 2025
29744cd
backup
jcp19 Jul 8, 2025
b8d4914
Merge branch 'master' into fix-overflow-checks
jcp19 Aug 9, 2025
af1b2bb
backup
jcp19 Aug 9, 2025
904a229
add encoding for +
jcp19 Aug 11, 2025
922d1e7
complete arith operations
jcp19 Aug 11, 2025
db0e322
add bitwise operations
jcp19 Aug 11, 2025
ec6c612
backup
jcp19 Aug 11, 2025
f656b07
backup
jcp19 Aug 16, 2025
e818486
fix conversions
jcp19 Sep 18, 2025
bef1dc1
fix errors with the type of Length expressions at the encoding level;…
jcp19 Sep 20, 2025
789286d
fix more issues in the slice encoding
jcp19 Sep 20, 2025
4e9c007
continue adding support for overflow checks
jcp19 Sep 20, 2025
9a54f63
continue transform
jcp19 Sep 21, 2025
f586810
Backup just before undoing changes to the encoding of eq
jcp19 Sep 21, 2025
3296553
continue adding support for features in the overflow transformation
jcp19 Sep 21, 2025
54328f9
backup src
jcp19 Sep 22, 2025
819b491
backup
jcp19 Sep 23, 2025
e1d6f27
continue improving slice encoding
jcp19 Sep 23, 2025
ff95a11
backup current progress
jcp19 Sep 23, 2025
fbc1f1b
continue adding features
jcp19 Sep 23, 2025
18dec5b
string encoding
jcp19 Sep 23, 2025
9c85f6d
continue impl
jcp19 Sep 23, 2025
d20bbdd
more progress
jcp19 Sep 23, 2025
936a205
up to pkg/path
jcp19 Sep 24, 2025
54efbd9
backup in the middle of slayers/path/scion
jcp19 Sep 25, 2025
5896493
skip tests to force docker image to build
jcp19 Sep 28, 2025
416f26b
skip tests to force docker image to build
jcp19 Sep 28, 2025
abfa6c9
skip tests to force docker image to build
jcp19 Sep 28, 2025
00d57ce
trigger github action
jcp19 Sep 28, 2025
a4f2272
trigger github action
jcp19 Sep 28, 2025
e2af516
Merge branch 'master' into fix-overflow-checks
jcp19 Sep 28, 2025
c028984
merge master
jcp19 Sep 28, 2025
6edb56b
features for slayers/
jcp19 Sep 28, 2025
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
223 changes: 99 additions & 124 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,105 +92,105 @@ jobs:
cache-from: type=gha, scope=${{ github.workflow }}
cache-to: type=gha, scope=${{ github.workflow }}

- name: Execute all tests
run: |
# create a directory to sync with the docker container and to store the created pidstats
mkdir -p $PWD/sync
docker run \
--mount type=volume,dst=/build/gobra/sync,volume-driver=local,volume-opt=type=none,volume-opt=o=bind,volume-opt=device=$PWD/sync \
${{ env.IMAGE_TAG }} \
/bin/sh -c "$(cat .github/test-and-measure-ram.sh)"

- name: Get max RAM usage by Java and Z3
if: ${{ always() }}
shell: bash
env:
JAVA_WARNING_THRESHOLD_GB: 4.5
JAVA_FAILURE_THRESHOLD_GB: 5.5
Z3_WARNING_THRESHOLD_GB: 0.5
Z3_FAILURE_THRESHOLD_GB: 1
# awk is used to perform the computations such that the computations are performed with floating point precision
# we transform bash variables into awk variables to not cause problems with bash's variable substitution
# after computing the memory usage (in KB) a few more computations are performed. At the very end, all (local)
# environment variables are exported to `$GITHUB_ENV` such that they will be available in the next step as well.
run: |
function getMaxMemOfProcessInKb {
# $1 is the regex used to filter lines by the 9th column
# - we use variable `max` to keep track of the maximum
# - `curCount` stores the sum of all processes with the given name for a particular timestamp
# - note that looking at the timestamp is only an approximation: pidstat might report different timestamps in the
# same "block" of outputs (i.e. the report every second)
# - `NR>=2` specifies that the file's first line (NR=1) is ignored
# - `$7` refers to the 7th column in the file which corresponds to the column storing RAM (in kb)
# - `java$` matches only lines that end in the string 'java'
# - variable `max` is printed after processing the entire file
local result=$(awk -v processName=$1 -v max=0 -v curCount=0 -v curTimestamp=0 'processName~$9 {if(NR>=2){if(curTimestamp==$1){curCount=curCount+$7}else{curTimestamp=$1; curCount=$7}}; if(curCount>max){max=curCount}}END{print max}' sync/pidstat.txt)
echo $result
}
function convertKbToGb {
# $1 is the value [KB] that should be converted
local result=$(awk -v value=$1 'BEGIN {print value / 1000 / 1000}')
echo $result
}
function getLevel {
# $1 is the value that should be comparing against the thresholds
# $2 is the threshold causing a warning
# $3 is the threshold causing an error
# writes ${{ env.NOTICE_LEVEL }}, ${{ env.WARNING_LEVEL}} or ${{ env.FAILURE_LEVEL }} to standard output
local result=$(awk -v value=$1 -v warnThres=$2 -v errThres=$3 'BEGIN { print (value>errThres) ? "${{ env.FAILURE_LEVEL }}" : (value>warnThres) ? "${{ env.WARNING_LEVEL }}" : "${{ env.NOTICE_LEVEL}}" }')
echo $result
}
MAX_JAVA_KB=$(getMaxMemOfProcessInKb 'java$')
MAX_Z3_KB=$(getMaxMemOfProcessInKb 'z3$')
MAX_JAVA_GB=$(convertKbToGb $MAX_JAVA_KB)
MAX_Z3_GB=$(convertKbToGb $MAX_Z3_KB)
JAVA_LEVEL=$(getLevel $MAX_JAVA_GB ${{ env.JAVA_WARNING_THRESHOLD_GB }} ${{ env.JAVA_FAILURE_THRESHOLD_GB }})
Z3_LEVEL=$(getLevel $MAX_Z3_GB ${{ env.Z3_WARNING_THRESHOLD_GB }} ${{ env.Z3_FAILURE_THRESHOLD_GB }})
if [[ "$JAVA_LEVEL" = "${{ env.FAILURE_LEVEL }}" || "$Z3_LEVEL" = "${{ env.FAILURE_LEVEL }}" ]]
then
CONCLUSION="${{ env.CONCLUSION_FAILURE }}"
else
CONCLUSION="${{ env.CONCLUSION_SUCCESS }}"
fi
echo "MAX_JAVA_GB=$MAX_JAVA_GB" >> $GITHUB_ENV
echo "MAX_Z3_GB=$MAX_Z3_GB" >> $GITHUB_ENV
echo "JAVA_LEVEL=$JAVA_LEVEL" >> $GITHUB_ENV
echo "Z3_LEVEL=$Z3_LEVEL" >> $GITHUB_ENV
echo "CONCLUSION=$CONCLUSION" >> $GITHUB_ENV

- name: Create annotations
if: ${{ always() }}
# Outputs the memory consumption of JAVA and Z3. The message format is described in
# https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions .
run: |
JAVA_MESSAGE="Java used up to ${{ env.MAX_JAVA_GB }}GB of RAM"
if [ "${{ env.JAVA_LEVEL }}" = "${{ env.NOTICE_LEVEL }}" ]
then
echo "$JAVA_MESSAGE"
else
echo "::${{ env.JAVA_LEVEL }} file=.github/workflows/test.yml,line=1::$JAVA_MESSAGE"
fi

Z3_MESSAGE="Z3 used up to ${{ env.MAX_Z3_GB }}GB of RAM"
if [ "${{ env.Z3_LEVEL }}" = "${{ env.NOTICE_LEVEL }}" ]
then
echo "$Z3_MESSAGE"
else
echo "::${{ env.Z3_LEVEL }} file=.github/workflows/test.yml,line=1::$Z3_MESSAGE"
fi

if [ $CONCLUSION = "${{ env.CONCLUSION_FAILURE }}" ]
then
# the whole step fails when this comparison fails
exit 1
fi

- name: Upload RAM usage
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: pidstat.txt
path: sync/pidstat.txt
# - name: Execute all tests
# run: |
# # create a directory to sync with the docker container and to store the created pidstats
# mkdir -p $PWD/sync
# docker run \
# --mount type=volume,dst=/build/gobra/sync,volume-driver=local,volume-opt=type=none,volume-opt=o=bind,volume-opt=device=$PWD/sync \
# ${{ env.IMAGE_TAG }} \
# /bin/sh -c "$(cat .github/test-and-measure-ram.sh)"
#
# - name: Get max RAM usage by Java and Z3
# if: ${{ always() }}
# shell: bash
# env:
# JAVA_WARNING_THRESHOLD_GB: 4.5
# JAVA_FAILURE_THRESHOLD_GB: 5.5
# Z3_WARNING_THRESHOLD_GB: 0.5
# Z3_FAILURE_THRESHOLD_GB: 1
# # awk is used to perform the computations such that the computations are performed with floating point precision
# # we transform bash variables into awk variables to not cause problems with bash's variable substitution
# # after computing the memory usage (in KB) a few more computations are performed. At the very end, all (local)
# # environment variables are exported to `$GITHUB_ENV` such that they will be available in the next step as well.
# run: |
# function getMaxMemOfProcessInKb {
# # $1 is the regex used to filter lines by the 9th column
# # - we use variable `max` to keep track of the maximum
# # - `curCount` stores the sum of all processes with the given name for a particular timestamp
# # - note that looking at the timestamp is only an approximation: pidstat might report different timestamps in the
# # same "block" of outputs (i.e. the report every second)
# # - `NR>=2` specifies that the file's first line (NR=1) is ignored
# # - `$7` refers to the 7th column in the file which corresponds to the column storing RAM (in kb)
# # - `java$` matches only lines that end in the string 'java'
# # - variable `max` is printed after processing the entire file
# local result=$(awk -v processName=$1 -v max=0 -v curCount=0 -v curTimestamp=0 'processName~$9 {if(NR>=2){if(curTimestamp==$1){curCount=curCount+$7}else{curTimestamp=$1; curCount=$7}}; if(curCount>max){max=curCount}}END{print max}' sync/pidstat.txt)
# echo $result
# }
# function convertKbToGb {
# # $1 is the value [KB] that should be converted
# local result=$(awk -v value=$1 'BEGIN {print value / 1000 / 1000}')
# echo $result
# }
# function getLevel {
# # $1 is the value that should be comparing against the thresholds
# # $2 is the threshold causing a warning
# # $3 is the threshold causing an error
# # writes ${{ env.NOTICE_LEVEL }}, ${{ env.WARNING_LEVEL}} or ${{ env.FAILURE_LEVEL }} to standard output
# local result=$(awk -v value=$1 -v warnThres=$2 -v errThres=$3 'BEGIN { print (value>errThres) ? "${{ env.FAILURE_LEVEL }}" : (value>warnThres) ? "${{ env.WARNING_LEVEL }}" : "${{ env.NOTICE_LEVEL}}" }')
# echo $result
# }
# MAX_JAVA_KB=$(getMaxMemOfProcessInKb 'java$')
# MAX_Z3_KB=$(getMaxMemOfProcessInKb 'z3$')
# MAX_JAVA_GB=$(convertKbToGb $MAX_JAVA_KB)
# MAX_Z3_GB=$(convertKbToGb $MAX_Z3_KB)
# JAVA_LEVEL=$(getLevel $MAX_JAVA_GB ${{ env.JAVA_WARNING_THRESHOLD_GB }} ${{ env.JAVA_FAILURE_THRESHOLD_GB }})
# Z3_LEVEL=$(getLevel $MAX_Z3_GB ${{ env.Z3_WARNING_THRESHOLD_GB }} ${{ env.Z3_FAILURE_THRESHOLD_GB }})
# if [[ "$JAVA_LEVEL" = "${{ env.FAILURE_LEVEL }}" || "$Z3_LEVEL" = "${{ env.FAILURE_LEVEL }}" ]]
# then
# CONCLUSION="${{ env.CONCLUSION_FAILURE }}"
# else
# CONCLUSION="${{ env.CONCLUSION_SUCCESS }}"
# fi
# echo "MAX_JAVA_GB=$MAX_JAVA_GB" >> $GITHUB_ENV
# echo "MAX_Z3_GB=$MAX_Z3_GB" >> $GITHUB_ENV
# echo "JAVA_LEVEL=$JAVA_LEVEL" >> $GITHUB_ENV
# echo "Z3_LEVEL=$Z3_LEVEL" >> $GITHUB_ENV
# echo "CONCLUSION=$CONCLUSION" >> $GITHUB_ENV

# - name: Create annotations
# if: ${{ always() }}
# # Outputs the memory consumption of JAVA and Z3. The message format is described in
# # https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions .
# run: |
# JAVA_MESSAGE="Java used up to ${{ env.MAX_JAVA_GB }}GB of RAM"
# if [ "${{ env.JAVA_LEVEL }}" = "${{ env.NOTICE_LEVEL }}" ]
# then
# echo "$JAVA_MESSAGE"
# else
# echo "::${{ env.JAVA_LEVEL }} file=.github/workflows/test.yml,line=1::$JAVA_MESSAGE"
# fi
#
# Z3_MESSAGE="Z3 used up to ${{ env.MAX_Z3_GB }}GB of RAM"
# if [ "${{ env.Z3_LEVEL }}" = "${{ env.NOTICE_LEVEL }}" ]
# then
# echo "$Z3_MESSAGE"
# else
# echo "::${{ env.Z3_LEVEL }} file=.github/workflows/test.yml,line=1::$Z3_MESSAGE"
# fi
#
# if [ $CONCLUSION = "${{ env.CONCLUSION_FAILURE }}" ]
# then
# # the whole step fails when this comparison fails
# exit 1
# fi
#
# - name: Upload RAM usage
# if: ${{ always() }}
# uses: actions/upload-artifact@v4
# with:
# name: pidstat.txt
# path: sync/pidstat.txt

- name: Build entire image
uses: docker/build-push-action@v6
Expand All @@ -205,39 +205,14 @@ jobs:
# use GitHub cache:
cache-from: type=gha, scope=${{ github.workflow }}
cache-to: type=gha, scope=${{ github.workflow }}

- name: Test final container by verifying a file
run: |
docker run \
${{ env.IMAGE_TAG }} \
-i tutorial-examples/basic-annotations.gobra

- name: Decide whether image should be deployed or not
run: |
# note that these GitHub expressions return the value '1' for 'true' (as opposed to bash)
IS_GOBRA_REPO=${{ github.repository == 'viperproject/gobra' }}
IS_MASTER=${{ github.ref == 'refs/heads/master' }}
IS_VERIFIED_SCION=${{ github.ref == 'refs/heads/verified-scion-preview-without-selective' }}
IS_PROPER_TAG=${{ startsWith(github.ref, 'refs/tags/v') }}
# we use `${{ true }}` to be able to compare against GitHub's truth value:
if [[ "$IS_GOBRA_REPO" == ${{ true }} && ("$IS_MASTER" == ${{ true }} || "$IS_VERIFIED_SCION" == ${{ true }} || "$IS_PROPER_TAG" == ${{ true }}) ]]
then
SHOULD_DEPLOY='true'
else
SHOULD_DEPLOY='false'
fi
echo "SHOULD_DEPLOY=$SHOULD_DEPLOY" >> $GITHUB_ENV

- name: Login to Github Packages
if: env.SHOULD_DEPLOY == 'true'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Push entire image
if: env.SHOULD_DEPLOY == 'true'
uses: docker/build-push-action@v6
with:
context: .
Expand Down
9 changes: 7 additions & 2 deletions src/main/scala/viper/gobra/Gobra.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import com.typesafe.scalalogging.StrictLogging
import org.slf4j.LoggerFactory
import scalaz.Scalaz.futureInstance
import viper.gobra.ast.internal.Program
import viper.gobra.ast.internal.transform.{CGEdgesTerminationTransform, ConstantPropagation, InternalTransform, OverflowChecksTransform}
import viper.gobra.ast.internal.transform.{CGEdgesTerminationTransform, ConstantPropagation, IntConversionInferenceTransform, InternalTransform, OverflowChecksTransform}
import viper.gobra.backend.BackendVerifier
import viper.gobra.frontend.PackageResolver.{AbstractPackage, RegularPackage}
import viper.gobra.frontend.Parser.ParseResult
Expand Down Expand Up @@ -173,10 +173,15 @@ class Gobra extends GoVerifier with GoIdeVerifier {
finalConfig <- EitherT.fromEither(Future.successful(getAndMergeInFileConfig(config, pkgInfo)))
_ = setLogLevel(finalConfig)
parseResults <- performParsing(finalConfig, pkgInfo)
_ = println("parsing finished")
typeInfo <- performTypeChecking(finalConfig, pkgInfo, parseResults)
_ = println("type-checking finished")
program <- performDesugaring(finalConfig, typeInfo)
_ = println("desugaring finished")
program <- performInternalTransformations(finalConfig, pkgInfo, program)
_ = println("transforms finished")
viperTask <- performViperEncoding(finalConfig, pkgInfo, program)
_ = println("encoding finished")
} yield (viperTask, finalConfig)

task.foldM({
Expand Down Expand Up @@ -303,7 +308,7 @@ class Gobra extends GoVerifier with GoIdeVerifier {
// by overflow checks (if enabled) because all overflows in constant declarations
// can be found by the well-formedness checks.
val startMs = System.currentTimeMillis()
var transformations: Vector[InternalTransform] = Vector(CGEdgesTerminationTransform, ConstantPropagation)
var transformations: Vector[InternalTransform] = Vector(CGEdgesTerminationTransform, ConstantPropagation, IntConversionInferenceTransform)
if (config.checkOverflows) {
transformations :+= OverflowChecksTransform
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/viper/gobra/ast/frontend/Ast.scala
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,8 @@ case class PUInt64Type() extends PActualPredeclaredType("uint64") with PIntegerT
case class PByte() extends PActualPredeclaredType("byte") with PIntegerType
case class PUIntPtr() extends PActualPredeclaredType("uintptr") with PIntegerType

case class PGhostIntegerType() extends PGhostPredeclaredType("integer") with PIntegerType

sealed trait PFloatType extends PType
case class PFloat32() extends PActualPredeclaredType("float32") with PFloatType
case class PFloat64() extends PActualPredeclaredType("float64") with PFloatType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ class DefaultPrettyPrinter extends PrettyPrinter with kiama.output.PrettyPrinter
}

def showGhostType(typ : PGhostType) : Doc = typ match {
case PGhostIntegerType() => "integer"
case PPermissionType() => "perm"
case PSequenceType(elem) => "seq" <> brackets(showType(elem))
case PSetType(elem) => "set" <> brackets(showType(elem))
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/viper/gobra/ast/internal/PrettyPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -518,8 +518,8 @@ class DefaultPrettyPrinter extends PrettyPrinter with kiama.output.PrettyPrinter

case IndexedExp(base, index, _) => showExpr(base) <> brackets(showExpr(index))
case ArrayUpdate(base, left, right) => showExpr(base) <> brackets(showExpr(left) <+> "=" <+> showExpr(right))
case Length(exp) => "len" <> parens(showExpr(exp))
case Capacity(exp) => "cap" <> parens(showExpr(exp))
case Length(exp, _) => "len" <> parens(showExpr(exp))
case Capacity(exp, _) => "cap" <> parens(showExpr(exp))
case RangeSequence(low, high) =>
"seq" <> brackets(showExpr(low) <+> ".." <+> showExpr(high))
case GhostCollectionUpdate(seq, left, right, _) =>
Expand Down
29 changes: 24 additions & 5 deletions src/main/scala/viper/gobra/ast/internal/Program.scala
Original file line number Diff line number Diff line change
Expand Up @@ -741,16 +741,33 @@ case class Multiplicity(left : Expr, right : Expr)(val info: Source.Parser.Info)
* Denotes the length of `exp`, which is expected to be either
* of an array type or a sequence type or a set.
*/
case class Length(exp : Expr)(val info : Source.Parser.Info) extends Expr {
override def typ : Type = IntT(Addressability.rValue)
case class Length(exp : Expr, underlyingTypeExp : Type)(val info : Source.Parser.Info) extends Expr {
override def typ: Type = {
underlyingTypeExp match {
case _: ArrayT | PointerT(_: ArrayT, _) | _: SliceT | _: MapT | _: ChannelT | _: StringT =>
// TODO: make the default type dependent on the config (or drop the option for 32 bit int)
IntT(Addressability.rValue, TypeBounds.DefaultInt)
case _ =>
IntT(Addressability.rValue)
}
}
}

/**
* Represents the "cap(`exp`)" in Go, which gives
* the capacity of `exp` according to its type.
*/
case class Capacity(exp : Expr)(val info : Source.Parser.Info) extends Expr {
override def typ : Type = IntT(Addressability.rValue)
case class Capacity(exp : Expr, underlyingTypeExp : Type)(val info : Source.Parser.Info) extends Expr {
override def typ : Type = {
underlyingTypeExp match {
case _: ArrayT | PointerT(_: ArrayT, _) | _: SliceT | _: MapT | _: ChannelT | _: StringT =>
// TODO: make the default type dependent on the config (or drop the option for 32 bit int)
IntT(Addressability.rValue, TypeBounds.DefaultInt)
case _ =>
IntT(Addressability.rValue)
}
}

}

/**
Expand Down Expand Up @@ -1110,7 +1127,9 @@ case class ShiftLeft(left: Expr, right: Expr)(val info: Source.Parser.Info) exte
case class ShiftRight(left: Expr, right: Expr)(val info: Source.Parser.Info) extends BinaryIntExpr(">>") {
override val typ: Type = left.typ
}
case class BitNeg(op: Expr)(val info: Source.Parser.Info) extends IntOperation
case class BitNeg(op: Expr)(val info: Source.Parser.Info) extends IntOperation {
override val typ: Type = op.typ
}

/*
* Convert 'expr' to non-interface type 'newType'. If 'newType' is
Expand Down
Loading
Loading