← Back to context

Comment by MrBuddyCasino

11 days ago

The heart of the matter:

  struct Q<T: 'static>(Option<T>);

  impl<T: 'static> Drop for Q<T> {
    fn drop(&mut self) {
        // Code in here has access to a non-'static &String
        // but assumes that it's a &'static String
    }
  }

  fn main() {
    let l = String::new();
    let mut q = Q(None);
    q.0 = Some(&l);
  }

I wonder if the certified compiler for safety critical uses (edit: Ferrocene) has the same issue.

> I wonder if the certified compiler for safety critical uses has the same issue.

It uses the same code, so yes.

(Although I don't know what are their guidelines, it might be that they require avoiding code relying on implied bounds, for example).

Can someone ELI5 for people who don't know Rust (but e.g. C)?

  • Rust is supposed to guarantee statically that a reference cannot outlive its referent. The `T: 'static` bound means that the value that Q holds must have a 'static lifetime (e.g. either be an owned value or a reference to a static value). So this program should not compile since a reference to l is not 'static. This code is unsound because rust will drop the string l before q so when the Q destructor runs the underlying string has already been freed yet we have a valid reference to it.

    • So the equivalent in C would be:

              $ cat main.c
              #include <stdlib.h>
              #include <string.h>
      
              struct Q {
                      /*@null@*/ /*@only@*/ char * t;
              };
      
      
              /*@only@*/
              struct Q *
              Q_new (/*@only@*/ char * t)
              {
                      struct Q * q = malloc (sizeof *q);
                      q->t = t;
                      return q;
              }
      
              void
              Q_set (struct Q * q, /*@only@*/ char * t)
              {
                      free (q->t);
                      q->t = t;
                      return;
              }
      
              void
              Q_free (/*@only@*/ struct Q * q)
              {
                      free (q->t);
                      free (q);
                      return;
              }
      
      
      
              int
              main (void)
              {
                      /*@only@*/ char * l;
                      /*@only@*/ struct Q * q;
      
                      l = strdup ("");
                      q = Q_new (NULL);
      
                      Q_set (q, l);
                      free (l);
      
                      /* This is UB */
                      Q_free (q);
      
                      return EXIT_SUCCESS;
              }
      
              $ splint main.c
              Splint 3.1.2 --- 21 Feb 2021
      
              main.c: (in function Q_new)
              main.c:15:3: Arrow access from possibly null pointer q: q->t
                A possibly null pointer is dereferenced.  Value is either the result of a
                function which may return null (in which case, code should check it is not
                null), or a global, parameter or structure field declared with the null
                qualifier. (Use -nullderef to inhibit warning)
                 main.c:14:17: Storage q may become null
              main.c: (in function main)
              main.c:43:6: Unrecognized identifier: strdup
                Identifier used in code has not been declared. (Use -unrecog to inhibit
                warning)
              main.c:44:13: Null storage passed as non-null param: Q_new (NULL)
                A possibly null pointer is passed as a parameter corresponding to a formal
                parameter with no /*@null@*/ annotation.  If NULL may be used for this
                parameter, add a /*@null@*/ annotation to the function parameter declaration.
                (Use -nullpass to inhibit warning)
              main.c:47:8: Dead storage l passed as out parameter to free: l
                Memory is used after it has been released (either by passing as an only param
                or assigning to an only global). (Use -usereleased to inhibit warning)
                 main.c:46:12: Storage l released
              main.c:12:1: Function exported but not used outside main: Q_new
                A declaration is exported, but not used outside this module. Declaration can
                use static qualifier. (Use -exportlocal to inhibit warning)
                 main.c:17:1: Definition of Q_new
              main.c:20:1: Function exported but not used outside main: Q_set
                 main.c:25:1: Definition of Q_set
              main.c:28:1: Function exported but not used outside main: Q_free
                 main.c:33:1: Definition of Q_free
      
              Finished checking --- 7 code warnings
      

      (The equivalent warning is main.c:47)

      5 replies →

  • 'static lifetime bounds are kind of confusing in Rust, but here goes: A 'static lifetime constraint on T says: types that satisfy T must not contain any borrowed value/content whose lifetime will be shorter than the life of the program. So suppose you have a string s, and Option<&String> q. If q has the constraint 'static, and you do q = Some(&s) this should be rejected by the compiler since a reference to s gets a shorter lifetime than 'static by default (you could do q = Some(&'static s) if s is say a string literal or a leaked box but not for a normal string). In the examples it looks like you have to use a tuple to trick the borrow checker but the concept is the same: Rust allowed violation of the 'static lifetime bounds. The drop implemenation demonstrates it: drop happens in reverse declaration order, so the string s gets dropped first. Now q has a dangling reference, a pointer to dropped memory essentially, and we get a pretty classic use after free bug.

DeepSeek v4.1 Flash thinks it should NOT compile. Whereas in reality it DOES compile which is why it is filed as a bug.

  • They're seriously using AI as a judge for what to file?

    • The issue and none of the comments mention AI. It is pretty clear your parent said it by themselves.

    • I was just checking what outcome an LLM would have based on the patterns in its probabilistic distribution it has seen the wild.

      Most LLMs would probably say this piece of code won't compile.

      1 reply →