Canonical finance une thèse de doctorat visant à automatiser la conversion d’anciennes bases de code C vers le langage Rust en utilisant l’intelligence artificielle

Le langage Rust a largement gagné en visibilité ces dernières années, au point qu’on le retrouve désormais dans le noyau de Linux. Pour de nombreux acteurs de la filière du développement de logiciels, c’est le moment d’arrêter d’initier de nouveaux projets en C ou en C++ et de passer au Rust compte tenu des avantages qu’il offre en matière de sécurisation des logiciels. Un autre pan des réflexions en cours concerne la conversion des anciennes bases de code C vers le Rust. Canonical annonce le financement d’une thèse de doctorat en la matière.

Les chercheurs entendent mettre l’intelligence artificielle à profit pour atteindre leur objectif

L’équipe de recherche propose un modèle neurosymbolique hybride qui combine l'apprentissage automatique avec des méthodes classiques d'analyse, de tests de résistance et de vérification formelle. Le flux de travail se divise en quatre étapes principales :
  • Primo, la phase de programmation divise le dépôt massif en fragments indépendants, en conservant le fil sémantique nécessaire concernant les types et les dépendances.
  • Deuxio, la phase de traduction, qui utilise des modèles de langage optimisés avec des bibliothèques de conversions C vers Rust connues pour générer des abstractions correctes au lieu d'une simple copie syntaxique.
  • La troisième étape part du principe que le code généré n'est pas fiable tant que les techniques de fuzzing et les vérifications d'équivalence n'ont pas démontré qu'il se comporte exactement comme le code C original.
  • Enfin, un système de débogage localise les échecs de validation et applique des corrections précises à l'aide de techniques de réparation symbolique.

L’objectif est de pallier à certaines limitations en matière de conversion de code C vers le Rust. En effet, la réécriture manuelle d'un logiciel écrit en C comporte en réalité un risque technique important. Les projets établis contiennent des décennies de corrections de bogues, d'optimisations de performances et de connaissances opérationnelles qui sont souvent perdues lors du processus de refactorisation.

Il est vrai que les outils traditionnels de traduction de code source peuvent traiter de grands volumes de données, mais ils génèrent du code Rust non sécurisé qui conserve des structures artificielles héritées du C. D'autre part, les outils d'IA disponibles peuvent générer du code à partir de petits exemples, mais ils échouent à traiter des dépôts entiers en raison de leurs volumes importants et finissent par perdre l'idée centrale, sans pour autant offrir de garanties que le programme résultant conservera le comportement du code original.

L’approche neurosymbolique proposée par les chercheurs de l’université de Bristol entend corriger des tares comme celles observées avec la récente migration de la base de code Bun de C vers le Rust

La plus-value de l’utilisation de l’intelligence artificielle qui est en général mise en avant dans le processus du développement de logiciels est le gain de productivité. Le cas de la migration du code source Bun de Zig vers Rust ne fait pas exception étant donné que la tête derrière l’initiative rapporte que cette transition a été possible en seulement 11 jours contre une année pour une petite équipe d’ingénieurs lancée sur les 535 496 lignes de code de Zig, selon son estimatif.

Résultat :

  • le code en Rust produit par l'IA contient plus de 13 000 blocs unsafe, soit une proportion anormalement élevée comparée aux projets écrits par des humains. Par exemple, la gestion de la mémoire de bas niveau et les allocateurs personnalisés s'interfacent directement avec les pointeurs bruts via des constructions non sécurisées (unsafe). Elle gère la provenance des pointeurs et les appels explicites de désallocation par le biais des tables virtuelles (vtables) des allocateurs standard comme on peut le voir dans la section de code suivante :


Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
// bun_alloc is the T0 foundation crate that bun_threading and bun_collections
// depend on; importing either to satisfy the disallowed-types lint would create
// a dependency cycle.
#![allow(clippy::disallowed_types)]
#![feature(arbitrary_self_types_pointers)]
#![feature(allocator_api)]
// `#[thread_local]` (vs the `thread_local!` macro) compiles to a bare
// `__thread` slot — single `mov reg, fs:[OFFSET]` access, no `LocalKey`
// `__getit()` wrapper, no lazy-init flag check, no dtor-registration probe.
// Used for the per-allocation hot-path TLS in `ast_alloc::AST_ALLOC`.
#![feature(thread_local)]
 
use core::fmt::Write as _;
use core::mem::{MaybeUninit, size_of};
use core::ptr::{NonNull, addr_of_mut};
use core::sync::atomic::{AtomicU16, AtomicU32, Ordering};
use std::collections::HashMap;
 
// ──────────────────────────────────────────────────────────────────────────
// Re-exports
// ──────────────────────────────────────────────────────────────────────────
 
pub use bun_mimalloc_sys::mimalloc;
pub mod c_thunks;
 
// ── Allocator vtable ───────────────────────────────────────────────────────
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Alignment(pub u8); // log2 of byte alignment
impl Alignment {
    #[inline]
    pub(crate) const fn to_byte_units(self) -> usize {
        1usize << self.0
    }
    #[inline]
    pub(crate) const fn from_byte_units(b: usize) -> Self {
        Self(b.trailing_zeros() as u8)
    }
}
 
// ── `max_align_t` alignment ────────────────────────────────────────────────
// The `libc` crate does not expose `max_align_t` on every target Bun ships
// (missing on Windows MSVC and on FreeBSD aarch64), so those targets carry a
// local mirror of `max_align_t`. Remaining non-Windows targets keep
// `libc::max_align_t` (which carries `long double`, align 16 on x86_64/aarch64;
// the {f64,i64,*const ()} fallback would silently downgrade to 8).
#[cfg(windows)]
#[repr(C)]
struct MaxAlignT {
    _f: f64,
    _i: i64,
    _p: *const (),
}
#[cfg(windows)]
pub(crate) const MAX_ALIGN_T: usize = core::mem::align_of::<MaxAlignT>();
// On AArch64
// AAPCS64 `long double` is IEEE binary128, 16-byte aligned. The `libc` crate
// only defines `max_align_t` for FreeBSD on x86_64, so hardcode the ABI value
// for the aarch64 port.
#[cfg(all(target_os = "freebsd", target_arch = "aarch64"))]
pub(crate) const MAX_ALIGN_T: usize = 16;
#[cfg(not(any(windows, all(target_os = "freebsd", target_arch = "aarch64"))))]
pub(crate) const MAX_ALIGN_T: usize = core::mem::align_of::<libc::max_align_t>();
 
pub struct AllocatorVTable {
    pub alloc: unsafe fn(*mut core::ffi::c_void, usize, Alignment, usize) -> *mut u8,
    pub resize: unsafe fn(*mut core::ffi::c_void, &mut [u8], Alignment, usize, usize) -> bool,
    pub remap: unsafe fn(*mut core::ffi::c_void, &mut [u8], Alignment, usize, usize) -> *mut u8,
    pub free: unsafe fn(*mut core::ffi::c_void, &mut [u8], Alignment, usize),
}
impl AllocatorVTable {
    /// `alloc` impl that always fails. For vtables that only ever `free` an
    /// externally-produced buffer (mmap region, plugin-owned memory, refcounted
    /// foreign string) and never allocate or grow it.
    pub(crate) const NO_ALLOC: unsafe fn(
        *mut core::ffi::c_void,
        usize,
        Alignment,
        usize,
    ) -> *mut u8 = |_, _, _, _| core::ptr::null_mut();
    pub(crate) const NO_RESIZE: unsafe fn(
        *mut core::ffi::c_void,
        &mut [u8],
        Alignment,
        usize,
        usize,
    ) -> bool = |_, _, _, _, _| false;
    pub(crate) const NO_REMAP: unsafe fn(
        *mut core::ffi::c_void,
        &mut [u8],
        Alignment,
        usize,
        usize,
    ) -> *mut u8 = |_, _, _, _, _| core::ptr::null_mut();
 
    /// Build a "free-only" vtable: `alloc`/`resize`/`remap` all no-op/fail and
    /// only `free` is meaningful. Each call site still gets its own `static`
    /// (vtable address is an identity tag for `is_instance`).
    pub const fn free_only(
        free: unsafe fn(*mut core::ffi::c_void, &mut [u8], Alignment, usize),
    ) -> Self {
        Self {
            alloc: Self::NO_ALLOC,
            resize: Self::NO_RESIZE,
            remap: Self::NO_REMAP,
            free,
        }
    }
}
 
/// Fat allocator handle (ptr + vtable). Distinct from the `Allocator` trait below.
#[derive(Clone, Copy)]
pub struct StdAllocator {
    pub ptr: *mut core::ffi::c_void,
    pub vtable: &'static AllocatorVTable,
}
 
// SAFETY: `ptr` is an opaque tag/context handle; the vtable is `&'static`.
// Thread-safety of dispatch is the implementor's concern (mimalloc is
// thread-safe).
unsafe impl Send for StdAllocator {}
// SAFETY: see the `Send` impl directly above.
unsafe impl Sync for StdAllocator {}
 
impl Default for StdAllocator {
    /// The mimalloc-backed `c_allocator`.
    #[inline]
    fn default() -> Self {
        basic::C_ALLOCATOR
    }
}
 
impl StdAllocator {
    #[inline]
    pub(crate) fn raw_free(&self, buf: &mut [u8], alignment: Alignment, ra: usize) {
        // SAFETY: vtable invariant — `free` callee respects the (ptr, buf, alignment, ra) contract.
        unsafe { (self.vtable.free)(self.ptr, buf, alignment, ra) }
    }
    /// `raw_free` with `ret_addr = 0`, byte-aligned.
    #[inline]
    pub fn free(&self, bytes: &[u8]) {
        if bytes.is_empty() {
            return;
        }
        // SAFETY: `bytes` is reborrowed mutably only for the vtable signature; the
        // callee treats it as opaque.
        let buf =
            unsafe { core::slice::from_raw_parts_mut(bytes.as_ptr().cast_mut(), bytes.len()) };
        self.raw_free(buf, Alignment::from_byte_units(1), 0);
    }
}
 
// PORTING.md §Allocators: AST crates thread an `Arena`; non-AST use Vec/Box
// (global mimalloc). `Arena` is the real per-heap `MimallocArena` — unlike
// `bumpalo::Bump`, it supports per-allocation free + realloc, so `ArenaVec`
// no longer leaks on grow.
pub use mimalloc_arena::MimallocArena;
pub type Arena = MimallocArena;
mod baby_vec;
pub use baby_vec::BabyVec;
/// Arena-backed `Vec` with `u32` length/capacity.
/// 24 B (vs 32 B for `Vec<T, &'a MimallocArena>`); the
/// allocator handle is kept inline for lifetime checking. Growth/free route
/// through `<&MimallocArena as Allocator>` (= `mi_heap_realloc_aligned` /
/// `mi_free`); reclaimed on arena `reset`/`Drop`.
pub type ArenaVec<'a, T> = BabyVec<'a, T>;
pub use mimalloc_arena::{ArenaString, ArenaVecExt};
 
/// `bumpalo::collections::Vec::from_iter_in` parity for [`ArenaVec`].
#[inline]
pub fn vec_from_iter_in<'a, T, I>(iter: I, arena: &'a MimallocArena) -> ArenaVec<'a, T>
where
    I: IntoIterator<Item = T>,
{
    let iter = iter.into_iter();
    let (lo, _) = iter.size_hint();
    let mut v = ArenaVec::with_capacity_in(lo, arena);
    v.extend(iter);
    v
}
 
/// Re-tag an [`ArenaVec`]'s allocator handle to `dst` without copying data.
///
/// Sound because `<&MimallocArena as Allocator>` is heap-agnostic on the
/// existing buffer:
/// - `deallocate` → `mi_free(ptr)`: looks up the owning heap from the pointer's
///   page metadata; works from any thread on any heap's allocation.
/// - `grow`/`shrink` → `mi_heap_realloc_aligned(dst, ptr, ..)`: returns `ptr`
///   in-place if it fits (read-only `mi_usable_size`), else allocs on `dst`,
///   `memcpy`s, then `mi_free(ptr)`.
///
/// The original arena is never `mi_heap_malloc`-ed from again via this `Vec`,
/// so the [`MimallocArena`] single-thread-alloc contract is preserved.
#[inline]
pub fn transfer_arena<'a, T>(v: &mut ArenaVec<'a, T>, dst: &'a MimallocArena) {
    v.set_allocator(dst);
}
 
/// `bumpalo::format!` parity — `arena_format!(in arena, "...", ..)` →
/// [`ArenaString`].
#[macro_export]
macro_rules! arena_format {
    (in $arena:expr, $($arg:tt)*) => {{
        let mut __s = $crate::ArenaString::new_in($arena);
        ::core::fmt::Write::write_fmt(&mut __s, ::core::format_args!($($arg)*))
            .expect("ArenaString::write_fmt is infallible");
        __s
    }};
}
 
/// `bun.use_mimalloc` — false under ASAN, where the global allocator is `std::alloc::System`.
pub const USE_MIMALLOC: bool = cfg!(not(bun_asan));
 
// ── Allocator-vtable modules: per-module disposition (PORTING.md §Allocators) ──
//
//   MimallocArena            → prefer `bun_alloc::Arena` (= bumpalo::Bump)
//   MaxHeapAllocator         → debug-only cap (single-allocation arena)
//   heap_breakdown           → macOS malloc_zone_* per-tag heaps (debug builds)
//   basic                    → `impl GlobalAlloc for Mimalloc` above is the canonical impl
//
//   LinuxMemFdAllocator, MimallocArena (the vtable impl)
//   import bun_core/sys/runtime/collections and so live in
//   `bun_runtime::allocators`; callers import from
//   there directly.
//
#[path = "MaxHeapAllocator.rs"]
pub mod max_heap_allocator;
pub mod stack_fallback;
 
/// Raw alloc/free matching the `#[global_allocator]` (`mi_*` normally, libc under ASAN).
pub mod default_alloc {
    use core::ffi::c_void;
 
    #[inline]
    pub fn malloc(size: usize) -> *mut c_void {
        if cfg!(bun_asan) {
            // SAFETY: `libc::malloc` has no input preconditions; null on failure.
            unsafe { libc::malloc(size) }
        } else {
            crate::mimalloc::mi_malloc(size)
        }
    }
 
    /// # Safety
    /// `ptr` must be null or a live allocation from the default allocator.
    #[inline]
    pub unsafe fn realloc(ptr: *mut c_void, new_size: usize) -> *mut c_void {
        if cfg!(bun_asan) {
            // SAFETY: caller guarantees `ptr` is null or a live libc allocation
            // (the default allocator under ASAN).
            unsafe { libc::realloc(ptr, new_size) }
        } else {
            // SAFETY: caller guarantees `ptr` is null or a live mimalloc allocation.
            unsafe { crate::mimalloc::mi_realloc(ptr, new_size) }
        }
    }
 
    /// # Safety
    /// `ptr` must be null or a live allocation from the default allocator.
    #[inline]
    pub unsafe fn free(ptr: *mut c_void) {
        if cfg!(bun_asan) {
            // SAFETY: caller guarantees `ptr` is null or a live libc allocation
            // (the default allocator under ASAN).
            unsafe { libc::free(ptr) }
        } else {
            // SAFETY: caller guarantees `ptr` is null or a live mimalloc allocation.
            unsafe { crate::mimalloc::mi_free(ptr) }
        }
    }
 
    /// # Safety
    /// `ptr` must be null or a live allocation from the default allocator.
    #[inline]
    pub unsafe fn usable_size(ptr: *const c_void) -> usize {
        if ptr.is_null() {
            return 0;
        }
        // Under `bun_asan` the global allocator is `std::alloc::System`, so the
        // size must come from libc, not mimalloc — and the symbol differs per
        // OS (`malloc_usable_size` on Linux, `malloc_size` on macOS). `bun_asan`
        // is only ever set on Linux or macOS, so the catch-all (non-asan, every
        // `check-all` target including Windows) stays on mimalloc.
        #[cfg(all(bun_asan, target_os = "linux"))]
        return unsafe { libc::malloc_usable_size(ptr.cast_mut()) };
        #[cfg(all(bun_asan, target_os = "macos"))]
        return unsafe { libc::malloc_size(ptr) };
        // SAFETY: caller guarantees `ptr` is a live mimalloc allocation (the
        // non-null check above already handled null).
        #[cfg(not(any(all(bun_asan, target_os = "linux"), all(bun_asan, target_os = "macos"))))]
        return unsafe { crate::mimalloc::mi_usable_size(ptr) };
    }
 
    // The aligned variants are `#[cfg]`-split (not `if cfg!()`) because the
    // posix_memalign/malloc_usable_size symbols don't exist on Windows.
 
    #[cfg(not(bun_asan))]
    #[inline]
    pub(crate) fn malloc_aligned(size: usize, align: usize) -> *mut c_void {
        crate::mimalloc::mi_malloc_auto_align(size, align)
    }
 
    #[cfg(bun_asan)]
    #[inline]
    pub(crate) fn malloc_aligned(size: usize, align: usize) -> *mut c_void {
        if align <= crate::MAX_ALIGN_T {
            return unsafe { libc::malloc(size) };
        }
        let mut p: *mut c_void = core::ptr::null_mut();
        let align = align.max(core::mem::size_of::<*mut c_void>());
        if unsafe { libc::posix_memalign(&mut p, align, size) } != 0 {
            return core::ptr::null_mut();
        }
        p
    }
 
    /// # Safety
    /// `ptr` must be null or a live allocation from the default allocator with the given `align`.
    #[cfg(not(bun_asan))]
    #[inline]
    pub(crate) unsafe fn realloc_aligned(
        ptr: *mut c_void,
        new_size: usize,
        align: usize,
    ) -> *mut c_void {
        // SAFETY: caller guarantees `ptr` is null or a live mimalloc allocation
        // with alignment `align`.
        unsafe { crate::mimalloc::mi_realloc_aligned(ptr, new_size, align) }
    }
 
    /// # Safety
    /// `ptr` must be null or a live allocation from the default allocator with the given `align`.
    #[cfg(bun_asan)]
    #[inline]
    pub(crate) unsafe fn realloc_aligned(
        ptr: *mut c_void,
        new_size: usize,
        align: usize,
    ) -> *mut c_void {
        if align <= crate::MAX_ALIGN_T {
            return unsafe { libc::realloc(ptr, new_size) };
        }
        let new_ptr = malloc_aligned(new_size, align);
        if new_ptr.is_null() {
            return core::ptr::null_mut();
        }
        if !ptr.is_null() {
            unsafe {
                let copy = usable_size(ptr).min(new_size);
                core::ptr::copy_nonoverlapping(ptr.cast::<u8>(), new_ptr.cast::<u8>(), copy);
                libc::free(ptr);
            }
        }
        new_ptr
    }
}
 
pub use max_heap_allocator::MaxHeapAllocator;
pub use stack_fallback::ArenaPtr;
 
#[path = "MimallocArena.rs"]
pub mod mimalloc_arena;
 
pub mod ast_alloc;
pub use ast_alloc::{AstAlloc, AstBox, AstVec, ast_box};
mod hashbrown_bridge;
/// Re-export so `bun_collections` can name the polyfill trait in
/// `StringHashMap`'s `A` bound without taking its own direct dep on
/// `allocator-api2`.
pub use allocator_api2::alloc::Allocator as HashbrownAllocator;
 
// ── tier-0 local primitives ───────────────────────────────────────────────
// Real, self-contained helpers used by the BSS containers below. These are the
// canonical tier-0 definitions, re-exported by higher tiers (`bun_paths::SEP_STR`,
// `bun_core::strings::trim_right`, `bun_core::strings::trim_right`).
 
/// `"\\"` on Windows, `"/"` elsewhere.
/// Canonical tier-0 definition; re-exported by `bun_paths::SEP_STR`.
pub const SEP_STR: &str = if cfg!(windows) { "\\" } else { "/" };
 
/// `b'\\'` on Windows, `b'/'` elsewhere.
/// Canonical tier-0 definition; re-exported by `bun_paths::SEP` / `bun_core::SEP`.
pub const SEP: u8 = if cfg!(windows) { b'\\' } else { b'/' };
 
/// Canonical tier-0 definition; re-exported by `bun_core::strings::trim_right`.
#[inline]
pub fn trim_right<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] {
    let mut end = s.len();
    while end > 0 && chars.contains(&s[end - 1]) {
        end -= 1;
    }
    &s[..end]
}
 
/// Canonical tier-0 definition; re-exported by `bun_core::strings::trim_left`.
#[inline]
pub fn trim_left<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] {
    let mut begin = 0usize;
    while begin < s.len() && chars.contains(&s[begin]) {
        begin += 1;
    }
    &s[begin..]
}
 
/// Strip `chars` from both ends.
/// Canonical tier-0 definition; re-exported by `bun_core::strings::trim`.
#[inline]
pub fn trim<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] {
    trim_right(trim_left(s, chars), chars)
}
 
// ─── ascii-lowercase helpers ──────────────────────────────────────────────
// Sunk from bun_core::strings so bun_alloc::BSSList::append_lower_case can call
// them without a dep cycle (bun_core → bun_alloc, not the reverse).
// `bun_core::strings` re-exports `copy_lowercase` and `ascii_lowercase_buf`.
 
/// ASCII-lowercase
/// `in_` into `out` (which must be at least `in_.len()`), returning the
/// written prefix. Memcpy-runs + per-uppercase-byte fixup; identical output
/// to a byte-at-a-time `to_ascii_lowercase` zip.
pub fn copy_lowercase<'a>(in_: &[u8], out: &'a mut [u8]) -> &'a [u8] {
    let mut in_slice = in_;
    // Reshaped for borrowck — track output offset instead of reslicing &mut.
    let mut out_off: usize = 0;
 
    'begin: loop {
        for (i, &c) in in_slice.iter().enumerate() {
            if let b'A'..=b'Z' = c {
                out[out_off..out_off + i].copy_from_slice(&in_slice[0..i]);
                out[out_off + i] = c.to_ascii_lowercase();
                let end = i + 1;
                in_slice = &in_slice[end..];
                out_off += end;
                continue 'begin;
            }
        }
 
        out[out_off..out_off + in_slice.len()].copy_from_slice(in_slice);
        break;
    }
 
    &out[0..in_.len()]
}
 
/// Lowercase `input` into a fresh `[u8; N]` stack buffer, returning
/// `Some((buf, input.len()))` or `None` if `input.len() > N`. The unused tail
/// of `buf` is zero-filled. Covers the ubiquitous "lowercase a short key into
/// a stack buffer, then look it up in a length-gated map" pattern.
#[inline]
pub fn ascii_lowercase_buf<const N: usize>(input: &[u8]) -> Option<([u8; N], usize)> {
    if input.len() > N {
        return None;
    }
    let mut buf = [0u8; N];
    copy_lowercase(input, &mut buf[..input.len()]);
    Some((buf, input.len()))
}
 
/// Wrap a raw allocator pointer in the `Result<NonNull<[u8]>, AllocError>`
/// shape `core::alloc::Allocator` wants. Null → `Err(AllocError)`. Generic
/// over the pointee so mimalloc's `*mut c_void` returns pass straight in.
#[inline(always)]
pub(crate) fn alloc_result<T>(
    p: *mut T,
    size: usize,
) -> core::result::Result<NonNull<[u8]>, core::alloc::AllocError> {
    NonNull::new(p.cast::<u8>())
        .map(|p| NonNull::slice_from_raw_parts(p, size))
        .ok_or(core::alloc::AllocError)
}
 
/// Number of bytes the formatted args would produce.
///
/// Drives a discarding `fmt::Write` that only sums `s.len()` — no allocation,
/// no UTF-8 validation beyond what the formatter already did. Lives here in
/// T0 so higher tiers (`bun_core::fmt::count` re-exports this) and `bun_alloc`
/// itself can share the single implementation.
#[inline]
pub fn fmt_count(args: core::fmt::Arguments<'_>) -> usize {
    struct Discarding(usize);
    impl core::fmt::Write for Discarding {
        #[inline]
        fn write_str(&mut self, s: &str) -> core::fmt::Result {
            self.0 += s.len();
            Ok(())
        }
    }
    let mut w = Discarding(0);
    // Infallible: our `write_str` never errors.
    let _ = core::fmt::write(&mut w, args);
    w.0
}
 
/// `core::fmt::Write` adapter over a borrowed `&mut [u8]` — the engine behind
/// [`buf_print`] / [`buf_print_len`] (and `bun_core::fmt::buf_print_z`).
///
/// Lives at T0 so `bun_alloc` itself can use it (`BSSStringList::print`); T1
/// `bun_core::fmt` re-exports it and adds an `io::Write` impl for write-only
/// sites.
pub struct SliceCursor<'a> {
    pub buf: &'a mut [u8],
    pub at: usize,
}
impl<'a> SliceCursor<'a> {
    #[inline]
    pub fn new(buf: &'a mut [u8]) -> Self {
        Self { buf, at: 0 }
    }
}
impl core::fmt::Write for SliceCursor<'_> {
    #[inline]
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        let bytes = s.as_bytes();
        let end = self.at + bytes.len();
        if end > self.buf.len() {
            return Err(core::fmt::Error);
        }
        self.buf[self.at..end].copy_from_slice(bytes);
        self.at = end;
        Ok(())
    }
}
 
/// Render the formatted args into `buf`, returning the written sub-slice.
/// Fails (`fmt::Error`) when `buf` is too short.
pub fn buf_print<'a>(
    buf: &'a mut [u8],
    args: core::fmt::Arguments<'_>,
) -> core::result::Result<&'a [u8], core::fmt::Error> {
    let mut c = SliceCursor { buf, at: 0 };
    core::fmt::write(&mut c, args)?;
    let len = c.at;
    Ok(&c.buf[..len])
}
}

  • l’on note l’apparition dans le code de centaines d'utilisations de static mut et de fonctions de contournement de la mémoire qui violent les garanties de sécurité traditionnelles de Rust. A titre d’illustration, la fonction PathString::init (située dans les sources de gestion de chaînes de caractères de bun_core) a fait l'objet d'audits publics de sécurité après avoir été signalée pour avoir exposé par inadvertance un comportement non sécurisé dans une interface pourtant déclarée comme sûre.



Ces efforts interviennent dans un contexte d’intérêt croissant pour Rust qui ravive le débat de la comparaison avec des langages comme le C et le C++

Pourquoi le langage Rust est-il devenu si désirable ? Essentiellement parce qu’il arrive à marier des performances proches du C tout en offrant de meilleures garanties de sécurisation de la mémoire, et ce, même si certains acteurs de la filière sont d’avis que le problème de fond c’est l’aptitude des programmeurs à mettre à profit les fonctionnalités d’un langage donné. Si le sujet autour de la conversion de code du C vers le Rust a pris tant d’ampleur, c’est notamment parce que certaines grandes entreprises courent après les bugs liés à la mémoire.

En 2019, Microsoft avertissait ainsi que 70 % des failles de sécurité détectées dans Windows étaient liées à la sécurité de la mémoire. Ce taux est le même depuis presque 20 ans. L’éditeur évoquait alors pour la première fois son intérêt pour le Rust. Même son de cloche chez Google. « Environ 70 % de nos bugs de sécurité de haute sévérité sont des problèmes d’insécurité de la mémoire (c’est-à-dire des erreurs avec les pointeurs C/C++). La moitié d’entre eux sont des bugs de type « use-after-free » », écrivait ainsi l’éditeur dans un billet de 2020 sur la sécurité de Chromium.

Source : Billet de blog sur l’approche neurosymbolique

Et vous ?

Que pensez-vous de cette initiative annoncée par Canonical ? Partagez-vous les avis selon lesquels il serait plus judicieux de créer un compilateur C qui émet directement du code assembleur sécurisé ?
Avez-vous récemment mené un projet de conversion d'ancienne base de code C vers le Rust ? Quelles sont les leçons que vous avez tirées ? Partagez vos anecdotes

Voir aussi :

L'équipe Microsoft Security Response Center recommande l'utilisation de Rust comme approche proactive pour un code plus sécurisé
Quel langage pourrait remplacer C ? Après avoir comparé Go, Rust et D, le choix d'Andrei Alexandrescu se porte sur D
C2Rust : un outil qui permet de faire la traduction et la refactorisation de votre code écrit en langage C vers le langage Rust