]> git.ipfire.org Git - thirdparty/bird.git/blobdiff - filter/f-inst.c
Filter: Add support for src/dst accessors for Flowspec and SADR
[thirdparty/bird.git] / filter / f-inst.c
index f13048332479a92d836e056415f5863071de975f..e3e0d76d9dafa1fd02342540eb72a77902b10f24 100644 (file)
  *     m4_dnl  (103)     [[ put it here ]]
  *     m4_dnl            ...
  *     m4_dnl            if (all arguments are constant)
- *     m4_dnl  (108)       [[ put it here ]]       
+ *     m4_dnl  (108)       [[ put it here ]]
  *     m4_dnl          }
  *     m4_dnl  For writing directly to constructor argument list, use FID_NEW_ARGS.
  *     m4_dnl  For computing something in constructor (103), use FID_NEW_BODY.
   }
   INST(FI_AND, 1, 1) {
     ARG(1,T_BOOL);
+    ARG_TYPE(2,T_BOOL);
+    RESULT_TYPE(T_BOOL);
+
     if (v1.val.i)
       LINE(2,0);
     else
   }
   INST(FI_OR, 1, 1) {
     ARG(1,T_BOOL);
+    ARG_TYPE(2,T_BOOL);
+    RESULT_TYPE(T_BOOL);
+
     if (!v1.val.i)
       LINE(2,0);
     else
     ARG_ANY(1);
     ARG(2, T_INT);
 
-    FID_MEMBER(enum ec_subtype, ecs, f1->ecs != f2->ecs, ec subtype %s, ec_subtype_str(item->ecs));
+    FID_MEMBER(enum ec_subtype, ecs, f1->ecs != f2->ecs, "ec subtype %s", ec_subtype_str(item->ecs));
 
     int check, ipv4_used;
     u32 key, val;
        case T_PATH_MASK_ITEM:
          pm->item[i] = vv(i).val.pmi;
          break;
+
        case T_INT:
          pm->item[i] = (struct f_path_mask_item) {
            .asn = vv(i).val.i,
            .kind = PM_ASN,
          };
          break;
+
+       case T_SET:
+         if (vv(i).val.t->from.type != T_INT)
+           runtime("Only integer sets allowed in path mask");
+
+         pm->item[i] = (struct f_path_mask_item) {
+           .set = vv(i).val.t,
+           .kind = PM_ASN_SET,
+         };
+         break;
+
        default:
          runtime( "Error resolving path mask template: value not an integer" );
       }
   INST(FI_LT, 2, 1) {
     ARG_ANY(1);
     ARG_ANY(2);
+    ARG_SAME_TYPE(1, 2);
+
     int i = val_compare(&v1, &v2);
     if (i == F_CMP_ERROR)
       runtime( "Can't compare values of incompatible types" );
   INST(FI_LTE, 2, 1) {
     ARG_ANY(1);
     ARG_ANY(2);
+    ARG_SAME_TYPE(1, 2);
+
     int i = val_compare(&v1, &v2);
     if (i == F_CMP_ERROR)
       runtime( "Can't compare values of incompatible types" );
     NEVER_CONSTANT;
     ARG_ANY(1);
     SYMBOL;
-
-    if ((sym->class != (SYM_VARIABLE | v1.type)) && (v1.type != T_VOID))
-    {
-      /* IP->Quad implicit conversion */
-      if ((sym->class == (SYM_VARIABLE | T_QUAD)) && val_is_ip4(&v1))
-       v1 = (struct f_val) {
-         .type = T_QUAD,
-         .val.i = ipa_to_u32(v1.val.ip),
-       };
-      else
-       runtime( "Assigning to variable of incompatible type" );
-    }
+    ARG_TYPE(1, sym->class & 0xff);
 
     fstk->vstk[curline.vbase + sym->offset] = v1;
   }
   INST(FI_VAR_GET, 0, 1) {
     SYMBOL;
     NEVER_CONSTANT;
+    RESULT_TYPE(sym->class & 0xff);
     RESULT_VAL(fstk->vstk[curline.vbase + sym->offset]);
   }
 
       struct f_val,
       val,
       [[ !val_same(&(f1->val), &(f2->val)) ]],
-      value %s,
+      "value %s",
       val_dump(&(item->val))
     );
 
+    RESULT_TYPE(val.type);
     RESULT_VAL(val);
   }
 
        val_format(&(vv(i)), &fs->buf);
   }
 
-  INST(FI_DIE, 0, 0) {
+  INST(FI_FLUSH, 0, 0) {
     NEVER_CONSTANT;
-    FID_MEMBER(enum filter_return, fret, f1->fret != f2->fret, %s, filter_return_str(item->fret));
-
-    if (fs->buf.start < fs->buf.pos)
+    if (!(fs->flags & FF_SILENT))
+      /* After log_commit, the buffer is reset */
       log_commit(*L_INFO, &fs->buf);
+  }
+
+  INST(FI_DIE, 0, 0) {
+    NEVER_CONSTANT;
+    FID_MEMBER(enum filter_return, fret, f1->fret != f2->fret, "%s", filter_return_str(item->fret));
 
     switch (whati->fret) {
     case F_QUITBIRD:
       die( "Filter asked me to die" );
-    case F_ACCEPT:
-      /* Should take care about turning ACCEPT into MODIFY */
+    case F_ACCEPT:     /* Should take care about turning ACCEPT into MODIFY */
     case F_ERROR:
-    case F_REJECT:     /* FIXME (noncritical) Should print complete route along with reason to reject route */
+    case F_REJECT:     /* Maybe print complete route along with reason to reject route? */
       return fret;     /* We have to return now, no more processing. */
-    case F_NOP:
-      break;
     default:
       bug( "unknown return type: Can't happen");
     }
     ACCESS_RTE;
     ARG_ANY(1);
     STATIC_ATTR;
-    if (sa.f_type != v1.type)
-      runtime( "Attempt to set static attribute to incompatible type" );
+    ARG_TYPE(1, sa.f_type);
 
     f_rta_cow(fs);
     {
     DYNAMIC_ATTR;
     ACCESS_RTE;
     ACCESS_EATTRS;
+    RESULT_TYPE(da.f_type);
     {
       eattr *e = ea_find(*fs->eattrs, da.ea_code);
 
       if (!e) {
        /* A special case: undefined as_path looks like empty as_path */
        if (da.type == EAF_TYPE_AS_PATH) {
-         RESULT(T_PATH, ad, &null_adata);
+         RESULT_(T_PATH, ad, &null_adata);
          break;
        }
 
        /* The same special case for int_set */
        if (da.type == EAF_TYPE_INT_SET) {
-         RESULT(T_CLIST, ad, &null_adata);
+         RESULT_(T_CLIST, ad, &null_adata);
          break;
        }
 
        /* The same special case for ec_set */
        if (da.type == EAF_TYPE_EC_SET) {
-         RESULT(T_ECLIST, ad, &null_adata);
+         RESULT_(T_ECLIST, ad, &null_adata);
          break;
        }
 
        /* The same special case for lc_set */
        if (da.type == EAF_TYPE_LC_SET) {
-         RESULT(T_LCLIST, ad, &null_adata);
+         RESULT_(T_LCLIST, ad, &null_adata);
          break;
        }
 
 
       switch (e->type & EAF_TYPE_MASK) {
       case EAF_TYPE_INT:
-       RESULT(da.f_type, i, e->u.data);
+       RESULT_(da.f_type, i, e->u.data);
        break;
       case EAF_TYPE_ROUTER_ID:
-       RESULT(T_QUAD, i, e->u.data);
+       RESULT_(T_QUAD, i, e->u.data);
        break;
       case EAF_TYPE_OPAQUE:
-       RESULT(T_ENUM_EMPTY, i, 0);
+       RESULT_(T_ENUM_EMPTY, i, 0);
        break;
       case EAF_TYPE_IP_ADDRESS:
-       RESULT(T_IP, ip, *((ip_addr *) e->u.ptr->data));
+       RESULT_(T_IP, ip, *((ip_addr *) e->u.ptr->data));
        break;
       case EAF_TYPE_AS_PATH:
-       RESULT(T_PATH, ad, e->u.ptr);
+       RESULT_(T_PATH, ad, e->u.ptr);
        break;
       case EAF_TYPE_BITFIELD:
-       RESULT(T_BOOL, i, !!(e->u.data & (1u << da.bit)));
+       RESULT_(T_BOOL, i, !!(e->u.data & (1u << da.bit)));
        break;
       case EAF_TYPE_INT_SET:
-       RESULT(T_CLIST, ad, e->u.ptr);
+       RESULT_(T_CLIST, ad, e->u.ptr);
        break;
       case EAF_TYPE_EC_SET:
-       RESULT(T_ECLIST, ad, e->u.ptr);
+       RESULT_(T_ECLIST, ad, e->u.ptr);
        break;
       case EAF_TYPE_LC_SET:
-       RESULT(T_LCLIST, ad, e->u.ptr);
+       RESULT_(T_LCLIST, ad, e->u.ptr);
        break;
       case EAF_TYPE_UNDEF:
        RESULT_VOID;
     ACCESS_EATTRS;
     ARG_ANY(1);
     DYNAMIC_ATTR;
+    ARG_TYPE(1, da.f_type);
     {
       struct ea_list *l = lp_alloc(fs->pool, sizeof(struct ea_list) + sizeof(eattr));
 
 
       switch (da.type) {
       case EAF_TYPE_INT:
-       if (v1.type != da.f_type)
-         runtime( "Setting int attribute to non-int value" );
-       l->attrs[0].u.data = v1.val.i;
-       break;
-
       case EAF_TYPE_ROUTER_ID:
-       /* IP->Quad implicit conversion */
-       if (val_is_ip4(&v1)) {
-         l->attrs[0].u.data = ipa_to_u32(v1.val.ip);
-         break;
-       }
-       /* T_INT for backward compatibility */
-       if ((v1.type != T_QUAD) && (v1.type != T_INT))
-         runtime( "Setting quad attribute to non-quad value" );
        l->attrs[0].u.data = v1.val.i;
        break;
 
        runtime( "Setting opaque attribute is not allowed" );
        break;
 
-      case EAF_TYPE_IP_ADDRESS:
-       if (v1.type != T_IP)
-         runtime( "Setting ip attribute to non-ip value" );
+      case EAF_TYPE_IP_ADDRESS:;
        int len = sizeof(ip_addr);
        struct adata *ad = lp_alloc(fs->pool, sizeof(struct adata) + len);
        ad->length = len;
        break;
 
       case EAF_TYPE_AS_PATH:
-       if (v1.type != T_PATH)
-         runtime( "Setting path attribute to non-path value" );
+      case EAF_TYPE_INT_SET:
+      case EAF_TYPE_EC_SET:
+      case EAF_TYPE_LC_SET:
        l->attrs[0].u.ptr = v1.val.ad;
        break;
 
       case EAF_TYPE_BITFIELD:
-       if (v1.type != T_BOOL)
-         runtime( "Setting bit in bitfield attribute to non-bool value" );
        {
          /* First, we have to find the old value */
          eattr *e = ea_find(*fs->eattrs, da.ea_code);
        }
        break;
 
-      case EAF_TYPE_INT_SET:
-       if (v1.type != T_CLIST)
-         runtime( "Setting clist attribute to non-clist value" );
-       l->attrs[0].u.ptr = v1.val.ad;
-       break;
-
-      case EAF_TYPE_EC_SET:
-       if (v1.type != T_ECLIST)
-         runtime( "Setting eclist attribute to non-eclist value" );
-       l->attrs[0].u.ptr = v1.val.ad;
-       break;
-
-      case EAF_TYPE_LC_SET:
-       if (v1.type != T_LCLIST)
-         runtime( "Setting lclist attribute to non-lclist value" );
-       l->attrs[0].u.ptr = v1.val.ad;
-       break;
-
       default:
        bug("Unknown dynamic attribute type");
       }
     }
   }
 
-  INST(FI_SADR_SRC, 1, 1) {    /* Get SADR src prefix */
+  INST(FI_NET_SRC, 1, 1) {     /* Get src prefix */
     ARG(1, T_NET);
-    if (!net_is_sadr(v1.val.net))
-      runtime( "SADR expected" );
 
-    net_addr_ip6_sadr *net = (void *) v1.val.net;
+    net_addr_union *net = (void *) v1.val.net;
     net_addr *src = falloc(sizeof(net_addr_ip6));
-    net_fill_ip6(src, net->src_prefix, net->src_pxlen);
+    const byte *part;
+
+    switch(v1.val.net->type) {
+    case NET_FLOW4:
+      part = flow4_get_part(&net->flow4, FLOW_TYPE_SRC_PREFIX);
+      if (part)
+       net_fill_ip4(src, flow_read_ip4_part(part), flow_read_pxlen(part));
+      else
+       net_fill_ip4(src, IP4_NONE, 0);
+      break;
+
+    case NET_FLOW6:
+      part = flow6_get_part(&net->flow6, FLOW_TYPE_SRC_PREFIX);
+      if (part)
+       net_fill_ip6(src, flow_read_ip6_part(part), flow_read_pxlen(part));
+      else
+       net_fill_ip6(src, IP6_NONE, 0);
+      break;
+
+    case NET_IP6_SADR:
+      net_fill_ip6(src, net->ip6_sadr.src_prefix, net->ip6_sadr.src_pxlen);
+      break;
+
+    default:
+      runtime( "Flow or SADR expected" );
+    }
 
     RESULT(T_NET, net, src);
   }
 
+  INST(FI_NET_DST, 1, 1) {     /* Get dst prefix */
+    ARG(1, T_NET);
+
+    net_addr_union *net = (void *) v1.val.net;
+    net_addr *dst = falloc(sizeof(net_addr_ip6));
+    const byte *part;
+
+    switch(v1.val.net->type) {
+    case NET_FLOW4:
+      part = flow4_get_part(&net->flow4, FLOW_TYPE_DST_PREFIX);
+      if (part)
+       net_fill_ip4(dst, flow_read_ip4_part(part), flow_read_pxlen(part));
+      else
+       net_fill_ip4(dst, IP4_NONE, 0);
+      break;
+
+    case NET_FLOW6:
+      part = flow6_get_part(&net->flow6, FLOW_TYPE_DST_PREFIX);
+      if (part)
+       net_fill_ip6(dst, flow_read_ip6_part(part), flow_read_pxlen(part));
+      else
+       net_fill_ip6(dst, IP6_NONE, 0);
+      break;
+
+    case NET_IP6_SADR:
+      net_fill_ip6(dst, net->ip6_sadr.dst_prefix, net->ip6_sadr.dst_pxlen);
+      break;
+
+    default:
+      runtime( "Flow or SADR expected" );
+    }
+
+    RESULT(T_NET, net, dst);
+  }
+
   INST(FI_ROA_MAXLEN, 1, 1) {  /* Get ROA max prefix length */
     ARG(1, T_NET);
     if (!net_is_roa(v1.val.net))
 
   INST(FI_AS_PATH_FIRST, 1, 1) {       /* Get first ASN from AS PATH */
     ARG(1, T_PATH);
-    int as = 0;
+    u32 as = 0;
     as_path_get_first(v1.val.ad, &as);
     RESULT(T_INT, i, as);
   }
 
   INST(FI_AS_PATH_LAST, 1, 1) {                /* Get last ASN from AS PATH */
     ARG(1, T_PATH);
-    int as = 0;
+    u32 as = 0;
     as_path_get_last(v1.val.ad, &as);
     RESULT(T_INT, i, as);
   }
     NEVER_CONSTANT;
     SYMBOL;
 
+    FID_SAME_BODY()
+      if (!(f2->sym->flags & SYM_FLAG_SAME))
+       return 0;
+    FID_INTERPRET_BODY()
+
     /* Push the body on stack */
     LINEX(sym->function);
     curline.emask |= FE_RETURN;
   INST(FI_SWITCH, 1, 0) {
     ARG_ANY(1);
 
-    FID_MEMBER(struct f_tree *, tree, [[!same_tree(f1->tree, f2->tree)]], tree %p, item->tree);
+    FID_MEMBER(struct f_tree *, tree, [[!same_tree(f1->tree, f2->tree)]], "tree %p", item->tree);
 
     const struct f_tree *t = find_tree(tree, &v1);
     if (!t) {
   INST(FI_CLIST_ADD, 2, 1) {   /* (Extended) Community list add */
     ARG_ANY(1);
     ARG_ANY(2);
+    RESULT_TYPE(f1->type);
+
     if (v1.type == T_PATH)
       runtime("Can't add to path");
 
       struct f_val dummy;
 
       if ((v2.type == T_PAIR) || (v2.type == T_QUAD))
-       RESULT(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, v2.val.i) ]]);
+       RESULT_(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, v2.val.i) ]]);
       /* IP->Quad implicit conversion */
       else if (val_is_ip4(&v2))
-       RESULT(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
+       RESULT_(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
       else if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy))
        runtime("Can't add set");
       else if (v2.type == T_CLIST)
-       RESULT(T_CLIST, ad, [[ int_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
+       RESULT_(T_CLIST, ad, [[ int_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
       else
        runtime("Can't add non-pair");
     }
       if ((v2.type == T_SET) && eclist_set_type(v2.val.t))
        runtime("Can't add set");
       else if (v2.type == T_ECLIST)
-       RESULT(T_ECLIST, ad, [[ ec_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
+       RESULT_(T_ECLIST, ad, [[ ec_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
       else if (v2.type != T_EC)
        runtime("Can't add non-ec");
       else
-       RESULT(T_ECLIST, ad, [[ ec_set_add(fpool, v1.val.ad, v2.val.ec) ]]);
+       RESULT_(T_ECLIST, ad, [[ ec_set_add(fpool, v1.val.ad, v2.val.ec) ]]);
     }
 
     else if (v1.type == T_LCLIST)
       if ((v2.type == T_SET) && lclist_set_type(v2.val.t))
        runtime("Can't add set");
       else if (v2.type == T_LCLIST)
-       RESULT(T_LCLIST, ad, [[ lc_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
+       RESULT_(T_LCLIST, ad, [[ lc_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
       else if (v2.type != T_LC)
        runtime("Can't add non-lc");
       else
-       RESULT(T_LCLIST, ad, [[ lc_set_add(fpool, v1.val.ad, v2.val.lc) ]]);
+       RESULT_(T_LCLIST, ad, [[ lc_set_add(fpool, v1.val.ad, v2.val.lc) ]]);
 
     }
 
   INST(FI_CLIST_DEL, 2, 1) {   /* (Extended) Community list add or delete */
     ARG_ANY(1);
     ARG_ANY(2);
+    RESULT_TYPE(f1->type);
+
     if (v1.type == T_PATH)
     {
       const struct f_tree *set = NULL;
       else
        runtime("Can't delete non-integer (set)");
 
-      RESULT(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, set, key, 0) ]]);
+      RESULT_(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, set, key, 0) ]]);
     }
 
     else if (v1.type == T_CLIST)
       struct f_val dummy;
 
       if ((v2.type == T_PAIR) || (v2.type == T_QUAD))
-       RESULT(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, v2.val.i) ]]);
+       RESULT_(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, v2.val.i) ]]);
       /* IP->Quad implicit conversion */
       else if (val_is_ip4(&v2))
-       RESULT(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
+       RESULT_(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
       else if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy) || (v2.type == T_CLIST))
-       RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+       RESULT_(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 0) ]]);
       else
        runtime("Can't delete non-pair");
     }
     {
       /* v2.val is either EC or EC-set */
       if ((v2.type == T_SET) && eclist_set_type(v2.val.t) || (v2.type == T_ECLIST))
-       RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+       RESULT_(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
       else if (v2.type != T_EC)
        runtime("Can't delete non-ec");
       else
-       RESULT(T_ECLIST, ad, [[ ec_set_del(fpool, v1.val.ad, v2.val.ec) ]]);
+       RESULT_(T_ECLIST, ad, [[ ec_set_del(fpool, v1.val.ad, v2.val.ec) ]]);
     }
 
     else if (v1.type == T_LCLIST)
     {
       /* v2.val is either LC or LC-set */
       if ((v2.type == T_SET) && lclist_set_type(v2.val.t) || (v2.type == T_LCLIST))
-       RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+       RESULT_(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
       else if (v2.type != T_LC)
        runtime("Can't delete non-lc");
       else
-       RESULT(T_LCLIST, ad, [[ lc_set_del(fpool, v1.val.ad, v2.val.lc) ]]);
+       RESULT_(T_LCLIST, ad, [[ lc_set_del(fpool, v1.val.ad, v2.val.lc) ]]);
     }
 
     else
   INST(FI_CLIST_FILTER, 2, 1) {        /* (Extended) Community list add or delete */
     ARG_ANY(1);
     ARG_ANY(2);
+    RESULT_TYPE(f1->type);
+
     if (v1.type == T_PATH)
     {
       u32 key = 0;
 
       if ((v2.type == T_SET) && (v2.val.t->from.type == T_INT))
-       RESULT(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, v2.val.t, key, 1) ]]);
+       RESULT_(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, v2.val.t, key, 1) ]]);
       else
        runtime("Can't filter integer");
     }
       struct f_val dummy;
 
       if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy) || (v2.type == T_CLIST))
-       RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+       RESULT_(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
       else
        runtime("Can't filter pair");
     }
     {
       /* v2.val is either EC or EC-set */
       if ((v2.type == T_SET) && eclist_set_type(v2.val.t) || (v2.type == T_ECLIST))
-       RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+       RESULT_(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
       else
        runtime("Can't filter ec");
     }
     {
       /* v2.val is either LC or LC-set */
       if ((v2.type == T_SET) && lclist_set_type(v2.val.t) || (v2.type == T_LCLIST))
-       RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+       RESULT_(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
       else
        runtime("Can't filter lc");
     }
     NEVER_CONSTANT;
     ARG(1, T_BOOL);
 
-    FID_MEMBER(char *, s, [[strcmp(f1->s, f2->s)]], string %s, item->s);
+    FID_MEMBER(char *, s, [[strcmp(f1->s, f2->s)]], "string %s", item->s);
 
     ASSERT(s);