Eine Funktionsreferenz umwandeln, die einen ungültigen Zeiger erzeugt?

9

Ich habe einen Fehler im Code von Drittanbietern aufgespürt und ihn auf etwas in der Art von eingegrenzt.

use libc::c_void;

pub unsafe fn foo() {}

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

Bei stabiler Version 1.38.0 wird der Funktionszeiger gedruckt, aber Beta (1.39.0-Beta.6) und nächtliche Rückgabe '1'. ( Spielplatz )

Worauf wird geschlossen _und warum hat sich das Verhalten geändert?

Ich gehe davon aus, dass der richtige Weg, dies zu übertragen, einfach wäre foo as *const c_void, aber dies ist nicht mein Code.

Maciej Goszczycki
quelle
Ich kann das "Warum hat es sich geändert" nicht beantworten, aber ich stimme Ihnen zu, dass der Code zunächst falsch ist. fooist bereits ein Funktionszeiger, daher sollten Sie ihm keine Adresse geben. Dadurch entsteht eine doppelte Referenz, die scheinbar auf einen Typ mit der Größe Null (also den magischen Wert 1) verweist .
Shepmaster
Dies beantwortet Ihre Frage nicht genau, aber Sie möchten wahrscheinlich:let ptr = foo as *const fn() as *const c_void;
Peter Hall

Antworten:

3

Diese Antwort basiert auf den Antworten auf den durch diese Frage motivierten Fehlerbericht .

Jede Funktion in Rust hat ihren eigenen Funktionselementtyp , der sich vom Funktionselementtyp jeder anderen Funktion unterscheidet. Aus diesem Grund muss eine Instanz des Funktionselementtyps überhaupt keine Informationen speichern. Auf welche Funktion sie verweist, geht aus ihrem Typ hervor. Also die Variable x in

let x = foo;

ist eine Variable der Größe 0.

Funktionselementtypen werden bei Bedarf implizit zu Funktionszeigertypen gezwungen . Die Variable

let x: fn() = foo;

ist ein generischer Zeiger auf eine Funktion mit Signatur fn()und muss daher einen Zeiger auf die Funktion speichern, auf die er tatsächlich zeigt, sodass die Größe von xdie Größe eines Zeigers ist.

Wenn Sie die Adresse einer Funktion verwenden, nehmen &fooSie tatsächlich die Adresse eines temporären Werts mit der Größe Null. Vor dieser Festschreibung für das rustRepo wurden temporäre Dateien mit der Größe Null verwendet, um eine Zuordnung auf dem Stapel zu erstellen, und &foodie Adresse dieser Zuordnung zurückgegeben. Seit diesem Commit erstellen Typen mit der Größe Null keine Zuordnungen mehr und verwenden stattdessen die magische Adresse 1. Dies erklärt den Unterschied zwischen den verschiedenen Versionen von Rust.

Sven Marnach
quelle
Das macht Sinn, aber ich bin nicht davon überzeugt, dass es allgemein erwünschtes Verhalten ist, weil es auf einer prekären Annahme beruht. Innerhalb des sicheren Rust-Codes gibt es keinen Grund, Zeiger auf einen Wert einer ZST zu unterscheiden, da nur ein möglicher Wert zur Kompilierungszeit bekannt ist. Dies bricht zusammen, sobald Sie einen ZST-Wert außerhalb des Rust-Typsystems verwenden müssen, wie hier. Es betrifft wahrscheinlich nur fnArtikeltypen und nicht erfassende Verschlüsse, und für diese gibt es eine Problemumgehung, wie in meiner Antwort, aber es ist immer noch eine ziemliche Fußwaffe!
Peter Hall
Ok, ich hatte die neueren Antworten zum Github-Problem nicht gelesen. Ich könnte einen Segfault mit diesem Code bekommen, aber wenn der Code einen Segfault verursachen könnte, dann denke ich, dass das neue Verhalten in Ordnung ist.
Peter Hall
Gute Antwort. @PeterHall Ich habe das Gleiche gedacht und bin immer noch nicht zu 100% mit dem Thema beschäftigt, aber zumindest für temporäre und andere Stapelvariablen sollte es kein Problem geben, alle Werte mit der Größe Null auf 0x1 zu setzen, da der Compiler no macht Garantien für das Stapellayout, und Sie können die Eindeutigkeit von Zeigern auf ZSTs sowieso nicht garantieren. Dies unterscheidet sich beispielsweise von einem Casting *const i32, bei *const c_voiddem nach meinem Verständnis die Identität des Zeigers garantiert erhalten bleibt.
Trentcl
2

Worauf wird geschlossen _und warum hat sich das Verhalten geändert?

Jedes Mal, wenn Sie eine Rohzeigerumwandlung durchführen, können Sie nur eine Information ändern (Referenz oder Rohzeiger; Veränderlichkeit; Typ). Wenn Sie diese Besetzung machen:

let ptr = &foo as *const _

da man von einem Verweis auf einen Rohzeiger geändert haben, abgeleitet der Typ für _ müssen unverändert sein und ist daher die Art der foo, die für die Funktion einige unaussprechliche Typ istfoo .

Stattdessen können Sie direkt in einen Funktionszeiger umwandeln, der in der Rust-Syntax ausgedrückt werden kann:

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

Warum es sich geändert hat, ist schwer zu sagen. Es könnte ein Fehler im nächtlichen Build sein. Es lohnt sich, dies zu melden - auch wenn es sich nicht um einen Fehler handelt, erhalten Sie vom Compilerteam wahrscheinlich eine gute Erklärung darüber, was tatsächlich vor sich geht!

Peter Hall
quelle
1
Danke, ich habe es gemeldet. Github.com/rust-lang/rust/issues/65499
Maciej Goszczycki
@MaciejGoszczycki Danke für die Berichterstattung! Die Antworten haben tatsächlich die Dinge für mich geklärt - ich werde eine Antwort basierend auf den Antworten dort veröffentlichen.
Sven Marnach