@@ -334,26 +334,41 @@ impl<'i> Formatter<'i, Query> {
334
334
fn with_prefix < F > ( & mut self , prefix : & str , f : F ) -> fmt:: Result
335
335
where F : FnOnce ( & mut Self ) -> fmt:: Result
336
336
{
337
- // The `prefix` string is pushed in a `StackVec` for use by recursive
338
- // (nested) calls to `write_raw`. The string is pushed here and then
339
- // popped here. `self.prefixes` is modified nowhere else, and no strings
340
- // leak from the the vector. As a result, it is impossible for a
341
- // `prefix` to be accessed incorrectly as:
342
- //
343
- // * Rust _guarantees_ it exists for the lifetime of this method
344
- // * it is only reachable while this method's stack is active because
345
- // it is popped before this method returns
346
- // * thus, at any point that it's reachable, it's valid
347
- //
348
- // Said succinctly: this `prefixes` stack shadows a subset of the
349
- // `with_prefix` stack precisely, making it reachable to other code.
350
- let prefix: & ' static str = unsafe { :: std:: mem:: transmute ( prefix) } ;
351
-
352
- self . prefixes . push ( prefix) ;
353
- let result = f ( self ) ;
354
- self . prefixes . pop ( ) ;
337
+ struct PrefixGuard < ' f , ' i > ( & ' f mut Formatter < ' i , Query > ) ;
355
338
356
- result
339
+ impl < ' f , ' i > PrefixGuard < ' f , ' i > {
340
+ fn new ( prefix : & str , f : & ' f mut Formatter < ' i , Query > ) -> Self {
341
+ // SAFETY: The `prefix` string is pushed in a `StackVec` for use
342
+ // by recursive (nested) calls to `write_raw`. The string is
343
+ // pushed in `PrefixGuard` here and then popped in `Drop`.
344
+ // `prefixes` is modified nowhere else, and no concrete-lifetime
345
+ // strings leak from the the vector. As a result, it is
346
+ // impossible for a `prefix` to be accessed incorrectly as:
347
+ //
348
+ // * Rust _guarantees_ `prefix` is valid for this method
349
+ // * `prefix` is only reachable while this method's stack is
350
+ // active because it is unconditionally popped before this
351
+ // method returns via `PrefixGuard::drop()`.
352
+ // * should a panic occur in `f()`, `PrefixGuard::drop()` is
353
+ // still called (or the program aborts), ensuring `prefix`
354
+ // is no longer in `prefixes` and thus inaccessible.
355
+ // * thus, at any point `prefix` is reachable, it is valid
356
+ //
357
+ // Said succinctly: `prefixes` shadows a subset of the
358
+ // `with_prefix` stack, making it reachable to other code.
359
+ let prefix = unsafe { std:: mem:: transmute ( prefix) } ;
360
+ f. prefixes . push ( prefix) ;
361
+ PrefixGuard ( f)
362
+ }
363
+ }
364
+
365
+ impl Drop for PrefixGuard < ' _ , ' _ > {
366
+ fn drop ( & mut self ) {
367
+ self . 0 . prefixes . pop ( ) ;
368
+ }
369
+ }
370
+
371
+ f ( & mut PrefixGuard :: new ( prefix, self ) . 0 )
357
372
}
358
373
359
374
/// Writes the named value `value` by prefixing `name` followed by `=` to
@@ -465,3 +480,54 @@ impl<'a> UriArguments<'a> {
465
480
Origin :: new ( path, query)
466
481
}
467
482
}
483
+
484
+ // See https://github.com/SergioBenitez/Rocket/issues/1534.
485
+ #[ cfg( test) ]
486
+ mod prefix_soundness_test {
487
+ use crate :: uri:: { Formatter , Query , UriDisplay } ;
488
+
489
+ struct MyValue ;
490
+
491
+ impl UriDisplay < Query > for MyValue {
492
+ fn fmt ( & self , _f : & mut Formatter < ' _ , Query > ) -> std:: fmt:: Result {
493
+ panic ! ( )
494
+ }
495
+ }
496
+
497
+ struct MyDisplay ;
498
+
499
+ impl UriDisplay < Query > for MyDisplay {
500
+ fn fmt ( & self , formatter : & mut Formatter < ' _ , Query > ) -> std:: fmt:: Result {
501
+ struct Wrapper < ' a , ' b > ( & ' a mut Formatter < ' b , Query > ) ;
502
+
503
+ impl < ' a , ' b > Drop for Wrapper < ' a , ' b > {
504
+ fn drop ( & mut self ) {
505
+ let _overlap = String :: from ( "12345" ) ;
506
+ self . 0 . write_raw ( "world" ) . ok ( ) ;
507
+ assert ! ( self . 0 . prefixes. is_empty( ) ) ;
508
+ }
509
+ }
510
+
511
+ let wrapper = Wrapper ( formatter) ;
512
+ let temporary_string = String :: from ( "hello" ) ;
513
+
514
+ // `write_named_value` will push `temp_string` into a buffer and
515
+ // call the formatter for `MyValue`, which panics. At the panic
516
+ // point, `formatter` contains an (illegal) static reference to
517
+ // `temp_string` in its `prefixes` stack. When unwinding occurs,
518
+ // `Wrapper` will be dropped. `Wrapper` holds a reference to
519
+ // `Formatter`, thus `Formatter` must be consistent at this point.
520
+ let _ = std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( || {
521
+ wrapper. 0 . write_named_value ( & temporary_string, MyValue )
522
+ } ) ) ;
523
+
524
+ Ok ( ( ) )
525
+ }
526
+ }
527
+
528
+ #[ test]
529
+ fn check_consistency ( ) {
530
+ let string = format ! ( "{}" , & MyDisplay as & dyn UriDisplay <Query >) ;
531
+ assert_eq ! ( string, "world" ) ;
532
+ }
533
+ }
0 commit comments