Ροή ελέγχου

eval#

Εκτελεί ένα κομμάτι κώδικα Perl με τυχόν μοιραίο σφάλμα να παγιδεύεται αντί να σκοτώνει το πρόγραμμα.

Η eval είναι ταυτόχρονα ο μηχανισμός πιασίματος εξαιρέσεων της Perl και ο φορτωτής κώδικα κατά τον χρόνο εκτέλεσης. Έρχεται σε δύο πραγματικές μορφές: eval μπλοκ (eval { ... }), που πιάνει εξαιρέσεις από ήδη μεταγλωττισμένο κώδικα, και eval συμβολοσειράς (eval EXPR), που μεταγλωττίζει και εκτελεί μια συμβολοσειρά πηγαίου κώδικα Perl σε χρόνο εκτέλεσης. Όπως και να έχει, μια die που διαφορετικά θα τερμάτιζε το πρόγραμμα γίνεται ανάθεση στην $@ και κανονική επιστροφή.

Σύνοψη#

eval BLOCK                  # catch exceptions in compiled code
eval EXPR                   # compile and run source at runtime
eval                        # short for: eval $_

Τι επιστρέφεται#

Η τιμή της τελευταίας έκφρασης που αποτιμήθηκε μέσα στο μίνι-πρόγραμμα, ή η τιμή που παραδόθηκε σε ρητή return. Η αποτίμηση γίνεται στο ίδιο περιβάλλον με την περιβάλλουσα eval - κενό, βαθμωτό ή λίστας - οπότε η wantarray μέσα σε eval αναφέρει το περιβάλλον του καλούντος, όχι «κάποιο περιβάλλον eval».

Αν κάτι μέσα εγείρει εξαίρεση (die, διαίρεση δια του μηδενός, συντακτικό σφάλμα σε eval συμβολοσειράς, οποιοδήποτε άλλο παγιδεύσιμο μοιραίο), η eval επιστρέφει undef σε βαθμωτό περιβάλλον και την κενή λίστα σε περιβάλλον λίστας, και η εξαίρεση προσγειώνεται στην $@:

my $n = eval { risky() };   # undef on failure, $@ holds the error
if ($@) { ... }

Σε επιτυχία, η $@ τίθεται στην κενή συμβολοσειρά. Κάθε ολοκληρωμένη eval αγγίζει την $@ - δεν υπάρχει επιλογή «αφήστε τη ήσυχη». Ένας τελεστής ροής ελέγχου που παρακάμπτει την κανονική έξοδο (last, goto έξω από το μπλοκ) μπορεί να αφήσει την $@ αμετάβλητη, που είναι οριακή περίπτωση, όχι χαρακτηριστικό στο οποίο να βασίζεστε.

Καθολική κατάσταση που επηρεάζει#

  • $@ - εγγράφεται σε κάθε κανονική έξοδο της eval: η εκτοξευμένη εξαίρεση σε αποτυχία, η κενή συμβολοσειρά σε επιτυχία. Διαβάζεται για να καθοριστεί αν η eval έπιασε κάτι.

  • $^S - αντικατοπτρίζει αν η Perl βρίσκεται αυτή τη στιγμή μέσα σε evaltry). undef κατά την ανάλυση, αληθής κατά την εκτέλεση μέσα σε μπλοκ eval/try, ψευδής στο ανώτατο επίπεδο. Διαβάζεται κυρίως από χειριστές $SIG{__DIE__} που θέλουν να ενεργήσουν διαφορετικά για πιασμένες έναντι μη πιασμένων εξαιρέσεων.

  • $SIG{__DIE__} - πυροδοτείται σε κάθε die, συμπεριλαμβανομένων εκείνων που μια περικλείουσα eval πρόκειται να πιάσει. Χρησιμοποιήστε local $SIG{__DIE__} μέσα σε eval για να καταστείλετε έναν καθολικά εγκαταστημένο γάντζο για τη διάρκεια της παγίδας.

  • $SIG{__WARN__} - δεν επηρεάζεται από την eval. Οι προειδοποιήσεις που εκπέμπονται μέσα στο μπλοκ εξακολουθούν να ακολουθούν την κανονική διαδρομή προειδοποίησης· η eval δεν τις ανακατευθύνει στην $@.

  • $_ - η πηγή για τη μορφή χωρίς όρισμα (eval χωρίς τίποτα μετά από αυτή είναι ακριβώς eval $_).

eval μπλοκ έναντι eval συμβολοσειράς#

Οι δύο μορφές μοιάζουν παρόμοιες αλλά κάνουν πολύ διαφορετικά πράγματα.

  • eval μπλοκ (eval { ... }) αναλύεται και μεταγλωττίζεται μαζί με το περιβάλλον πρόγραμμα. Σε χρόνο εκτέλεσης είναι απλώς ένα σήμα που λέει «πιάσε όποια die εκτοξευθεί από μέσα από αυτό το μπλοκ». Επειδή ο κώδικας είναι ήδη μεταγλωττισμένος, η eval μπλοκ είναι φθηνή και ασφαλής - ο μεταγλωττιστής τον έχει ήδη ελέγξει για συντακτικά σφάλματα. Αυτή είναι η μορφή στην οποία πρέπει να καταφεύγετε όποτε ο στόχος είναι ο χειρισμός εξαιρέσεων.

  • eval συμβολοσειράς (eval EXPR) αποτιμά την EXPR σε βαθμωτό περιβάλλον, στη συνέχεια αναλύει και εκτελεί την προκύπτουσα συμβολοσειρά σαν να ήταν μπλοκ στη λεκτική εμβέλεια του περιβάλλοντος κώδικα. Η ανάλυση γίνεται κάθε φορά που προσεγγίζεται η eval. Ένα συντακτικό σφάλμα στη συμβολοσειρά γίνεται $@ σε χρόνο εκτέλεσης, όχι αποτυχία κατά τη μεταγλώττιση του περιβάλλοντος προγράμματος. Αυτή είναι η μορφή για τη φόρτωση κώδικα που αποφασίζεται σε χρόνο εκτέλεσης - προαιρετικά χαρακτηριστικά, εκφράσεις παρεχόμενες από τον χρήστη, δυναμικά κατασκευασμένες υπορουτίνες - και κουβαλά τα κόστη και τους κινδύνους που αυτό συνεπάγεται.

Και οι δύο μορφές εκτελούνται στη λεκτική εμβέλεια του περιβάλλοντος κώδικα: οι εξωτερικές μεταβλητές my είναι ορατές, και αναθέσεις σε μεταβλητές πακέτου, ορισμοί υπορουτινών και ορισμοί φορμάτ που γίνονται μέσα στην eval παραμένουν αφού αυτή επιστρέψει.

Το συμβόλαιο της $@#

Μετά την κανονική ολοκλήρωση μιας eval, η $@ είναι πάντα ένα από:

  • Η κενή συμβολοσειρά - το μπλοκ έτρεξε μέχρι το τέλος χωρίς εξαίρεση.

  • Συμβολοσειρά που τελειώνει σε newline - die "message\n" ή συμβολοσειριακή εξαίρεση που ο καλούμενος έχει ήδη τερματίσει.

  • Συμβολοσειρά με προσαρτημένη τοποθεσία (... at FILE line N.\n) - die "message" χωρίς τελικό newline, ή σφάλμα χρόνου εκτέλεσης που εγείρει η ίδια η Perl.

  • Μια αναφορά, τυπικά ένα blessed αντικείμενο εξαίρεσης - η die που κλήθηκε με αναφορά.

Ελέγξτε την $@ αμέσως μετά την eval και πουθενά αλλού. Οποιαδήποτε κλήση μεθόδου, έλεγχος ref, έλεγχος isa ή ταίριασμα regex κατά τον χειρισμό μπορεί να καλέσει η ίδια εσωτερικά μια eval (για παράδειγμα κατά την autoload ή την επίλυση υπερφόρτωσης) και να γράψει πάνω στην $@ πριν τελειώσετε την επιθεώρησή της. Αντιγράψτε πρώτα σε λεκτική μεταβλητή:

eval { work() };
if (my $err = $@) {         # safe snapshot
    if (ref $err && $err->isa('MyApp::Error')) { handle($err) }
    else                                         { die $err      }
}

Εμφωλευμένη eval και local $@#

Η εξωτερική eval βλέπει ό,τι κρατά η $@ όταν τελειώσει το εξωτερικό μπλοκ - όχι ό,τι κρατούσε στο ενδιάμεσο. Αν ο εσωτερικός κώδικας έχει δική του eval, αυτή η εσωτερική eval θέτει την $@ (στην κενή συμβολοσειρά σε επιτυχία), που μπορεί να αλλοιώσει ένα σφάλμα που ο εξωτερικός κώδικας ήθελε να διαφυλάξει. Η προστασία είναι local $@ γύρω από την εσωτερική εργασία:

eval {
    # ... some work that might fail ...
    {
        local $@;           # shield outer $@
        eval { optional_step() };
        # ignore whatever optional_step did to $@
    }
    die "still needed\n" if $condition;
};
warn "outer caught: $@" if $@;

Πριν την Perl 5.14, η $@ ανατίθετο πριν αποκατασταθούν οι τοπικοποιημένες μεταβλητές, οπότε κώδικας που ήθελε να φιλτράρει σφάλματα έπρεπε να αναμεταδίδει την τιμή μέσω προσωρινής μεταβλητής. Στην 5.14+ (την οποία κληρονομεί η 5.42) η σειρά είναι διορθωμένη και η προσωρινή μεταβλητή είναι περιττή.

Ασφάλεια της eval συμβολοσειράς#

Η eval συμβολοσειράς εκτελεί ό,τι βρίσκεται στην έκφραση. Αν οποιοδήποτε μέρος αυτής της συμβολοσειράς προήλθε από μη έμπιστη είσοδο - αρχείο, δικτυακό άκρο, φόρμα χρήστη - η eval παραδίδει στον επιτιθέμενο τον πλήρη διερμηνέα Perl. Πρακτικοί κανόνες:

  • Μη διαβιβάζετε ποτέ είσοδο χρήστη μέσω eval EXPR ατροποποίητη. «Είναι μόνο αριθμός» δεν αποτελεί δικλείδα - και το "1; system 'rm -rf /'" είναι μόνο μια συμβολοσειρά.

  • Αν χρειάζεται πραγματικά κώδικας επιλεγμένος σε χρόνο εκτέλεσης, περιορίστε την είσοδο έναντι μιας αυστηρής λίστας επιτρεπτών πριν την παρεμβολή, και προτιμήστε καθοδηγούμενη από δεδομένα διανομή (hash από coderefs) έναντι παραγωγής πηγαίου κώδικα.

  • Τιμές κινητής υποδιαστολής που παρεμβάλλονται σε πηγαίο κώδικα είναι εύθραυστες. Υπό use locale ο διαχωριστής δεκαδικών μπορεί να μην είναι τελεία, και τιμές όπως "NaN" ή "Infinity" μετατρέπονται σε συμβολοσειρές με γράμματα που ο αναλυτής διαβάζει ως barewords, όχι αριθμούς.

  • Φίλτρα πηγαίου κώδικα που ενεργοποιούνται μέσα σε eval συμβολοσειράς διαρρέουν στην περιβάλλουσα εμβέλεια αρχείου (εκτός αν χρησιμοποιηθεί η evalbytes). Αυτή είναι μακροχρόνια ιδιοτροπία, όχι χαρακτηριστικό.

Η eval μπλοκ δεν έχει κανένα από αυτά τα προβλήματα - ο κώδικας μεταγλωττίστηκε μαζί με το υπόλοιπο πρόγραμμα.

BEGIN μέσα σε eval συμβολοσειράς#

Μπλοκ BEGIN { ... } ενσωματωμένα σε eval συμβολοσειράς εκτελούνται αμέσως, πριν τρέξει ο υπόλοιπος κώδικας της eval, ταιριάζοντας τη συμπεριφορά τους σε μια κανονική μονάδα μεταγλώττισης. Θέστε την ${^MAX_NESTED_EVAL_BEGIN_BLOCKS} σε 0 ώστε όποιο ενσωματωμένο BEGIN να εκτοξεύει εξαίρεση αντί να εκτελείται:

local ${^MAX_NESTED_EVAL_BEGIN_BLOCKS} = 0;
eval $untrusted;            # any BEGIN inside $untrusted dies

Αυτό δεν κάνει ασφαλή την eval συμβολοσειράς - απλώς αφαιρεί μία συγκεκριμένη επίθεση στις παρενέργειες κατά τη μεταγλώττιση.

try/catch#

Υπό use feature 'try' (σταθερό από την 5.40, διαθέσιμο στην 5.42), η κατασκευή try/catch/finally παρέχει αποκλειστική σύνταξη χειρισμού εξαιρέσεων:

use feature 'try';

try {
    risky();
}
catch ($e) {
    warn "caught: $e";
}
finally {
    cleanup();
}

Διαφορές από το eval { ... }; if ($@) { ... }:

  • Η πιασμένη τιμή δένεται σε νέα λεκτική $e (ή σε όποιο όνομα δοθεί), όχι στην $@. Δεν χρειάζεται ο χορός στιγμιότυπου my $err = $@.

  • Η $@ δεν τίθεται ως παρενέργεια - το μπλοκ try δεν έχει καθολική παρενέργεια σε αυτή.

  • Η try δεν είναι από μόνη της βρόχος, αλλά οι τελεστές ροής ελέγχου συμπεριφέρονται ανάλογα με την eval BLOCK: η return μέσα σε try επιστρέφει από την περικλείουσα υπορουτίνα, και οι next/last/redo στοχεύουν εξωτερικό βρόχο.

  • Η finally εκτελείται σε κάθε διαδρομή εξόδου και δεν μπορεί η ίδια να χρησιμοποιήσει return/next/last/redo. Είναι σημαδεμένη ως πειραματική στην 5.42 και εκπέμπει προειδοποίηση υπό experimental::try.

Καταφύγετε στην try/catch σε νέο κώδικα. Το ιδίωμα eval-συν-$@ παραμένει σωστό και εξακολουθεί να είναι το σωστό εργαλείο όποτε ο περιβάλλων κώδικας πρέπει να τρέχει σε παλαιότερες Perls ή να μην απαιτεί την προστασία feature.

Παραδείγματα#

Παγίδευση σφάλματος χρόνου εκτέλεσης και συνέχιση:

my $q = eval { $x / $y };
if ($@) { warn "division failed: $@"; $q = 0 }

Ανίχνευση αν ένα χαρακτηριστικό είναι διαθέσιμο χωρίς τερματισμό κατά την εκκίνηση:

my $have_socket = eval { socket(my $s, PF_INET, SOCK_STREAM, 0); 1 };
die "no sockets on this build" unless $have_socket;

Φόρτωση αρθρώματος του οποίου η απουσία θα πρέπει να είναι ανεκτή:

my $json = eval { require JSON::XS; JSON::XS->new } //
           do { require JSON::PP; JSON::PP->new };

Πιάσιμο blessed εξαίρεσης, επανεκτόξευση οποιουδήποτε άλλου:

eval {
    parse($input);
};
if (my $err = $@) {
    if (ref($err) && $err->isa('MyApp::ParseError')) {
        report($err);
    }
    else {
        die $err;
    }
}

Εμφωλευμένη eval με local $@ ώστε ένα εσωτερικό προαιρετικό βήμα να μη σβήσει ένα εξωτερικό διαγνωστικό:

eval {
    do_main_work();
    { local $@; eval { maybe_cleanup() } }   # ignore cleanup errors
    die "main still failed\n" if $something_else_wrong;
};
warn $@ if $@;

Καταστολή ενός καθολικά εγκαταστημένου γάντζου $SIG{__DIE__} για τη διάρκεια ιδιωτικής παγίδας:

eval {
    local $SIG{__DIE__};            # don't trigger hooks while we probe
    $answer = $x / $y;
};
warn $@ if $@;

Ένας γάντζος $SIG{__DIE__} που καταγράφει μόνο μη πιασμένες εξαιρέσεις, χρησιμοποιώντας τη $^S για να διακρίνει τις δύο περιπτώσεις:

$SIG{__DIE__} = sub {
    return if $^S;              # inside eval/try - let it propagate
    log_fatal(@_);              # top level - record before exit
};

Δυναμικά κατασκευασμένη διανομή (eval συμβολοσειράς που χρησιμοποιείται σκόπιμα, η είσοδος δεν προέρχεται από χρήστη):

for my $op (qw(add sub mul div)) {
    eval "sub ${op}_of { \$_[0] "
       . { add=>'+', sub=>'-', mul=>'*', div=>'/' }->{$op}
       . " \$_[1] }";
    die $@ if $@;
}

Οριακές περιπτώσεις#

  • Χωρίς όρισμα - η eval; είναι eval $_. Σπάνια αυτό που θέλετε· γράψτε ρητά eval { ... } ή eval EXPR.

  • Τελικό ερωτηματικό - μπορεί να παραλειφθεί από το τέλος του EXPR ή του BLOCK. Τα eval "1+2" και eval "1+2;" είναι ισοδύναμα.

  • Η eval BLOCK δεν είναι βρόχος - οι next, last και redo δεν μπορούν να αφήσουν ή να επανεκκινήσουν το μπλοκ. Ψάχνουν περικλείοντα βρόχο και κράζουν αν δεν υπάρχει.

  • Η return μέσα σε eval BLOCK επιστρέφει από την περικλείουσα υπορουτίνα, όχι από την eval - το μπλοκ eval δεν είναι σώμα συνάρτησης. Για να παράγετε τιμή από το μπλοκ, βάλτε την τιμή ως την τελική έκφραση.

  • Οι προειδοποιήσεις δεν παγιδεύονται. Η warn μέσα σε eval εξακολουθεί να γράφει στο STDERR. Δρομολογήστε τις στην $@ μόνο αν θέσετε εσείς οι ίδιοι την $SIG{__WARN__} να το κάνει.

  • Ροή ελέγχου έξω από την eval - μια goto, last ή next που πηδά έξω από το μπλοκ παρακάμπτει την κανονική έξοδο της eval και συνεπώς ενδέχεται να αφήσει την $@ ανέγγιχτη. Μην κατασκευάζετε λογική που εξαρτάται από αυτό.

  • Εισαγωγικά στην EXPR - eval $x έναντι eval "$x" συμπεριφέρονται πανομοιότυπα: και τα δύο εκτελούν τον κώδικα που περιέχει η $x. Τα διπλά εισαγωγικά προσθέτουν οπτικό θόρυβο, όχι σημασιολογία.

  • eval '$x' έναντι eval { $x } - και τα δύο επιστρέφουν την τιμή της $x. Η μορφή μπλοκ μεταγλωττίζεται μαζί με το περιβάλλον πρόγραμμα· προτιμήστε την εκτός αν ο κώδικας χρειάζεται πράγματι να κατασκευαστεί σε χρόνο εκτέλεσης.

  • eval '' μέσα στο πακέτο DB - δεν βλέπει τη συνηθισμένη περιβάλλουσα λεκτική εμβέλεια αλλά την εμβέλεια του πρώτου μη-DB καλούντος. Αυτό το αντιμετωπίζουν μόνο όσοι γράφουν debugger.

  • Αποτυχίες φόρτωσης XS - κάποια προβλήματα δυαδικής διεπαφής κατά την require ενός αρθρώματος XS είναι μοιραία ακόμη και μέσα σε eval, εκτός αν τεθεί η $ENV{PERL_DL_NONLAZY}. Η συνηθισμένη περίπτωση είναι ασυμβατότητα εκδόσεων μεταξύ του τρέχοντος διερμηνέα και ενός προ-κατασκευασμένου .so. Το pperl δεν φορτώνει XS· η επιφύλαξη ισχύει όταν κώδικας μέσα σε eval αναθέτει τη φόρτωση σε ένα κλασικό perl5.

Διαφορές από το upstream#

Πλήρως συμβατό με το upstream Perl 5.42.

Δείτε επίσης#

  • die - το αντίστοιχο: εκτοξεύει την εξαίρεση που πιάνει η eval. Ο κανόνας του τελικού newline στο μήνυμα διαμορφώνει το τι προσγειώνεται στην $@.

  • do - do FILE διαβάζει και αποτιμά ένα αρχείο όπως η eval συμβολοσειράς διαβάζει και αποτιμά μια συμβολοσειρά· χρήσιμο για αρχεία ρυθμίσεων γραμμένα σε Perl.

  • return - μέσα σε eval BLOCK επιστρέφει από την περικλείουσα υπορουτίνα, όχι από το μπλοκ· χρησιμοποιήστε μια τελική έκφραση για να επιστρέψετε τιμή από την ίδια την eval.

  • require - συχνά τυλίγεται σε eval για ανίχνευση προαιρετικών αρθρωμάτων χωρίς τερματισμό κατά τη μεταγλώττιση.

  • evalbytes - eval συμβολοσειράς που μεταχειρίζεται το όρισμά της ως συμβολοσειρά bytes και επιτρέπει στα φίλτρα πηγαίου κώδικα να λειτουργούν κανονικά· χρησιμοποιήστε την όταν έχει σημασία η σημασιολογία επιπέδου byte.

  • $@ - εκεί όπου προσγειώνεται μια πιασμένη εξαίρεση, καθαρίζεται στην κενή συμβολοσειρά σε επιτυχημένη eval. Η σελίδα της οικογένειας error καλύπτει επίσης τον κλασικό κίνδυνο αλλοίωσης της $@.

  • $^S - λέει στους χειριστές $SIG{__DIE__} και $SIG{__WARN__} αν είναι αυτή τη στιγμή ενεργή κάποια eval ή try.

  • $SIG{__DIE__} - γάντζος πριν την εκτόξευση· πυροδοτείται ακόμη και για εξαιρέσεις που μια περικλείουσα eval πρόκειται να πιάσει, που είναι ο συνηθισμένος λόγος για να την local-οποιήσετε για τη διάρκεια μιας παγίδας.