Skip to content

Commit

Permalink
prototype contract synthesis
Browse files Browse the repository at this point in the history
  • Loading branch information
s0mark committed Nov 3, 2024
1 parent cb77d7e commit 5729500
Show file tree
Hide file tree
Showing 18 changed files with 401 additions and 27 deletions.
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ include(
"sts/sts-cli",

"xcfa/xcfa",
"xcfa/arg2acsl",
"xcfa/cat",
"xcfa/c2xcfa",
"xcfa/litmus2xcfa",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class VarDecl<DeclType extends Type> extends Decl<DeclType> {

private final Map<Integer, IndexedConstDecl<DeclType>> indexToConst;

VarDecl(final String name, final DeclType type) {
public VarDecl(final String name, final DeclType type) {
super(name, type);
indexToConst = Containers.createMap();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ public CStatement visitGlobalDeclaration(CParser.GlobalDeclarationContext ctx) {
}
}
recordMetadata(ctx, decls);
for (Tuple2<CDeclaration, VarDecl<?>> tuple2 : decls.getcDeclarations()) {
tuple2.get1().recordMetadata(decls);
}
return decls;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@
package hu.bme.mit.theta.frontend.transformation.model.declaration;

import hu.bme.mit.theta.core.decl.VarDecl;
import hu.bme.mit.theta.frontend.transformation.model.statements.CDecls;
import hu.bme.mit.theta.frontend.transformation.model.statements.CStatement;
import hu.bme.mit.theta.frontend.transformation.model.types.complex.CComplexType;
import hu.bme.mit.theta.frontend.transformation.model.types.complex.compound.CArray;
import hu.bme.mit.theta.frontend.transformation.model.types.simple.CSimpleType;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;

import java.util.ArrayList;
import java.util.List;

import static hu.bme.mit.theta.grammar.UtilsKt.textWithWS;

public class CDeclaration {

private CSimpleType type;
Expand All @@ -36,6 +41,14 @@ public class CDeclaration {
private final List<CDeclaration> functionParams = new ArrayList<>();
private CStatement initExpr;

private int lineNumberStart = -1;
private int colNumberStart = -1;
private int lineNumberStop = -1;
private int colNumberStop = -1;
private int offsetStart = -1;
private int offsetEnd = -1;
private String sourceText = "";

public CDeclaration(CSimpleType cSimpleType) {
this.name = null;
this.type = cSimpleType;
Expand Down Expand Up @@ -147,4 +160,42 @@ public List<VarDecl<?>> getVarDecls() {
public void addVarDecl(VarDecl<?> varDecl) {
this.varDecls.add(varDecl);
}

public int getLineNumberStart() {
return lineNumberStart;
}

public int getColNumberStart() {
return colNumberStart;
}

public int getLineNumberStop() {
return lineNumberStop;
}

public int getColNumberStop() {
return colNumberStop;
}

public int getOffsetStart() {
return offsetStart;
}

public int getOffsetEnd() {
return offsetEnd;
}

public String getSourceText() {
return sourceText;
}

public void recordMetadata(CDecls cDecls) {
this.lineNumberStart = cDecls.getLineNumberStart();
this.lineNumberStop = cDecls.getLineNumberStop();
this.colNumberStart = cDecls.getColNumberStart();
this.colNumberStop = cDecls.getColNumberStop();
this.offsetStart = cDecls.getOffsetStart();
this.offsetEnd = cDecls.getOffsetEnd();
this.sourceText = cDecls.getSourceText();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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.arg2acsl

import hu.bme.mit.theta.analysis.Action
import hu.bme.mit.theta.analysis.State
import hu.bme.mit.theta.analysis.algorithm.arg.ARG
import hu.bme.mit.theta.analysis.expl.ExplState
import hu.bme.mit.theta.analysis.expr.ExprState
import hu.bme.mit.theta.analysis.pred.PredState
import hu.bme.mit.theta.analysis.ptr.PtrState
import hu.bme.mit.theta.c2xcfa.CMetaData
import hu.bme.mit.theta.c2xcfa.getCMetaData
import hu.bme.mit.theta.core.decl.VarDecl
import hu.bme.mit.theta.core.type.anytype.RefExpr
import hu.bme.mit.theta.core.type.booltype.NotExpr
import hu.bme.mit.theta.core.type.inttype.IntEqExpr
import hu.bme.mit.theta.core.type.inttype.IntLitExpr
import hu.bme.mit.theta.core.type.inttype.IntType
import hu.bme.mit.theta.xcfa.analysis.XcfaState
import hu.bme.mit.theta.xcfa.analysis.reverseMapping
import hu.bme.mit.theta.xcfa.analysis.withGeneralizedVars
import hu.bme.mit.theta.xcfa.model.XcfaGlobalVar
import hu.bme.mit.theta.xcfa.model.XcfaLocation
import java.io.File
import java.math.BigInteger
import kotlin.streams.asSequence

fun PredState.toAcsl(): String? =
if (this.preds.isEmpty()) null else
this.preds.joinToString(prefix = "(", separator = ") && (", postfix = ")") { pred ->
pred.toAcsl()
}

fun ExplState.toAcsl(): String? =
if (this.decls.isEmpty()) null else
this.decls.joinToString(prefix = "(", separator = ") && (", postfix = ")") { decl ->
"${decl.toAcsl()} == ${this.eval(decl).get().toAcsl()}"
}

fun ExprState.toAcsl(): String? =
when (this) {
is ExplState -> this.toAcsl()
is PredState -> this.toAcsl()
is PtrState<*> -> this.innerState.toAcsl()
else -> TODO("Conversion to ACSL is not implemented for the domain")
}

fun<S : ExprState, A : Action> ARG<XcfaState<S>, A>.initialProcedureStates(): Map<XcfaLocation, Set<S>> =
this.nodes.asSequence().filter { node -> // filter nodes in initial locations
node.state.processes.values.firstOrNull()?.locs?.peek()?.initial ?: false
}.groupByUnique( // group states by location
{ it.state.processes.values.first().locs.peek() },
{ node ->
val varLookup = node.state.processes.values.firstOrNull()?.varLookup?.peek()?.reverseMapping() ?: mapOf()
val globalVars = node.state.xcfa?.vars?.map(XcfaGlobalVar::wrappedVar) ?: listOf()
val relevantVars = (varLookup.keys + varLookup.values + globalVars).filter { !it.name.endsWith("_ret") }
node.state.withGeneralizedVars(relevantVars)
}
)

fun<S : ExprState, A : Action> ARG<XcfaState<S>, A>.writeAcslContracts(input: File, output: File) {
// TODO add @requires \false to unreachable procedures?
val lines = input.readLines().toMutableList()

// remove global variable declarations
val globalVarDecls = this.initNodes.asSequence().first().state.xcfa?.vars?.associate { globalVar ->
globalVar.getCMetaData()!!.lineNumberStart!!.let {
Pair(it, lines.removeAt(it - 1).apply { lines.add(it - 1, "") })
}
}?.apply { this.keys.sortedDescending().forEach { lines.removeAt(it - 1) } }

// add contracts
this.initialProcedureStates().toSortedMap { l1, l2 ->
(l2.metadata as CMetaData).lineNumberStart!! - (l1.metadata as CMetaData).lineNumberStart!!
}.forEach { (location, states) ->
states.mapIndexedNotNull { i, state ->
state.toAcsl()?.let{ pred -> "@ behavior ${location.name}_b$i: requires $pred;" }
}.let { preds ->
if (preds.isNotEmpty()) {
val lineNumber = location.getCMetaData()!!.lineNumberStart!!.let { lineNumber ->
val offset = globalVarDecls?.keys?.count { lineNumber > it } ?: 0
if (lineNumber > offset) lineNumber - 1 - offset else 0
}
lines.add(lineNumber, preds.joinToString(separator = "\n", prefix = "/*", postfix = "\n@ complete behaviors;*/"))
}
}
}

// place global variable declarations in the beginning
val afterExtern = lines.indexOfLast { it.contains("extern") } + 1
globalVarDecls?.values?.forEachIndexed { i, globalVarDecl -> lines.add(afterExtern + i, globalVarDecl) }

output.printWriter().use { writer -> lines.forEach(writer::println) }
}

fun<S : State, A : Action> writeAcsl(arg: ARG<S, A>, input: File, output: File) {
((arg as? ARG<XcfaState<ExprState>, A>?) ?: TODO("ARG cast not implemented")).writeAcslContracts(input, output)
}

fun main() {
PredState.of(listOf(
NotExpr.of(
IntEqExpr.of(
RefExpr.of(VarDecl("n", IntType.getInstance())),
IntLitExpr.of(BigInteger.valueOf(2147483647))
)
)
)).toAcsl()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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.arg2acsl

import hu.bme.mit.theta.core.decl.Decl
import hu.bme.mit.theta.core.type.*
import hu.bme.mit.theta.core.type.abstracttype.*
import hu.bme.mit.theta.core.type.anytype.IteExpr
import hu.bme.mit.theta.core.type.anytype.RefExpr
import hu.bme.mit.theta.core.type.booltype.*
import hu.bme.mit.theta.core.type.inttype.IntLitExpr

private fun<T : Type> Expr<T>.unimplemented(): String
= TODO("Conversion to ACSL is not implemented for expression $this")

fun<T : Type> NullaryExpr<T>.toAcsl(): String {
return when (this) {
is BoolLitExpr -> "\\$this"
is IntLitExpr -> "$this"
is RefExpr -> "$this".split("::").last()
else -> this.unimplemented()
}
}

fun<OpType : Type, ExprType : Type> UnaryExpr<OpType, ExprType>.toAcsl(): String {
val op = this.op.toAcsl()
return when (this) {
is NotExpr -> "!($op)"
is PosExpr<*> -> "+($op)"
is NegExpr<*> -> "-($op)"
else -> this.unimplemented()
}
}

fun<OpType: Type, ExprType : Type> BinaryExpr<OpType, ExprType>.toAcsl(): String {
val leftOp = this.leftOp.toAcsl()
val rightOp = this.rightOp.toAcsl()
return when (this) {
is EqExpr -> "($leftOp) == ($rightOp)"
is XorExpr -> "($leftOp) ^^ ($rightOp)"
is NeqExpr -> "($leftOp) != ($rightOp)"
is GtExpr -> "($leftOp) > ($rightOp)"
is GeqExpr -> "($leftOp) >= ($rightOp)"
is LtExpr -> "($leftOp) < ($rightOp)"
is LeqExpr -> "($leftOp) <= ($rightOp)"
is SubExpr<*> -> "($leftOp) - ($rightOp)"
is DivExpr<*> -> "($leftOp) / ($rightOp)"
is ModExpr<*> -> "($leftOp) % ($rightOp)"
is RemExpr<*> -> "($leftOp) % ($rightOp)"
else -> this.unimplemented()
}
}

fun<OpType : Type, ExprType : Type> MultiaryExpr<OpType, ExprType>.toAcsl(): String {
val separator = when (this) {
is AddExpr<*> -> "+"
is MulExpr<*> -> "*"
is AndExpr -> "&&"
is OrExpr -> "||"
else -> this.unimplemented()
}
return this.ops.joinToString(separator = separator) { op ->
"(${op.toAcsl()})"
}
}

fun<T : Type> IteExpr<T>.toAcsl(): String
= "(${this.cond.toAcsl()}) ? (${this.then.toAcsl()}) : (${this.`else`.toAcsl()})"

fun QuantifiedExpr.toAcsl(): String = this.unimplemented()

fun<T : Type> Expr<T>.toAcsl(): String {
return when (this) {
is NullaryExpr -> this.toAcsl()
is UnaryExpr<*, T> -> this.toAcsl()
is BinaryExpr<*, T> -> this.toAcsl()
is MultiaryExpr<*, T> -> this.toAcsl()
is IteExpr<*> -> this.toAcsl()
is QuantifiedExpr -> this.toAcsl()
else -> this.unimplemented()
}
}

fun<T : Type> Decl<T>.toAcsl() = this.name.split("::").last()
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.arg2acsl

inline fun <T, K, V> Sequence<T>.groupByUnique(keySelector: (T) -> K, valueTransform: (T) -> V): Map<K, Set<V>> =
mutableMapOf<K, MutableSet<V>>().apply {
this@groupByUnique.forEach { this.getOrPut(keySelector(it), ::mutableSetOf).add(valueTransform(it)) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@
*/
package hu.bme.mit.theta.c2xcfa

import hu.bme.mit.theta.xcfa.model.MetaData
import hu.bme.mit.theta.xcfa.model.XcfaEdge
import hu.bme.mit.theta.xcfa.model.XcfaLabel
import hu.bme.mit.theta.xcfa.model.XcfaLocation
import hu.bme.mit.theta.xcfa.model.*

data class CMetaData(
val lineNumberStart: Int?,
Expand Down Expand Up @@ -52,4 +49,6 @@ fun XcfaEdge.getCMetaData(): CMetaData? {
} else {
null
}
}
}

fun XcfaGlobalVar.getCMetaData() = this.metadata as? CMetaData
Loading

0 comments on commit 5729500

Please sign in to comment.