Die rekursive Struktur löst eine nicht erfasste JavaScript-Ausnahme in der Browsersolidität aus

Ich versuche, eine rekursive Datenstruktur zu implementieren. Der folgende Code wird nicht in Browsersolidity kompiliert:

pragma solidity ^0.4.4;
contract TestContract {
    struct Item {
        uint someUint;
        Item[] internalItems;
    }
    Item[] items;
    function TestContract() {}
    function test() {
        Item memory item;
        items.push(item);
    }
}

Mir ist klar, dass in Blockchain-Umgebungen von Rekursion abgeraten wird, aber in diesem speziellen Fall itemist das im Wesentlichen leer, daher habe ich nicht erwartet, dass eine unendliche Rekursion auftritt. Ich erhalte unterschiedliche Fehler in Chromium und Firefox (beide in Ubuntu 16.04).

Chrom 53.0.2785.143 sagt:

Uncaught JavaScript exception:
RangeError: Maximum call stack size exceeded

(und manchmal lädt es sogar einen CPU-Kern zu 100% und friert ein)

Firefox 49.0.2 sagt:

Uncaught JavaScript exception:
InternalError: too much recursion

A habe zwei Fragen:

  1. Implementiert Browser-Solidity die Solidity-Compiler-Spezifikation vollständig? Wenn ja, wie kann die Fehlermeldung davon abhängen, welchen Browser ich verwende? Wenn nicht, was ist die Referenz-Compiler-Implementierung?

  2. Gibt es eine Möglichkeit, rekursive Datenstrukturen in Solidity zu implementieren? So etwas wie ein Baum, eine verknüpfte Liste usw.

Möglicherweise verwandtes Github-Problem: github.com/ethereum/browser-solidity/issues/32

Antworten (2)

Dies ist ein offener Fehler im Solidity-Compiler selbst, der nichts mit Browser-Solidity oder der Javascript-Laufzeit zu tun hat: https://github.com/ethereum/solidity/issues/736

Es scheint mir, dass der Compiler selbst in eine unendliche Rekursion geraten ist und daher abgestürzt ist. Der Grund, warum Sie unterschiedliche Fehler erhalten, ist, dass Firefox und Chromium die unendliche Rekursion unterschiedlich handhaben.

Um die Solidity-Dokumente zu zitieren :

Es ist nicht möglich, dass eine Struktur ein Mitglied ihres eigenen Typs enthält, obwohl die Struktur selbst der Werttyp eines Zuordnungsmitglieds sein kann.

Es gibt eine einfachere Möglichkeit, rekursive Datenstrukturen zu erstellen. Geben Sie jedem Element eine Art ID (entweder eine aufsteigende Nummer oder einen Hash der Daten). Dann können Sie nur Verweise auf diese IDs speichern. Zum Beispiel:

struct Item {
    uint ID;
    uint someInt;
    Item[] internalItems;
};
mapping (uint => Item) public Items;
uint public nextID;
// Some time later...
uint newID = nextID++;
Items[newID] = new Item(newID, _someInt);
oldItem.internalItems.push(newID);

Dies ist eine etwas grundlegende Methode. Je komplizierter Items sind, desto sinnvoller ist es, tatsächlich eine Funktion zu haben (z. B. createItem()), die dies alles richtig macht. Ich habe diesen "hausgemachten Zeiger" in diesem Beispiel auch in der Struktur selbst zwischengespeichert. Wenn Sie die Struktur einer Funktion als Argument übergeben, haben Sie nicht die uint, die sie sonst identifiziert. Wenn Sie ein Hash-System verwenden, können Sie darauf verzichten, solange Sie den Hash regenerieren können.

Darüber hinaus können Sie auf diese Weise kreisförmige Datenstrukturen erstellen, z. B. einen zyklischen Graphen.

Es gibt noch einen weiteren wichtigen Vorteil dieser Methode. Getter und rekursive Strukturen vertragen sich nicht . Wenn Sie einen solchen Getter von der Außenwelt aufrufen, gibt er kein Feld zurück, das eine Struktur in einer Struktur ist. Dies gilt auch für Arrays unbegrenzter Länge und Zuordnungen innerhalb von Strukturen.

Wenn Sie jemals vorhaben, eines dieser Elemente öffentlich zu machen, können Sie nicht einfach auf die Inhalte interner Elemente zugreifen. Solidity gibt jedoch gerne ein Array fester Länge an die Außenwelt zurück. Wenn Sie bereit sind, eine maximale Anzahl interner Elemente (z. B. zehn) zu akzeptieren, können Sie ein Dapp sehr einfach ein Element lesen und den Baum nach unten iterieren lassen.

Sie könnten auch an Bibliotheken interessiert sein , mit denen Sie sehr natürlich aussehende Datenstrukturen erstellen können.

"Diesen "hausgemachten Zeiger" tatsächlich in der Struktur selbst zwischenspeichern" - könnten Sie das bitte näher erläutern?
Sie können möglicherweise in eine Situation geraten, in der Sie Zugriff auf die Struktur haben, aber nicht auf die Uint oder den Hash, die sie identifizieren. Dies ist insbesondere bei Bibliotheken möglich, da Sie den Bibliotheksfunktionen "echte" Zeiger geben. Da Sie in dieser Situation die Adresse der Struktur nicht "nehmen" können, hilft es sehr, den Uint oder Hash, der darauf verweist, vorher zwischenzuspeichern.