Skip to content

Commit

Permalink
Merge pull request boogie-org#8 from atomb/structural-diff
Browse files Browse the repository at this point in the history
Support for structural comparison of Boogie implementations
  • Loading branch information
atomb authored Aug 8, 2024
2 parents ff9b6ea + c8fd603 commit 21830c7
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 5 deletions.
270 changes: 270 additions & 0 deletions Sources/SymDiff/source/BoogieStructuralComparisonManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Boogie;
using SymDiffUtils;

namespace SymDiff.source;

public static class BoogieStructuralComparisonManager
{
/// <summary>
/// Structurally compares two implementations with procedure call / variable
/// renaming. If the two are structurally equivalent, maps of procedure
/// and global variable names from this to other implementation that make
/// the two structurally equivalent are returned.
/// </summary>
public static bool EqualsStructural(this Implementation? thisImpl,
Implementation? otherImpl,
out Dictionary<Procedure, Procedure> procedureMapping,
out Dictionary<Variable, Variable> globalsMapping)
{
var implComparer = new ImplementationComparer();
var res = implComparer.Compare(thisImpl, otherImpl);

procedureMapping = implComparer.ProcedureMapping;
globalsMapping = implComparer.GlobalVarMapping;
return res;
}
}

public class ImplementationComparer
{
private Dictionary<Variable, Variable> localVarMapping = new();
private Dictionary<Block, Block> blockMapping = new();
public Dictionary<Variable, Variable> GlobalVarMapping = new();
public Dictionary<Procedure, Procedure> ProcedureMapping = new();

public bool Compare(Implementation? implA, Implementation? implB)
{
if (implA == null || implB == null
|| implA.TypeParameters.Count != implB.TypeParameters.Count
|| implA.InParams.Count != implB.InParams.Count
|| implA.OutParams.Count != implB.OutParams.Count
|| implA.Blocks.Count != implB.Blocks.Count)
return false;

foreach (var (blk1, blk2) in implA.Blocks.Zip(implB.Blocks))
if (!Compare(blk1, blk2)) return false;

return true;
}

private bool Compare(Block blockA, Block blockB)
{
if (blockA.TransferCmd == null || blockB.TransferCmd == null ||
blockA.TransferCmd.GetType() != blockB.TransferCmd.GetType())
return false;

if (blockA.TransferCmd is GotoCmd t1 &&
blockB.TransferCmd is GotoCmd t2)
{
foreach (var (blkTarget1, blkTarget2) in t1.labelTargets.Zip(t2.labelTargets))
{
if (!blockMapping.ContainsKey(blkTarget1))
blockMapping.Add(blkTarget1, blkTarget2);
else if (blockMapping[blkTarget1] != blkTarget2)
return false;
}
}

var i = 0;
var j = 0;
while (i < blockA.Cmds.Count && j < blockB.Cmds.Count)
{
if (IsIrrelevantCommand(blockA.Cmds[i]))
{
i++;
continue;
}
if (IsIrrelevantCommand(blockB.Cmds[j]))
{
j++;
continue;
}
if (!Compare(blockA.Cmds[i], blockB.Cmds[j]))
return false;
i++;
j++;
}
// Any remaining commands must be irrelevant
for (; i < blockA.Cmds.Count; ++i)
{
if (!IsIrrelevantCommand(blockA.Cmds[i]))
return false;
}
for (; j < blockB.Cmds.Count; ++j)
{
if (!IsIrrelevantCommand(blockB.Cmds[j]))
return false;
}

return true;
}

private bool EqualsStructuralExpr(Expr exprA, Expr exprB)
{
var combinedMapping = localVarMapping.Concat(GlobalVarMapping).ToDictionary();
var exprComparator = new ExprComparatorWithRenaming(combinedMapping);
var res = exprComparator.Compare(exprA, exprB);
var updatedMapping = exprComparator.GetUpdatedVariableMapping;

// update the mappings
foreach (var (v1, v2) in updatedMapping)
{
switch (v1)
{
case GlobalVariable:
case Constant:
GlobalVarMapping.TryAdd(v1, v2);
break;
default:
localVarMapping.TryAdd(v1, v2);
break;
};
}

return res;
}

private bool Compare(Cmd? cmdA, Cmd? cmdB)
{
if (cmdA == null || cmdB == null) return cmdA == cmdB;
if (cmdA.GetType() != cmdB.GetType()) return false;
return (cmdA, cmdB) switch
{
(AssertCmd a, AssertCmd b) => EqualsStructuralExpr(a.Expr, b.Expr),
(AssumeCmd a, AssumeCmd b) => EqualsStructuralExpr(a.Expr, b.Expr),
(AssignCmd a, AssignCmd b) => EqualsStructuralAssign(a, b),
(CallCmd a, CallCmd b) => EqualsStructuralCall(a, b),
(HavocCmd a, HavocCmd b) => EqualsStructuralHavoc(a, b),
_ => throw new NotSupportedException(
$"Do not know how to compare commands {cmdA} and {cmdB}")
};
}

private bool EqualsStructuralCall(CallCmd cmdA, CallCmd cmdB)
{
var procA = cmdA.Proc;
if (ProcedureMapping.TryGetValue(procA, out var procB) && cmdB.Proc != procB)
return false;

procB = cmdB.Proc;

if (procA.InParams.Count != procB.InParams.Count)
return false;

if (cmdA.Ins .Zip(cmdB.Ins) .Any(p => !EqualsStructuralExpr(p.First, p.Second)) ||
cmdA.Outs.Zip(cmdB.Outs).Any(p => !EqualsStructuralExpr(p.First, p.Second)))
return false;

ProcedureMapping.TryAdd(procA, procB);
return true;
}

private bool EqualsStructuralAssign(AssignCmd cmdA, AssignCmd cmdB)
{
if (cmdA.Lhss.Count != cmdB.Lhss.Count ||
cmdA.Rhss.Count != cmdB.Rhss.Count)
return false;

if (cmdA.Lhss.Zip(cmdB.Lhss).Any(p => !EqualsStructuralExpr(p.First.AsExpr, p.Second.AsExpr)) ||
cmdA.Rhss.Zip(cmdB.Rhss).Any(p => !EqualsStructuralExpr(p.First, p.Second)))
return false;

return true;
}

private bool EqualsStructuralHavoc(HavocCmd cmdA, HavocCmd cmdB)
{
return cmdA.Vars.Count == cmdB.Vars.Count &&
cmdA.Vars.Zip(cmdB.Vars).All(p => EqualsStructuralExpr(p.First, p.Second));
}

private bool IsIrrelevantCommand(Cmd cmd)
{
return cmd is AssertCmd { Expr: LiteralExpr { asBool: true } }
or AssumeCmd { Expr: LiteralExpr { asBool: true } }
or CommentCmd;
}
}

/// <summary>
/// Structurally compares two expressions, ignoring variable renaming.
/// It will build up a variable mapping in the process. Any pairs in the
/// input mapping will be used during the comparison, but are not needed.
/// </summary>
public class ExprComparatorWithRenaming(Dictionary<Variable, Variable> variableMapping)
{
private readonly Dictionary<Variable, Variable> mapping = new(variableMapping);
public Dictionary<Variable, Variable> GetUpdatedVariableMapping => mapping;

public bool Compare(Expr? exprA, Expr? exprB)
{
if (exprA == null || exprB == null)
return exprA == exprB;

if (exprA.GetType() != exprB.GetType())
return false;

return (exprA, exprB) switch
{
(LiteralExpr e1, LiteralExpr e2) => e1.Equals(e2), // no variables, Expr.Equals is fine
(BvExtractExpr e1, BvExtractExpr e2) => e1.Equals(e2), // no variables, Expr.Equals is fine
(OldExpr e1, OldExpr e2) => Compare(e1.Expr, e2.Expr),
(BvConcatExpr e1, BvConcatExpr e2) => Compare(e1.E0, e2.E0) && Compare(e1.E1, e2.E1),
(NAryExpr e1, NAryExpr e2) => CompareNAryExpr(e1, e2),
(BinderExpr e1, BinderExpr e2) => CompareBinderExpr(e1, e2),
(IdentifierExpr e1, IdentifierExpr e2) => CompareIdentifierExpr(e1, e2),
_ => throw new NotSupportedException($"Expression type {exprA.GetType().Name}" +
$" is not supported for structural equivalence check." +
$"Tried to compare {exprA} and {exprB}.")
};
}

private bool CompareIdentifierExpr(IdentifierExpr a, IdentifierExpr b)
{
if (mapping.TryGetValue(a.Decl, out var bVar))
{
return b.Name.Equals(bVar.Name);
}

mapping.Add(a.Decl, b.Decl);
return true;
}

private bool CompareNAryExpr(NAryExpr a, NAryExpr b)
{
if (!Equals(a.Fun, b.Fun) || a.Args.Count != b.Args.Count)
return false;
for (var i = 0; i < a.Args.Count; i++)
{
if (!Compare(a.Args[i], b.Args[i]))
return false;
}
return true;
}

private bool CompareBinderExpr(BinderExpr a, BinderExpr b)
{
if (a.Kind != b.Kind
|| !a.TypeParameters.SequenceEqual(b.TypeParameters)
|| a.Dummies.Count != b.Dummies.Count)
return false;

// To handle dummies, we create a new ExprComparator only for this expr,
// which contains the dummy mapping in the two expressions. After
// comparison, we eliminate dummies but preserve any non-dummy variables
// in the new mapping.
Dictionary<Variable, Variable> mappingWithDummies = new(mapping);
a.Dummies.Zip(b.Dummies).ForEach(p => mappingWithDummies.Add(p.First, p.Second));
var comp = new ExprComparatorWithRenaming(mappingWithDummies);
if (!comp.Compare(a.Body, b.Body))
return false;
var newMappingWithDummies = comp.GetUpdatedVariableMapping;
var newKeysWithoutDummies = newMappingWithDummies.Keys.Except(mappingWithDummies.Keys);
newKeysWithoutDummies.ForEach(k => mapping.Add(k, newMappingWithDummies[k]));
return true;
}
}
5 changes: 4 additions & 1 deletion Sources/SymDiff/source/BoogieVerify.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ public class SDiffCounterexamples : List<SDiffCounterexample>
{
}

public enum VerificationResult { Error, Verified, Inconclusive, TimeOut, OutOfMemory, TBA, Unknown };
public enum VerificationResult
{
Error, Verified, Inconclusive, TimeOut, OutOfMemory, TBA, Unknown, VerifiedSyntactic
}

public class VerificationTask : Triple<Implementation, Implementation, Implementation>, IEmittable
{
Expand Down
1 change: 1 addition & 0 deletions Sources/SymDiff/source/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public enum DAC_ENCODING_OPT { DAC_LINEAR, DAC_NORMAL };
public static bool localcheck;
public static bool nonModularMode;
public static bool checkAssertsOnly;
public static bool noSyntacticCheck;

//mutual summaries
public static bool mutualSummaryMode;
Expand Down
23 changes: 19 additions & 4 deletions Sources/SymDiff/source/WholeProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading;
using Microsoft.Boogie;
using B = SDiff.Boogie;
using SymDiff.source;

using SymDiffUtils;
using VC;
Expand Down Expand Up @@ -68,6 +69,7 @@ private static void AllInOneUsage(){
Console.WriteLine("\t -nonmodular inline callees");
Console.WriteLine("\t -recursionDepth:k inline procedures upto k depth");
Console.WriteLine("\t -localcheck only checks the syntactically changed procedures");
Console.WriteLine("\t -noSyn do not check for syntactic equvalence (except those specified with -synEq)");
Console.WriteLine("\t -rvt Godlin & Strichman's method for dealing with recursion (not tested well)");
Console.WriteLine("\t -returnAsOnlyOutput only considers retuns as the output of a procedure (input is all globals read + params)");
Console.WriteLine("\t -outvar:\"<name>\" only compares output variables with substring <name> (needs -splitOutputEqualities). Can specify multiple -outvar:\"x\" -outvar:\"y\"");
Expand Down Expand Up @@ -112,6 +114,7 @@ private static bool ParseArgs(string[] args0) {
Options.nonModularMode = argsList.Remove("-nonmodular");
Options.localcheck = argsList.Remove("-localcheck");
Options.oneproc = argsList.Remove("-oneproc");
Options.noSyntacticCheck = argsList.Remove("-noSyn");

//DAC related
Options.checkAssertsOnly = argsList.Remove("-asserts");
Expand Down Expand Up @@ -1077,9 +1080,13 @@ private static List<EquivalenceResult> CreateAndVerifyEqProcs(CallGraph cg1, Cal
continue;
}
//skip if the procedure belongs to the syntactically equal list
if (Options.syntacticEqProcs.Contains(n1.Impl.Name.Replace(p1Prefix + ".", "")))
if (Options.syntacticEqProcs.Contains(n1.Impl.Name.Replace(p1Prefix + ".", "")) ||
!Options.noSyntacticCheck &&
n1.Impl.EqualsStructural(n2.Impl, out var procedureMapping, out var globalsMapping))
{
Log.Out(Log.Normal, "Syntactically equivalent:: Skipping procedure " + n1.Impl.Name.Replace(p1Prefix + ".", "") + "...");
equivalenceResults.Add(new EquivalenceResult(
n1.Name, n2.Name, VerificationResult.VerifiedSyntactic, [], null));
continue;
}
//[SKL]: What is going on here?
Expand Down Expand Up @@ -1615,7 +1622,8 @@ public static List<EquivalenceResult> AllInOneMainWithResult(string[] args)
//////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////
// Assume all globals with 'heap' in its name as modified.
// Assume all globals that contain any heap identifiers
// in their names as modified.
//////////////////////////////////////////////////////////
var globalsToAssumeModifiedP1 = p1.GlobalVariables
.Where(v => Options.HeapStringIdentifiers.Any(id => v.Name.Contains(id)))
Expand Down Expand Up @@ -1747,12 +1755,19 @@ public class EquivalenceResult(string procedure1,
{
public string Procedure1 { get; } = procedure1;
public string Procedure2 { get; } = procedure2;
public VerificationResult VerificationRunResult { get; } = verificationResult;
public VerificationResult VerificationResult { get; } = verificationResult;
public SDiffCounterexamples Counterexamples { get; } = counterexamples;
public EqualityProcedureParameterInfo ParameterInfo { get; } = parameterInfo;
public override string ToString()
{
return $"{Procedure1} == {Procedure2}: {VerificationRunResult}";
var resultString = VerificationResult switch
{
VerificationResult.VerifiedSyntactic => "Verified (syntactic)",
VerificationResult.TimeOut => "T/O",
VerificationResult.OutOfMemory => "Out of memory",
_ => VerificationResult.ToString()
};
return $"{Procedure1} == {Procedure2}: {resultString}";
}
}

Expand Down

0 comments on commit 21830c7

Please sign in to comment.