Rust: Problème d'insécurité de la mémoire dans Safe Rust

Créé le 17 févr. 2020  ·  38Commentaires  ·  Source: rust-lang/rust

J'ai un petit programme (une simplification d'une fonction de test à partir d'un plus grand projet) qui tranche un petit tableau et tente d'accéder à un élément hors limites de la tranche. L'exécuter avec cargo run --release utilisant la version stable 1.41.0 imprime quelque chose comme ceci (testé sur macOS 10.15 et Ubuntu 19.10):

0 0 3 18446744073709551615
[1]    21065 segmentation fault  cargo run --release

Il semble que la tranche résultante ait en quelque sorte une longueur 2**64 - 1 , donc la vérification des limites est omise, ce qui entraîne de manière prévisible un segfault. Sur 1.39.0 et 1.40.0 le même programme imprime ce à quoi je m'attendais:

0 0 3 0
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 16777216', src/main.rs:13:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Le problème disparaît si je fais l'une des choses suivantes:

  • supprimez l'un des deux appels do_test(...); dans main() ;
  • supprimez la boucle for _ in 0..1 { ;
  • remplacez la boucle for y in 0..x { par for y in 0..1 { ;
  • supprimez la ligne z.extend(std::iter::repeat(0).take(x)); ou remplacez-la par z.extend(std::iter::repeat(0).take(1)); ;
  • remplacez la boucle for arr_ref in arr { par let arr_ref = &arr[0]; ;
  • spécifiez RUSTFLAGS="-C opt-level=2" ;
  • spécifiez RUSTFLAGS="-C codegen-units=1" .

Ma meilleure estimation est que -C opt-level=3 permet une passe d'optimisation problématique dans LLVM, ce qui entraîne une mauvaise compilation. Ceci est corroboré par le fait que MIR ( --emit mir ) et LLVM IR avant optimisations ( --emit llvm-ir -C no-prepopulate-passes ) sont les mêmes pour -C opt-level=2 et -C opt-level=3 .

Quelques informations supplémentaires qui pourraient être utiles:

  • Je ne peux pas reproduire le problème dans le terrain de jeu Rust (probablement parce qu'il utilise codegen-units = 1 );
  • Je ne peux pas reproduire le problème sur Windows 10 avec la même version 1.41.0 (aucune idée de ce qui le rend différent);
  • cargo-bisect-rustc indique que la régression s'est produite pour la première fois dans la nuit 2019-12-12 , spécifiquement dans ce commit . Cela me semble suspect, étant donné que 1.40.0 , qui ne présente pas le problème, a été publié après cette date.

J'attache le programme en ligne au cas où le dépôt GitHub ne fonctionnerait pas (si vous voulez le compiler sans Cargo, utilisez rustc -C opt-level=3 main.rs ):

fn do_test(x: usize) {
    let arr = vec![vec![0u8; 3]];

    let mut z = Vec::new();
    for arr_ref in arr {
        for y in 0..x {
            for _ in 0..1 {
                z.extend(std::iter::repeat(0).take(x));
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &arr_ref[a..b];
                eprintln!("{} {} {} {}", a, b, arr_ref.len(), slice.len());
                eprintln!("{:?}", slice[1 << 24]);
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}
A-LLVM C-bug I-unsound 💥 ICEBreaker-LLVM P-medium T-compiler regression-from-stable-to-stable

Commentaire le plus utile

Reproducteur LLVM IR: https://gist.github.com/comex/881074b1bcc545e299e65527c719eef4

Exécutez opt bconfused.ll -scalar-evolution -loop-idiom -scalar-evolution -indvars -S -O3 -o - | grep xprint . Si l'intérieur des parenthèses est i64 -1 , l'optimisation des bugs s'est produite. Si ce n'est pas le cas ... ce n'est peut-être pas le cas, mais il est difficile d'en être sûr.

Il semble provenir de LLVM en ajoutant incorrectement nuw à add nuw i64 %x, -1 dans le cadre de la passe de simplification des variables d'induction. x est l'argument de la fonction, et nuw signifie pas de wrap non signé, donc cela affirme efficacement que l'argument est 0, à un point de la fonction où il n'est pas garanti d'être.

Bisecting (modifier: de LLVM 9 à LLVM 10, qui @tmiasko a dit qu'il n'était pas affecté) produit ce commit:

commit 58e8c793d0e43150a6452e971a32d7407a8a7401
Author: Tim Northover <[email protected]>
Date:   Mon Sep 30 07:46:52 2019 +0000

    Revert "[SCEV] add no wrap flag for SCEVAddExpr."

    This reverts r366419 because the analysis performed is within the context of
    the loop and it's only valid to add wrapping flags to "global" expressions if
    they're always correct.

    llvm-svn: 373184

Cela semble prometteur, car r366419 (le commit que le commit ci-dessus annule) est inclus dans la branche LLVM 9.0 utilisée par Rust.

Tous les 38 commentaires

cc @ rust-lang / compilateur
@rustbot ping icebreakers-llvm

L'équipe de publication envisage de faire une version intermédiaire pour Rust 1.41 (nous en avons brièvement discuté lors de la réunion de la semaine dernière), et j'aimerais que cela soit inclus si nous pouvons obtenir un PR bientôt.

Salut les disjoncteurs LLVM ICE! Ce bug a été identifié comme un bon
"Candidat à la rupture de LLVM ICE". Au cas où cela serait utile, voici quelques
[instructions] pour résoudre ce type de bogues. Jetez-y un coup d'oeil?
Merci! <3

cc @comex @DutchGhost @ hanna-kruppe @hdhoang @heyrutvik @ JOE1994 @jryans @mmilenko @nagisa @nikic @ Noah-Kennedy @SiavoshZarrasvand @spastorino @vertexclique @vgxbj

L'exécuter avec cargo run - release utilisant la version stable 1.41.0 imprime quelque chose comme ceci (testé sur macOS 10.15 et Ubuntu 19.10):

Je ne peux pas reproduire cela sur le terrain de jeu . Le programme fonctionne très bien là-bas sur 1.41.0 en mode version.

EDIT: Ah, vous avez déjà dit cela.
De plus, le programme est bien dans Miri, donc ce n'est probablement pas UB mais une mauvaise compilation.

Juste pour ajouter un point de données, je peux reproduire ceci sur Linux avec le dernier soir:

[andrew<strong i="6">@krusty</strong> rust-69225]$ rustc --version
rustc 1.43.0-nightly (5e7af4669 2020-02-16)

[andrew<strong i="7">@krusty</strong> rust-69225]$ cat main.rs
fn do_test(x: usize) {
    let arr = vec![vec![0u8; 3]];

    let mut z = Vec::new();
    for arr_ref in arr {
        for y in 0..x {
            for _ in 0..1 {
                z.extend(std::iter::repeat(0).take(x));
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &arr_ref[a..b];
                eprintln!("{} {} {} {}", a, b, arr_ref.len(), slice.len());
                eprintln!("{:?}", slice[1 << 24]);
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}

[andrew<strong i="8">@krusty</strong> rust-69225]$ rustc -C opt-level=3 main.rs

[andrew<strong i="9">@krusty</strong> rust-69225]$ ./main
0 0 3 18446744073709551615
zsh: segmentation fault (core dumped)  ./main

J'ai pu reproduire ce qui précède avec exactement la même sortie avec Rust 1.41 stable. Rust 1.40 stable ne présente pas le problème:

$ ./main
0 0 3 0
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 16777216', main.rs:13:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Je pense que tout cela est cohérent avec le rapport de @dfyz , sauf que cela confirme au moins que ce n'est pas spécifique à macOS.

  • cargo-bisect-rustc dit que la régression s'est produite pour la première fois dans le 2019-12-12 chaque nuit, spécifiquement dans ce commit . Cela me semble suspect, étant donné que 1.40.0 , qui ne présente pas le problème, a été publié après cette date.

Ceci est attendu. La 1.40.0 est sortie le 2019-12-19 sur la base de ce qui était alors la branche beta , qui avait été branchée à partir de master six semaines plus tôt (à peu près au moment de la version 1.39.0). Voir https://doc.rust-lang.org/book/appendix-07-nightly-rust.html pour en savoir plus sur les canaux de publication.

Si je devais deviner, je dirais que https://github.com/rust-lang/rust/pull/67015 est le coupable probable. Cela a corrigé 3 problèmes de codegen, donc il touche le code critique de codegen.
Cc @ osa1 @ oli-obk @wesleywiser

Merci pour le ping. Je suis en train de faire une compilation pour enquêter. Il est possible que ce soit un bogue introduit avec # 67015. Une autre possibilité est que # 67015 vient de révéler un bug existant.

J'ai réussi à reproduire le segfault en utilisant rustc tous les soirs sous Linux, mais pas avec ma version générée avec ce config.toml:

config.toml

[llvm]

[build]

[install]

[rust]
optimize = true
debug = true
codegen-units = 0
debug-assertions = true
debuginfo-level = 2

[target.x86_64-unknown-linux-gnu]
llvm-config = "/usr/bin/llvm-config-9"

[dist]

En utilisant rustc tous les soirs, j'ai vérifié les MIR avant et après ConstProp et les MIR sont identiques. Donc, si cela est causé par ConstProp, c'est à cause d'une différence dans le code généré d'une bibliothèque, pas de ce programme.

Régression dans 033662dfbca088937b9cdfd3d9584015b5e375b2

@rustbot modifier les libellés: -E-needs-bisection


@ osa1 le debug-assertions = true est probablement à blâmer. Quand j'essaye de compiler (avec un compilateur nocturne vanille) le programme avec -C debug-assertions=y le programme panique au lieu du segfault

Je pense que je l'ai résolu! La restauration de a983e0590a43ed8b0f60417828efd4e79b51f494 résout le problème. Cela m'a semblé être le coupable toute la journée, mais je n'ai pas pu le tester au travail :) Quelqu'un peut-il m'aider à faire au mieux un PR pour ce problème? Je pense que le meilleur moyen serait d'ajouter un cas de test qui doit échouer, mais il semble que cela soit très spécifique à la plate-forme, etc. Des idées ici? Merci!

(Cela a déjà été coupé en deux par le PO)

J'ai réussi à reproduire cela avec une version locale avec debug-assertions désactivé (merci pour cela @ hellow554).

Certains des PR dans le rollup provoquent des conflits lorsqu'ils sont rétablis car nous avons depuis lors rustfmt -ed tout, mais je crois que ce problème est dû à # 67174.

edit: il semble que nous ayons trouvé cela en même temps @shahn :)

@lqd oui, c'est le problème qui inclut le commit auquel j'ai fait référence ci-dessus. C'est le coupable.

Pour ajouter un autre point de données, le problème disparaît lorsque codegen-units est défini sur 3 ou moins (en supposant le profil de version, avec incremental=false ). En d'autres termes, je suis capable de reproduire lorsque codegen-units est égal ou supérieur à 4.

L'appel à panic_bounds_check disparaît après le passage de LLVM Jump Threading. Je peux reproduire le problème avec opt de LLVM 9, mais pas de LLVM 10.

Donc, j'ai vérifié et construit une étape 1 rustc ( ./x.py build -i --stage 1 ), reconstruit libstd ( ./x.py build -i --stage 1 --keep-stage 0 src/libstd ) sans # 67174, et recompilé le programme de segfaulting avec quatre unités de codegen ( rustc +stage1 -C opt-level=3 -C codegen-units=4 main.rs ). Comme prévu, cela a fait disparaître le segfault. Si j'applique à nouveau le # 67174, le segfault revient.

Cela signifie que j'ai maintenant deux compilateurs qui ne diffèrent que par la bibliothèque standard qu'ils utilisent. Appelons ces compilateurs GOOD (pas de segfault) et BAD (segfault).

J'ai alors remarqué que les 4 fichiers _ non optimisés_ *.ll générés par GOOD ( -C no-prepopulate-passes ) sont à peu près les mêmes que ceux générés par BAD (la seule différence J'ai vu les différents ID aléatoires dans les noms de fonction), mais les fichiers _optimized_ *.ll (non -C no-prepopulate-passes ) sont très différents. Je ne suis en aucun cas un expert en compilateur, mais comme le programme en cours de compilation est exactement le même dans les deux cas, les deux compilateurs proprement dits sont exactement les mêmes, et la seule différence est dans une bibliothèque standard précompilée, je pense que LTO pourrait être impliqué .

En effet, si je passe l'un des -Z thinlto=no , -C lto=no , -C lto=yes , -C lto=thin (à ce stade, je ne suis vraiment pas sûr, mais je suppose que toutes les formes de -C lto sont différents de ThinLTO, qui est utilisé par défaut) à BAD , le segfault disparaît à nouveau.

Est-il probable que LTO soit en faute ici?

J'ai lu le cas de test. J'ai lu le commit qui est annulé. Je n'ai toujours pas la moindre idée de ce qui se passe. Qu'est-ce qui a cassé?

Qu'est-ce qui a cassé?

Je ne crois pas à ce stade que quiconque puisse dire avec certitude ce qui s'est cassé exactement, mais mon analyse provisoire est la suivante (veuillez prendre ce qui suit avec un grain de sel, je ne suis ni dans l'équipe Rust ni dans l'équipe LLVM, tout ce que je peut faire est bricoler avec le compilateur et regarder le LLVM IR):

  • nous avons supprimé les contrôles de dépassement d'une seule ligne dans Layout::repeat() de la bibliothèque standard, ce qui a finalement conduit à cette insécurité de la mémoire. Mathématiquement, utiliser une addition non cochée ici devrait être parfaitement sûr - le commentaire dans cette fonction (et aussi dans Layout::pad_to_align() ) explique pourquoi;
  • mon exemple de code démontrant le problème n'appelle même pas cette fonction, mais il utilise explicitement Vec , qui utilise implicitement Vec::reserve_internal() , qui à son tour appelle Layout::repeat() ;
  • Layout::repeat() est marqué comme #[inline] , et apparemment la seule différence pertinente entre GOOD et BAD est de savoir si cette fonction est incorporée dans do_test() ou ne pas. Par exemple, la restauration des vérifications de débordement interdit l'inlining et résout le problème; la suppression de l'attribut #[inline] provoque le même effet; la désactivation de LTO désactive l'inlining pour les fonctions de bibliothèque et résout à nouveau le problème.

Si cela est vrai (encore une fois, je ne suis pas sûr à 100% de tout ce qui précède), cela signifie qu'une passe LLVM non autorisée ou une combinaison de passes optimise mal l'IR après l'inlining. C'est ce que j'essaie actuellement d'enquêter, mais malheureusement ce n'est pas facile (du moins pour un non-LLVM-ICE-breaker comme moi) car les différences IR entre GOOD et BAD sont modérément grand. Il semble que la mauvaise version omet panic_bounds_check , mais je ne sais toujours pas exactement pourquoi.

Aussi, inspiré par le commentaire de @tmiasko , j'ai essayé de compiler rustc avec différentes versions de LLVM, et il semble que LLVM 10rc1 résout le problème (la dernière version défectueuse de LLVM que j'ai essayée est la 9.0.1). Il ne semble pas que la passe de Jump Threading soit à blâmer, car je ne vois pas de commits pertinents entre 9.0.1 et 10rc1.

Si l'annulation de la modification de Layout::repeat() ne masque qu'un symptôme d' une occurrence

Si l'annulation du changement de Layout :: repeat () ne masque qu'un symptôme d'une occurrence spécifique d'un bogue sans rapport, est-ce vraiment la bonne chose à faire?

Je pense que ça va peut-être si:

  • Le changement est expédié
  • Cela rend le bogue beaucoup plus facile à déclencher, affectant de nombreux utilisateurs
  • Le réparer correctement prendra du temps

Si ceux-ci tiennent, je pense que j'annulerais le changement, enverrais une version mineure pour débloquer les utilisateurs (les choses fonctionnaient bien sans le changement même si le bogue était toujours là), puis me concentrerais sur le bogue réel.

Dans un autre compilateur, je me souviens avoir fait cela. Nous avons annulé un changement dans une branche de version, mais pas sur le maître (ce qui n'est pas une bonne pratique, cela a causé des problèmes plus tard), a livré une nouvelle version mineure. Puis corrigé le bug réel.

Dans tous les cas, tant que le bogue sera priorisé et corrigé, et que le commit qui rend le bogue beaucoup plus facile à déclencher n'est pas un correctif de bogue en lui-même, je ne vois aucun problème pour le rétablir pour le moment.

Une question est donc la suivante: ce bug est-il vraiment facile à déclencher? Jusqu'à présent, nous avons eu un rapport avec un cas de test quelque peu complexe où essayer de minimiser davantage (comme le déroulement d'une boucle for _ in 0..1 apparemment triviale) échoue à se reproduire.

Je ne pense pas que le bogue soit facile à déclencher, il semble que je sois particulièrement malchanceux.

Quoi qu'il en soit, j'apprécie vraiment le problème que @shahn a eu pour changement de Layout::new() , mais IMO l'annuler n'est pas la bonne chose à faire dans ce cas. Mon raisonnement (en plus de ce que @SimonSapin a dit):

  • la suppression des vérifications de dépassement de capacité dans Layout::repeat() permet à LLVM d'insérer Vec::reserve() dans les versions de version. Cela pourrait donner une belle amélioration des performances dans certains cas (bien que cela devrait être mesuré, bien sûr);
  • littéralement, la fonction précédente dans libcore/alloc.rs ( Layout::pad_to_align() ) utilise le même modèle d'addition non vérifiée avec exactement le même commentaire expliquant ce qui le rend possible. La restauration des contrôles de dépassement dans Layout::repeat() mais pas dans Layout::pad_to_align() me semble vraiment bizarre;
  • si quelqu'un est vraiment bloqué sur ce problème (je ne le suis certainement pas), il existe de nombreuses autres solutions de contournement qui n'impliquent pas de changements stdlib (par exemple, désactivez ThinLTO, changez le niveau d'optimisation, diminuez le nombre d'unités de codegen).

Peut-être lancer localement une assertion défensive incluse dans la version de l'invariant comme condition préalable afin qu'il panique avec des détails spécifiques pour traquer ce cas particulier ou un débogueur-fu? Je parierais que c'est un calcul non contrôlé qui passe sous certaines conditions.

Ensuite, quand il est retrouvé (quelque part dans LLVM comme je viens de l'apprendre, ty @dyfz), un cas de test de régression serait génial pour que cela ne se reproduise plus. 🙏

Reproducteur LLVM IR: https://gist.github.com/comex/881074b1bcc545e299e65527c719eef4

Exécutez opt bconfused.ll -scalar-evolution -loop-idiom -scalar-evolution -indvars -S -O3 -o - | grep xprint . Si l'intérieur des parenthèses est i64 -1 , l'optimisation des bugs s'est produite. Si ce n'est pas le cas ... ce n'est peut-être pas le cas, mais il est difficile d'en être sûr.

Il semble provenir de LLVM en ajoutant incorrectement nuw à add nuw i64 %x, -1 dans le cadre de la passe de simplification des variables d'induction. x est l'argument de la fonction, et nuw signifie pas de wrap non signé, donc cela affirme efficacement que l'argument est 0, à un point de la fonction où il n'est pas garanti d'être.

Bisecting (modifier: de LLVM 9 à LLVM 10, qui @tmiasko a dit qu'il n'était pas affecté) produit ce commit:

commit 58e8c793d0e43150a6452e971a32d7407a8a7401
Author: Tim Northover <[email protected]>
Date:   Mon Sep 30 07:46:52 2019 +0000

    Revert "[SCEV] add no wrap flag for SCEVAddExpr."

    This reverts r366419 because the analysis performed is within the context of
    the loop and it's only valid to add wrapping flags to "global" expressions if
    they're always correct.

    llvm-svn: 373184

Cela semble prometteur, car r366419 (le commit que le commit ci-dessus annule) est inclus dans la branche LLVM 9.0 utilisée par Rust.

T-compiler triage: P-medium, basé sur le résumé de situation suivant:

pnkfelix: Il semble que les éléments de travail restants pour # 69225 soient 1. correction de LLVM (soit en choisissant leur 58e8c793d0e43150a6452e971a32d7407a8a7401, soit en mettant à niveau vers LLVM 10), puis 2. readd PR # 67174.
pnkfelix: cependant, aucun de ces éléments ne me paraît hautement prioritaire.
pnkfelix: Au moins, ce bogue LLVM ne semble pas meilleur ou pire que les autres bogues de code LLVM. Et je suppose que c'est ce que @simulacrum vient de dire.

Mise à jour: la mise à niveau vers LLVM 10 est en cours dans le PR # 67759

Mise à jour 2: Il peut être imprudent de choisir aveuglément leur validation de retour, car nous avons vraisemblablement sélectionné l'original pour une raison quelconque, et donc le retour pourrait avoir des effets en aval involontaires. À tout le moins, nous ne devrions pas tenter de le faire sans en comprendre les conséquences (et, étant donné l'effort de mise à niveau vers LLVM 10, nous ne devrions probablement pas essayer de choisir le retour, car ce serait un effort largement gaspillé ...)

Le commit original a-t-il été sélectionné avec soin? Au moins à partir du commentaire de @comex qui n'est pas clair pour moi ("est inclus dans la branche LLVM 9.0 utilisée par Rust" pourrait aussi signifier qu'il ne fait que partie de LLVM 9.0).

Le commit en question est un petit changement très local qui ajoute un seul paramètre à un appel de fonction et dit littéralement it is safe [in this case] to add SCEV::FlagNSW (et à en juger par le code, le nouveau paramètre peut également être SCEV::FlagNUW ), donc je pense qu'il est très probable que c'est exactement ce qui cause la mauvaise optimisation. Je peux confirmer que la suppression de ce paramètre (c'est-à-dire, changer (void)getAddRecExpr(getAddExpr(StartVal, Accum, Flags), Accum, L, Flags); en (void)getAddRecExpr(getAddExpr(StartVal, Accum), Accum, L, Flags); ) résout le problème.

De plus, ce commit problématique n'a pas été trié sur le volet. Il est juste de la malchance - On dirait que le Revert est passé après a été créé 9.0.0, donc en amont encore 9.0.0 a le paramètre incriminé. Le retour n'a pas non plus été rétroporté à 9.0.1, pour une raison quelconque. 10.0.0-rc1 et les versions ultérieures ont le retour.

Voici un commentaire qui explique pourquoi il n'est pas sûr, en fait, d'ajouter nsw ou nuw ici. C'est probablement une bonne idée de parler à un développeur LLVM à ce sujet, mais je pense que le fait de choisir le retour résoudra ce problème et n'aura aucun effet indésirable, car il est si petit et autonome.

PS Félicitations énormes à

FWIW Je peux confirmer que https://github.com/llvm/llvm-project/commit/58e8c793d0e43150a6452e971a32d7407a8a7401 est sûr de choisir, c'est un changement conservateur. Voir aussi https://lists.llvm.org/pipermail/llvm-dev/2019-September/135195.html si vous êtes intéressé par plus de contexte concernant le problème avec les indicateurs nowrap SCEV.

Je pense que je viens de trouver un moyen de reproduire le problème même après avoir rétabli le # 67174. Voici un programme légèrement plus long, mais toujours sûr, qui segmente de manière fiable les défauts sur Windows, Linux et macOS en utilisant la dernière nuit avec # 67174 rétabli:

fn do_test(x: usize) {
    let mut arr = vec![vec![0u8; 3]];

    let mut z = vec![0];
    for arr_ref in arr.iter_mut() {
        for y in 0..x {
            for _ in 0..1 {
                z.reserve_exact(x);
                let iterator = std::iter::repeat(0).take(x);
                let mut cnt = 0;
                iterator.for_each(|_| {
                    z[0] = 0;
                    cnt += 1;
                });
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &mut arr_ref[a..b];
                slice[1 << 24] += 1;
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}

Les fenêtres:

PS> rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
PS> rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 0.01s
     Running `target\release\rust-segfault.exe`
error: process didn't exit successfully: `target\release\rust-segfault.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

Linux:

$ rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
$ rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 1.13s
     Running `target/release/rust-segfault`
Segmentation fault (core dumped)

macOS:

λ rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
λ rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 0.01s
     Running `target/release/rust-segfault`
[1]    24331 segmentation fault  rustup run nightly cargo run --release

Ce programme ne dépend pas du nombre d'unités Codegen, il segfaults dans la cour, aussi (sur stable, bêta, et la nuit). J'ai également reproduit cela en compilant rustc partir de master (avec # 67174 inversé) lié à LLVM 9.

Le bogue LLVM sous-jacent est toujours le même, donc la mise à niveau vers LLVM 10 ou la sélection du correctif LLVM fait disparaître l'erreur de segmentation.

Je souhaite vraiment comprendre ce qui se passe mieux. Il semble que les vérifications des limites soient éludées à cause du supplément nuw , qui provient de valeurs SCEV incorrectement mises en cache (tout comme dans le programme C du thread @nikic lié à). Mais au moment où la mauvaise optimisation se produit, je peux à peine reconnaître mon programme simple à travers les couches de blocs de base LLVM; pire, tout changement apparemment sans opération dans le code source (par exemple, la suppression de la variable cnt ) conduit à un IR LLVM d'aspect très différent et fait disparaître le problème.

J'ai l'impression que la 1.41.1 vient d'être finalisée en # 69359 (mauvais timing de ma part), donc il n'y a pas grand-chose à faire à ce stade. Est-ce au moins une bonne idée de mettre à jour le commentaire dans Layout::repeat() avec une explication plus détaillée du problème LLVM? Si oui, je peux envoyer un PR.

J'ai l'impression que la 1.41.1 vient d'être finalisée en # 69359 (mauvais timing de ma part), donc il n'y a pas grand-chose à faire à ce stade.

Si le correctif que nous avons inclus dans 1.41.1 ne résout pas réellement le problème, nous devons reconsidérer si nous voulons rétroporter le nouveau correctif et reconstruire la version. Il y a eu un consensus lors de la réunion de l'équipe de publication pour ne pas rétroporter le correctif de LLVM, mais je pense personnellement qu'un autre ce nouveau PoC pourrait justifier une autre discussion sur le sujet.

cc @ Mark-Simulacrum @ rust-lang / release

@dfyz, nous essaierons d'obtenir une autre version de 1.41.1 avec le correctif LLVM rétroporté, en attendant le consensus sur l'envoi.

FWIW, pour moi, le nouveau reproducteur fonctionne comme prévu ( index out of bounds ) sur la version 1.38.0 stable et antérieure, mais les segfaults sur 1.39.0 et versions ultérieures. Il n'y a pas beaucoup de différence dans LLVM entre 1,38 et 1,39 (https://github.com/rust-lang/llvm-project/compare/71fe7ec06b85f612fc0e4eb4134c7a7d0f23fac5...8adf9bdccfefb8d03f0e8db4b, une différence de type Rust-B8d03f0e8db3 pourrait être générée par Rust en cours de route aussi.

le nouveau reproducteur fonctionne comme prévu (index hors limites) sur stable 1.38.0

J'ai (accidentellement) découvert que le réglage de -C codegen-units=1 sur 1.38.0 reproduit le segfault. 1.37.0 me semble sûr (aucune combinaison d'options que j'ai essayées ne produit un segfault).

Ne tenez pas compte du fait que 1.37.0 utilise LLVM 8.
Curieusement, la différence LLVM IR entre 1.37.0 et 1.38.0 (avec -C codegen-units=1 ) est juste une ligne:

- %71 = icmp eq {}* %70, null
+ %71 = icmp ule {}* %70, null

(où %70 est dérivé du résultat de <core::slice::IterMut<T> as core::iter::traits::iterator::Iterator>::next() )

Cela suffit à convaincre LLVM d'ajouter le redoutable nuw à add nuw i64 %x, -1 .

1.37.0 me semble sûr (aucune combinaison d'options que j'ai essayées ne produit un segfault).

Cela utilise LLVM 8, donc le changement SCEV blâmé ne devrait pas exister du tout.

Cela utilise LLVM 8

Mon mauvais, désolé pour la confusion (j'étais si heureux de le réduire à un différentiel d'une ligne que je n'ai même pas pris la peine de vérifier la version LLVM).

Nous avons préparé de nouveaux artefacts 1.41.1 avec le correctif LLVM sélectionné. Vous pouvez les tester localement avec:

RUSTUP_DIST_SERVER=https://dev-static.rust-lang.org rustup update stable

ping dans https://github.com/rust-lang/rust/issues/69225#issuecomment -586941455

[triagebot] Le problème a été résolu avec succès sans aucune implication de l'équipe du compilateur ping.
Bretty bien.

1.41.1 est sorti, je suppose qu'il est temps de clôturer enfin ce problème.

Cette page vous a été utile?
0 / 5 - 0 notes