Casting d'une référence de fonction produisant un pointeur invalide?

9

Je recherche une erreur dans le code tiers et je l'ai réduite à quelque chose dans le sens de.

use libc::c_void;

pub unsafe fn foo() {}

fn main() {
    let ptr = &foo as *const _ as *const c_void;
    println!("{:x}", ptr as usize);
}

Fonctionné sur la version 1.38.0 stable, cela affiche le pointeur de la fonction, mais la version bêta (1.39.0-beta.6) et la nuit renvoient «1». ( Aire de jeux )

À quoi est-il _déduit et pourquoi le comportement a-t-il changé?

Je suppose que la bonne façon de lancer ce serait simplement foo as *const c_void, mais ce n'est pas mon code.

Maciej Goszczycki
la source
Je ne peux pas répondre au "pourquoi a-t-il changé", mais je suis d'accord avec vous que le code est incorrect pour commencer. fooest déjà un pointeur de fonction, vous ne devez donc pas lui attribuer d'adresse. Cela crée une double référence, apparemment à un type de taille nulle (donc la valeur magique 1).
Shepmaster
Cela ne répond pas exactement à votre question, mais vous voulez probablement:let ptr = foo as *const fn() as *const c_void;
Peter Hall

Réponses:

3

Cette réponse est basée sur les réponses au rapport de bug motivées par cette question .

Chaque fonction dans Rust a son type d'élément de fonction individuel , qui est distinct du type d'élément de fonction de toutes les autres fonctions. Pour cette raison, une instance du type d'élément de fonction n'a pas besoin de stocker d'informations du tout - la fonction vers laquelle elle pointe est claire de son type. Donc, la variable x dans

let x = foo;

est une variable de taille 0.

Les types d'élément de fonction contraignent implicitement les types de pointeur de fonction si nécessaire. La variable

let x: fn() = foo;

est un pointeur générique vers n'importe quelle fonction avec signature fn(), et doit donc stocker un pointeur vers la fonction vers laquelle il pointe réellement, donc la taille de xest la taille d'un pointeur.

Si vous prenez l'adresse d'une fonction, &foovous prenez en fait l'adresse d'une valeur temporaire de taille nulle. Avant cette validation dans le rustréférentiel , des temporaires de taille nulle avaient l'habitude de créer une allocation sur la pile et &foorenvoyaient l'adresse de cette allocation. Depuis cette validation, les types de taille zéro ne créent plus d'allocations et utilisent plutôt l'adresse magique 1. Cela explique la différence entre les différentes versions de Rust.

Sven Marnach
la source
Cela a du sens, mais je ne suis pas convaincu que ce comportement soit généralement souhaité, car il repose sur une hypothèse précaire. Dans le code sécurisé de Rust, il n'y a aucune raison de distinguer les pointeurs vers une valeur d'un ZST - car il n'y a qu'une seule valeur possible qui est connue au moment de la compilation. Cela tombe en panne une fois que vous devez utiliser une valeur ZST en dehors du système de type Rust, comme ici. Cela n'affecte probablement que les fntypes d'éléments et les fermetures non capturantes et pour ceux-ci, il existe une solution de contournement, comme dans ma réponse, mais c'est toujours une arme à feu!
Peter Hall
D'accord, je n'avais pas lu les nouvelles réponses sur le problème Github. Je pourrais obtenir une erreur de segmentation avec ce code mais, si le code pouvait provoquer une erreur de segmentation, je suppose que le nouveau comportement est correct.
Peter Hall du
Très bonne réponse. @PeterHall Je pensais la même chose, et je ne suis toujours pas à 100% sur le sujet, mais au moins pour les variables temporaires et autres variables de pile, il ne devrait pas y avoir de problème à mettre toutes les valeurs de taille nulle à 0x1 parce que le compilateur ne fait pas garantit la disposition de la pile, et vous ne pouvez pas garantir l'unicité des pointeurs vers les ZST de toute façon. C'est différent, disons, de lancer un film *const i32sur *const c_voidlequel, à ma connaissance, il est toujours garanti de préserver l'identité du pointeur.
trentcl
2

À quoi est-il _déduit et pourquoi le comportement a-t-il changé?

Chaque fois que vous effectuez une conversion de pointeur brut, vous ne pouvez modifier qu'une seule information (référence ou pointeur brut; mutabilité; type). Par conséquent, si vous effectuez ce casting:

let ptr = &foo as *const _

puisque vous êtes passé d'une référence à un pointeur brut, le type déduit pour _ doit être inchangé et est donc le type de foo, qui est un type inexprimable pour la fonction foo.

Au lieu de cela, vous pouvez directement transtyper vers un pointeur de fonction, qui est exprimable dans la syntaxe Rust:

let ptr = foo as *const fn() as *const c_void;

Quant à savoir pourquoi cela a changé, c'est difficile à dire. Cela pourrait être un bug dans la version nocturne. Cela vaut la peine de le signaler - même s'il ne s'agit pas d'un bogue, vous obtiendrez probablement une bonne explication de l'équipe du compilateur sur ce qui se passe réellement!

Peter Hall
la source
1
Merci je l'ai signalé github.com/rust-lang/rust/issues/65499
Maciej Goszczycki
@MaciejGoszczycki Merci d'avoir signalé! Les réponses ont en fait clarifié les choses pour moi - je posterai une réponse basée sur les réponses là-bas.
Sven Marnach