You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

验证采用类型双关多字节优化的musl libc memchr实现

证明musl libc memchr优化版本的循环不变式方法

我正尝试验证一个采用类型双关(type-punning)多字节优化的musl libc memchr修改版本:该实现先在缓冲区前缀中查找目标值,直到指针对齐到sizeof(size_t);随后通过位操作每次前进sizeof(size_t)字节;最后逐个检查缓冲区后缀。

使用Typed+cast模型时安全属性无问题,但正确性验证遇到困难。以下是存在问题的循环(完整代码见文末):

// Advance i by sizeof(size_t).
  if (SS <= n && i <= (n - SS) && *(src + i) != c) {
    size_t pat = ONES * c;
    /*@
      @ loop assigns i;
      // @ loop invariant \forall integer j; 0 <= j < i ==> *(src + j) != c;
      @ loop variant n - i;
     */
    while (i <= n - SS && !HASZERO(*((size_t *) (src + i)) ^ pat)) {
      i += SS;
    }
    //@ admit \forall integer j; 0 <= j < i ==> *(src + j) != c;
  }

其中SS即sizeof(size_t),ONES是每个字节值为1的size_t类型值,HASZERO用于一次性对比缓冲区的SS字节与模式。当前用admit注解替代了原本想要证明的注释掉的loop invariant断言。

相关宏定义如下:

#define SS (sizeof(size_t))
#define ALIGN (sizeof(size_t)-1)
#define ONES ((size_t)-1/UCHAR_MAX)
#define HIGHS (ONES * (UCHAR_MAX/2+1))
#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS)

验证命令为:

frama-c -no-frama-c-stdlib -wp -wp-rte -wp-model=Typed+cast  mymemchr.c

证明循环不变式的方法

1. 形式化定义HASZERO的语义

首先要让Frama-C/WP理解HASZERO位操作背后的字节级含义。添加如下断言和引理,将位操作结果与“是否存在字节等于目标值”的命题关联:

/*@ 
  predicate has_target_byte(const char *src, size_t pos, unsigned char c) = 
    \exists integer k; 0 <= k < SS && (unsigned char)src[pos + k] == c;
  
  lemma has_target_HASZERO: 
    \forall const char *src, size_t pos, unsigned char c;
    ((size_t)src + pos) % SS == 0 ==>
    (has_target_byte(src, pos, c) <==> HASZERO(*((size_t*)(src+pos)) ^ (ONES * c)) != 0);
*/

这个引理明确:当src+pos对齐时,HASZERO返回非零值等价于src[pos..pos+SS-1]中存在等于c的字节。

2. 强化循环不变式与前置断言

在循环中补充对齐断言,并修复循环不变式的注释,同时在进入循环前添加辅助断言:

if (SS <= n && i <= (n - SS) && *(src + i) != c) {
  size_t pat = ONES * c;
  /*@
    @ assert ((size_t)(src + i)) & ALIGN == 0;
    @ assert \forall integer j; 0 <= j < i ==> *(src + j) != c;
    @ loop assigns i;
    @ loop invariant ((size_t)(src + i)) & ALIGN == 0;
    @ loop invariant \forall integer j; 0 <= j < i ==> *(src + j) != c;
    @ loop variant n - i;
   */
  while (i <= n - SS && !HASZERO(*((size_t *) (src + i)) ^ pat)) {
    /*@
      @ assert !has_target_byte(src, i, c);
      @ assert \forall integer k; 0 <= k < SS ==> *(src + i + k) != c;
     */
    i += SS;
  }
  // 移除admit,用循环不变式推导
}

补充的对齐断言让验证器明确类型转换的合法性,而字节级的辅助断言则连接了位操作和原始数组访问。

3. 利用归纳法推导不变式

循环不变式的证明依赖归纳逻辑:

  • 初始化阶段:进入循环前,前面的对齐循环已经保证\forall j < i; src[j] != c,且src+i对齐、*(src+i) != c,不变式成立。
  • 归纳步骤:假设迭代前不变式成立,循环条件!HASZERO(...)为真,结合has_target_HASZERO引理,可得src[i..i+SS-1]中没有等于c的字节。因此\forall j < i+SS; src[j] != c,迭代后i += SS,不变式依然成立。

4. 优化验证工具参数

调用多个定理证明器提升自动证明成功率,或者指定优先处理自定义引理:

frama-c -no-frama-c-stdlib -wp -wp-rte -wp-model=Typed+cast -wp-prover alt-ergo,z3 -wp-lemma has_target_HASZERO mymemchr.c

完整修改后代码示例

#include <string.h>
#include <stddef.h>
#include <limits.h>

#define SS (sizeof(size_t))
#define ALIGN (sizeof(size_t)-1)
#define ONES ((size_t)-1/UCHAR_MAX)
#define HIGHS (ONES * (UCHAR_MAX/2+1))
#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS)

/*@ 
  predicate has_target_byte(const char *src, size_t pos, unsigned char c) = 
    \exists integer k; 0 <= k < SS && (unsigned char)src[pos + k] == c;
  
  lemma has_target_HASZERO: 
    \forall const char *src, size_t pos, unsigned char c;
    ((size_t)src + pos) % SS == 0 ==>
    (has_target_byte(src, pos, c) <==> HASZERO(*((size_t*)(src+pos)) ^ (ONES * c)) != 0);
*/

/*@
  @ requires s_valid: \valid_read(((const char *)s) + (0 .. n-1));
  @ ensures \result == 0 ||
     ((const char *)s) <= ((const char *)\result) < ((const char *)s) + n;
  @ assigns \result \from s, c, n;
 
  @ behavior not_found:
     assumes \forall integer i; 0 <= i < n ==> *(((const char *)s) + i) != ((unsigned char)c);
     ensures not_found_zero: \result == 0;
  
  @ behavior found:
     assumes \exists integer i; 0 <= i < n && *(((const char *)s) + i) == ((unsigned char)c);
     ensures found_match: *((const char *)\result) == ((unsigned char)c);
     ensures found_in_range:
       ((const char *)s) <= ((const char *)\result) < ((const char *)s) + n;
     ensures found_earliest: \forall integer i;
       ((const char *)s) <= ((const char *)s) + i < ((const char *)\result) ==>
         *(((const char *)s) + i) != ((unsigned char)c);
  
  @ complete behaviors;
  @ disjoint behaviors;
 */
void *
memchr(const void *s, int c, size_t n) {
  const char *src = (char *) s;
  c = (unsigned char) c;
  size_t i = 0;

#ifdef __GNUC__

  // Advance i until aligned on sizeof(size_t).
  /*@
    @ loop assigns i;
    @ loop invariant \forall integer j; 0 <= j < i ==> *(src + j) != c;
    @ loop variant n - i;
   */
  for (; i < n && ((size_t)(src+i)) & ALIGN && *(src + i) != c; i++);
  //@ assert i >= n || *(src + i) == c || (((size_t)(src+i)) & ALIGN) == 0;

  // Advance i by sizeof(size_t).
  if (SS <= n && i <= (n - SS) && *(src + i) != c) {
    size_t pat = ONES * c;
    /*@
      @ assert ((size_t)(src + i)) & ALIGN == 0;
      @ assert \forall integer j; 0 <= j < i ==> *(src + j) != c;
      @ loop assigns i;
      @ loop invariant ((size_t)(src + i)) & ALIGN == 0;
      @ loop invariant \forall integer j; 0 <= j < i ==> *(src + j) != c;
      @ loop variant n - i;
     */
    while (i <= n - SS && !HASZERO(*((size_t *) (src + i)) ^ pat)) {
      /*@
        @ assert !has_target_byte(src, i, c);
        @ assert \forall integer k; 0 <= k < SS ==> *(src + i + k) != c;
       */
      i += SS;
    }
  }

#endif

  /*@
    @ loop assigns i;
    @ loop invariant \forall integer j; 0 <= j < i ==> *(src + j) != c;
    @ loop variant n - i;
   */
  for (; i < n && *(src + i) != c; i++);
  return (i >= n) ? 0 : ((void *) src + i);
}

内容的提问来源于stack exchange,提问作者Tommy McGuire

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 01:17:37