【问题标题】:Modeling nested tuples / sequences in z3在 z3 中建模嵌套元组/序列
【发布时间】:2021-09-07 17:32:45
【问题描述】:

我目前正在为一小部分 Python 构建一个符号执行引擎。这个子集支持的最复杂的数据结构是任意嵌套的元组,也就是说,你可以写像x = (1, 2, (3, 4), 5)这样的东西。在我的 SE 引擎中,所有值都表示为 z3 对象。昨天,我尝试在 z3 中为这些嵌套元组建模时遇到了很困难的事情。

我尝试了什么:

  • 数组。问题:数组就是数组,也就是说,它们跨越一个矩形空间,这不适合我的元组。
  • 序列。我喜欢序列数据类型;但是,它不支持嵌套。例如,你不能写z3.Concat(z3.Unit(z3.IntVal(1)), z3.Unit(z3.Concat(z3.Unit(z3.IntVal(2)), z3.Unit(z3.IntVal(3))))),这会引发z3.z3types.Z3Exception: b"Sort of function 'seq.++' does not match the declared type. Given domain: (Seq Int) (Seq (Seq Int))"
  • 列表。然而,列表只有一个类型T,我只能将其实例化为List,例如Int。据我所知,z3 中没有联合类型或通用超级排序之类的东西。
  • 未解释的函数。我认为应该可以引入带有排序Tuple 的函数tuple,甚至为不同的参数长度和排序重载它。但是,我不知道如何提取 tuple(1, 2, 3) 的元素,因为该表达式不是递归定义的。

我会感谢 z3 / SMT 专家的帮助!

提前非常感谢!

元组数据类型:“抽象”索引和元组的问题

我还尝试了@alias 提出的想法,为元组定义数据类型。这工作得很好,但有一个问题:如果元组或索引不是具体的,即是(包含)变量的(表达式),我如何对元组元素的访问进行建模?

例如,我可以将 2 元组的整数定义为:

Tup_II = z3.Datatype('Tuple_II')
Tup_II.declare('tuple', ('fst', z3.IntSort()), ('snd', z3.IntSort()))
Tup_II = Tup_II.create()

a_tuple = Tup_II.tuple(z3.IntVal(1), z3.IntVal(2))
print(a_tuple)  # tuple(1, 2)

tuple_concrete_access = z3.simplify(Tup_II.fst(a_tuple))
print(tuple_concrete_access)  # 1

没关系。我还可以通过嵌入 Tup_II 数据类型在顶部定义嵌套元组:

Tup_IT = z3.Datatype('Tuple_IT')
Tup_IT.declare('tuple', ('fst', z3.IntSort()), ('snd', Tup_II))
Tup_IT = Tup_IT.create()

another_tuple = Tup_IT.tuple(z3.IntVal(0), a_tuple)
print(another_tuple)  # tuple(0, tuple(1, 2))

但是要访问一个元素,我需要知道索引是 0 还是 1 才能选择正确的访问器 (fst, snd) .

我试图从序列类型的行为中获得灵感:

int_seq = z3.Concat(z3.Unit(z3.IntVal(1)), z3.Unit(z3.IntVal(2)))
print(int_seq)  # Concat(Unit(1), Unit(2))

concrete_access = z3.simplify(int_seq[z3.IntVal(0)])
print(concrete_access)  # 1

concrete_access_2 = z3.simplify(int_seq[z3.IntVal(2)])

x = z3.Int("x")
abstract_access = z3.simplify(int_seq[x])
print(abstract_access)
# If(And(x >= 0, Not(2 <= x)),
#    seq.nth_i(Concat(Unit(1), Unit(2)), x),
#    seq.nth_u(Concat(Unit(1), Unit(2)), x))

所以一个想法是定义一个Tuple_II.nth 函数。但是,如果我们有一个像 Tup_IT 这样由不同类型的元素组成的元组,我该如何定义这个函数的目标域呢?例如,

target_sort = # ???
tup_IT_nth = z3.Function("Tuple_IT.nth", z3.IntSort(), Tup_II, target_sort)

因此,我需要某种超级类型的intTup_II:与列表相同的问题。

有什么想法吗? :)

我想要的:创建元组类型的实用函数

只是假设我可以解决getter函数的排序问题;然后我写了一个很好的实用函数,创建了在存在抽象索引的情况下处理元组所需的所有东西:

def create_tuple_type(*sorts: z3.SortRef) -> \
        Tuple[z3.Datatype, Dict[int, z3.FuncDeclRef], z3.FuncDeclRef, z3.BoolRef]:
    """
    DOES NOT YET WORK WITH NESTED TUPLES!

    Example:
    >>> tuple_II, accessors, getter, axioms = create_tuple_type(z3.IntSort(), z3.IntSort())
    >>>
    >>> a_tuple = tuple_II.tuple(z3.IntVal(1), z3.IntVal(2))
    >>>
    >>> print(z3.simplify(accessors[0](a_tuple)))  # 1
    >>> print(z3.simplify(getter(a_tuple, z3.IntVal(0))))  # Tuple_II.nth(tuple(1, 2), 0)
    >>>
    >>> s = z3.Solver()
    >>> s.set("timeout", 1000)
    >>> s.add(z3.Not(z3.Implies(axioms, z3.IntVal(1) == getter(a_tuple, z3.IntVal(0)))))
    >>> assert s.check() == z3.unsat  # proved!
    >>>
    >>> s = z3.Solver()
    >>> s.set("timeout", 1000)
    >>> s.add(z3.Not(z3.Implies(axioms, z3.IntVal(0) == getter(a_tuple, z3.IntVal(0)))))
    >>> assert s.check() == z3.unknown  # not proved!
    :param sorts: The argument sorts for the tuple type
    :return: The new tuple type along with
      accessor functions,
      a generic accessor function,
      and axioms for the generic accessor
    """
    dt_name = "Tuple_" + "".join([str(sort)[0] for sort in sorts])
    datatype = z3.Datatype(dt_name)
    datatype.declare('tuple', *{f"get_{i}": sort for i, sort in enumerate(sorts)}.items())
    datatype = datatype.create()

    accessors = {i: getattr(datatype, f"get_{i}") for i in range(len(sorts))}

    target_sort = z3.IntSort()  # ??? <-- What to do here?

    get = z3.Function(f"{dt_name}.nth", datatype, z3.IntSort(), target_sort)
    get_in_range = z3.Function(f"{dt_name}.nth_i", datatype, z3.IntSort(), target_sort)
    get_not_in_range = z3.Function(f"{dt_name}.nth_u", datatype, z3.IntSort(), target_sort)

    x = z3.Int("x")
    t = z3.Const("t", datatype)

    axiom_1 = z3.ForAll(
        [t, x],
        get(t, x) == z3.If(
            z3.And(x >= z3.IntVal(0), x < z3.IntVal(len(sorts))),
            get_in_range(t, x),
            get_not_in_range(t, x)
        )
    )

    axiom_2 = None
    for idx in range(len(sorts)):
        axiom = get_in_range(t, z3.IntVal(idx)) == accessors[idx](t)
        if axiom_2 is None:
            axiom_2 = axiom
            continue

        axiom_2 = z3.And(axiom_2, axiom)

    axiom_2 = z3.ForAll([t], axiom_2)

    return datatype, accessors, get, z3.And(axiom_1, axiom_2)

问题在于target_sort 的声明与# ??? &lt;-- What to do here? 注释。

【问题讨论】:

    标签: tuples z3 smt z3py smt-lib


    【解决方案1】:

    为什么不直接使用元组来建模元组呢?您可以声明一个泛型元组类型,然后将其实例化多次以处理嵌套的。

    这里有一个例子:https://rise4fun.com/z3/tutorialcontent/guide#h27,同样可以在 z3py 中编码。有关更多示例,请参阅https://ericpony.github.io/z3py-tutorial/advanced-examples.htm。 SMTLib 中支持数据类型的官方描述见http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2021-05-12.pdf 的第 4.2.3 节。

    请注意,对于您将拥有的每种元组类型,您都必须声明一个新的元组类型。 SMT 不是那种多态的。这个过程通常称为单态化。如果最终您正在建模的语言具有可以“改变”形状的变量(即,简单的 2 元组在赋值后变为 3 元组),您将不得不考虑一个新变量并将其建模为这样的。 (但这与分配一个 int 和一个 boolean 的变量没有什么不同。SMTLib 是“简单类型的”,即所有变量始终具有一个声明的类型。)

    试试这个想法,如果您在发布代码示例时遇到问题。

    处理类型

    如果您有一个类型为IntxBool 的元组(即,第一个组件是Int,第二个是Bool),那么投影函数将是fst : IntxBool -&gt; Intsnd : IntxBool -&gt; Bool。如果您要使用的投影功能不具体,那么您就有问题了。结果是Int,还是Bool

    然而,这与符号执行关系不大。考虑一下如何“类型检查”这样的功能。在类似 Haskell 的符号中:

    index :: (Int, Bool) -> Int -> XX
    index (a, _) 0 = a
    index (_, b) 1 = b
    

    XX 中输入什么?没有办法在简单类型的 lambda 演算中输入这个定义。这就是您所处的情况:SMTLib 本质上是一个简单类型的微积分,所有这些类型都必须在“编译”时解决。

    那么,你如何处理这个问题?最简单的答案是您不允许 允许使用符号值进行索引。这是 Haskell、ML 等语言的立场。但在 Lisp/Scheme/Python 等具有动态类型的语言中,情况并非如此。那么问题就变成了,如何在 SMTLib 等简单类型系统中对这种动态类型语言进行建模。

    到目前为止,您采用的方法是最自然的方法,是将您的 Python 子集浅嵌入到 SMTLib 中。您正在使用 SMTLib 函数/类型来建模您的语言。这是典型的方法,没有任何问题。它更简单,利用了底层语言类型系统和特性的所有特性。如果可能的话,应该是首选。

    这种方法的不足之处在于您的对象语言和元语言功能不匹配:您的对象语言本质上是动态类型的(我假设您遵循 Python 的约定),而 SMTLib 是静态的和简单的类型。这种不匹配意味着您不能再直接使用浅嵌入方法。

    另一种方法是使用一个通用类型来表示您的目标语言中的所有术语。这也称为深度嵌入,您的语言术语本质上成为句法元素。对象语言表达式的类型成为元语言本身的数据类型。显然,这需要做更多的工作,但如果您想在 SMTLib 中编写代码,则非常需要这样做。但请记住,如果您使用 Haskell/ML 等高级类型语言为该子集编写解释器,您会遇到同样的问题:一旦类型策略中的不匹配开始显示,浅嵌入就会中断起来。

    我的建议是根本不允许对元组进行符号索引。仅支持具体索引。当您解决这个系统的怪癖时,您无疑会发现进一步的差异。此时您可以切换到深度嵌入。

    这是一篇值得阅读的好论文(在 Haskell 上下文中),讨论如何使用浅层样式和嵌入式样式对特定领域的语言进行建模。由于您还想支持符号结构,因此您的情况会有所不同,但基本思想适用:http://www.cse.chalmers.se/~josefs/publications/TFP12.pdf

    【讨论】:

    • 非常感谢您的回复!使用元组数据类型时,我会遇到与使用列表时相同的打字问题。根本问题是我的索引和元组可能都是或包含变量,所以我不知道使用哪个访问器。嵌套元组很难定义访问器函数。我在我的问题中添加了描述和示例代码!
    • 我还添加了一个用于创建元组数据类型内容的实用程序函数,以说明我最终想要提出的内容。
    猜你喜欢
    • 2021-06-26
    • 1970-01-01
    • 2023-04-03
    • 2017-09-12
    • 2013-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-14
    相关资源
    最近更新 更多