[GSOC 2026] Implementation of kf_strnlen and follow-up on Testsuite

Saish Kambali via Gcc <[email protected]>
Newsgroups gmane.comp.gcc.devel
Message-ID <CAKv9=92vn_n1iRucFn3JG_uTQS3nUfA4OYLPg8yp-nGYHLHFcg@mail.gmail.com>
Hi David,

I am writing to share my current progress on the strnlen implementation in
kf.cc.

I fully referenced and understood the implementation of the strncpy() from
which I have implemented kf_strnlen by bifurcating the state into two, to
match function definition with unbounded test (where \0 is not present)
also.

1) Truncated read: This checks upto limit n and added constraint for
terminator if exists > n.
2) Full read: I added constraint here if \0 is found within <= n.

Also, added a safety check at the end model->read_bytes to look for buffer
over-flow or poison bytes.

I have a question, strnlen return (size - 1), so I used MINUS_EXPR,  with
lhs_type(), see if the user wrote strnlen(ptr, 5), instead of size_t len =
strnlen(ptr, 5); so would it be valid type to prevent a crash? Is there any
standard way gcc follows for void calls or ignored return values?

Looking for your guidance. I have send you a test suite earlier on mailing
list and also the current kf_strnlen implementation code I have tried is
here:

------------------------



/* Handles strnlen by splitting into two outcomes:
a) Truncated read: The limit 'n' is reached before the null-terminator,
in this case the result is 'n'.
b) Non-truncated read: The null-terminator is found before or at the limit
n,
in this case the result is the number of bytes read before the null
terminator*/

class kf_strnlen : public builtin_known_function
{
public:
  bool matches_call_types_p (const call_details &cd) const final override
  {
    return (cd.num_args () == 2
      && cd.arg_is_pointer_p (0)
      && cd.arg_is_integral_p (1));
  }
  enum built_in_function builtin_code () const final override
  {
    return BUILT_IN_STRNLEN;
  }
  void impl_call_post (const call_details &cd) const final override;
};

void
kf_strnlen::impl_call_post (const call_details &cd) const
{
    class strnlen_call_info : public call_info
    {
    public:
      strnlen_call_info(const call_details &cd,
             const svalue *num_bytes_with_terminator_sval,
            bool truncated_read)
    : call_info (cd),
      m_num_bytes_with_terminator_sval (num_bytes_with_terminator_sval),
      m_truncated_read (truncated_read)
    {
    }

    void print_desc (pretty_printer &pp) const final override
    {
      if (m_truncated_read)
   pp_printf (&pp,
        "when %qE reaches the maximum limit",
        get_fndecl ());
      else
   pp_printf (&pp,
         "when %qE finds the null terminator",
          get_fndecl ());
    }

    bool update_model (region_model *model,
           const exploded_edge *,
           region_model_context *ctxt) const final override
    {
      const call_details cd (get_call_details (model, ctxt));

      const svalue *src_sval = cd.get_arg_svalue (0);  /* Argument 0 is the
 string */
      const region *src_reg
  = model->deref_rvalue (src_sval, cd.get_arg_tree (0), ctxt);
      const svalue *limit_n_sval = cd.get_arg_svalue (1); /* Argument 1 is
the limit n */

      const svalue *result_sval = nullptr; /*Symbolic value for return
value of the call*/

      /*Limit(n) reached before finding null-terminator*/
      if (m_truncated_read)
  {
     result_sval = limit_n_sval;  /* it will return n */

    if (m_num_bytes_with_terminator_sval)  /* is false for poisoned values
*/
      {
        /* the null-terminator is not in bounds of n */
        if (!model->add_constraint (m_num_bytes_with_terminator_sval,
            GT_EXPR,
            limit_n_sval,
            ctxt))
      return false;
      }
    else
      {

      }
  }
      /* found the null-terminator within or on the limit 'n' */
      else
  {

    if (m_num_bytes_with_terminator_sval)
      {
        /* the string length is less than or equal to n */
        if (!model->add_constraint (m_num_bytes_with_terminator_sval,
            LE_EXPR,
            limit_n_sval,
            ctxt))
    return false;

    /* since the strnlen() returns the count of bytes excluding \0 (size -
1)
    it should return count excluding \0 */
        result_sval
  = model->get_manager ()->get_or_create_binop (cd.lhs_type (),
    MINUS_EXPR,
    m_num_bytes_with_terminator_sval,
    model->get_manager ()->get_or_create_int_svalue (cd.lhs_type (), 1));

      }
      else
      {
        return false; /* this bifurcation part is removed once falsed */
      }
  }

  /* this sets the return value */
  if (result_sval)
  {
    cd.maybe_set_lhs (result_sval);
  }
  else
  {

  }

  /* this checks for buffer over read, and poison bytes as a safety check */
  if(result_sval)
  {
  model->read_bytes (src_reg,
  cd.get_arg_tree (0),
  result_sval,
  ctxt);
  }

      return true;
    }


  private:

  /*this symbolic value representing the number of bytes read upto the
first non-terminating character,
  if it does not find one it will be NULL */
    const svalue *m_num_bytes_with_terminator_sval;

  /* if true: we are simulating the 'truncated read'
     if false: we are simulating the 'Full read'*/
    bool m_truncated_read;

    };

    /* Body of kf_strnlen::impl_call_post. */
    if (cd.get_ctxt ())
      {
        /*First, scan for a null terminator as if there were no limit,
        with a null ctxt so no errors reported */
        const region_model *model = cd.get_model ();
        const svalue *ptr_arg_sval = cd.get_arg_svalue (0);
        const region *buf_reg
    = model->deref_rvalue (ptr_arg_sval, cd.get_arg_tree (0), nullptr);
        const svalue *num_bytes_with_terminator_sval
    = model->scan_for_null_terminator (buf_reg,
               cd.get_arg_tree (0),
               nullptr,
               nullptr);
        cd.get_ctxt ()->bifurcate
    (std::make_unique<strnlen_call_info>
        (cd, num_bytes_with_terminator_sval,
         false));
        cd.get_ctxt ()->bifurcate
    (std::make_unique<strnlen_call_info>
        (cd, num_bytes_with_terminator_sval,
         true));
        cd.get_ctxt ()->terminate_path ();
      }
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.