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 הסובב - void, סקלרי, או רשימה - כך ש־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 נמצא כעת בתוךeval(אוtry).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 מסתיים באופן רגיל, $@ תמיד אחד מ:
המחרוזת הריקה - הבלוק רץ עד הסוף ללא חריגה.
מחרוזת המסתיימת בתו שורה חדשה -
die "message\n"או חריגת מחרוזת שהנקרא כבר סיים.מחרוזת עם מיקום מצורף (
... at FILE line N.\n) -die "message"ללא תו שורה חדשה מסיים, או שגיאת זמן ריצה ש־Perl עצמו העלה.הפניה, בדרך כלל עצם חריגה מבורך -
dieשנקרא עם הפניה.
יש לבדוק את $@ מיד לאחר ה־eval ולא במקום אחר. כל קריאת מתודה, בדיקת ref, בדיקת isa, או התאמת regex הנעשית במהלך הטיפול יכולה בעצמה להפעיל eval פנימי (למשל, במהלך autoloading או פתרון overload) ולדרוס את $@ לפני שתסיימו לבחון אותו. יש להעתיק למשתנה לקסיקלי ראשון:
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 /'"הוא גם רק מחרוזת.אם באמת נחוץ קוד שנבחר בזמן ריצה, יש להגביל את הקלט מול רשימת־התרים קפדנית לפני השיבוץ, ולהעדיף ניתוב מונחה־נתונים (האש של הפניות קוד) על־פני יצירת מקור.
ערכי נקודה צפה המשובצים במקור שבירים. תחת
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־פלוס־$@ נשאר נכון ועדיין הכלי הנכון בכל פעם שהקוד הסובב חייב לרוץ על Perl ישנים יותר או לא יכול לדרוש את שומר התכונות.
דוגמאות#
לכידת שגיאת זמן ריצה והמשכת הריצה:
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 };
תפיסת חריגה מבורכת, זריקה מחדש של כל דבר אחר:
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. רק כותבי דבאגרים נתקלים בזה.כישלונות טעינת XS - חלק מבעיות הממשק הבינארי במהלך
requireשל מודול XS הן קטלניות גם בתוךeval, אלא אם$ENV{PERL_DL_NONLAZY}נקבע. הפרשי גרסה בין המפרש הרץ ל־.soבנוי־מראש הם המקרה הנפוץ. pperl אינו טוען XS; האזהרה חלה כאשר קוד שעוברevalמאצֵל את הטעינה ל־perl5 קלאסי.
הבדלים מהמעלה־הזרם#
תאימות מלאה עם Perl 5.42 מהמעלה־הזרם.
ראו גם#
die- המקבילה: זורק את החריגה ש־evalתופס. כלל תו השורה החדשה המסיים על ההודעה מעצב את מה שנוחת ב־$@.do-do FILEקורא ומעריך קובץ באותה דרך ש־eval מחרוזת קורא ומעריך מחרוזת; שימושי עבור קובצי הגדרה הכתובים ב־Perl.return- בתוךeval BLOCKחוזר מתת־השגרה המקיפה, לא מהבלוק; יש להשתמש בביטוי מסיים כדי למסור ערך בחזרה מה־evalעצמו.require- לרוב עטוף ב־evalכדי לבחון מודולים אופציונליים ללא יציאה בזמן הידור.evalbytes- eval מחרוזת שמתייחס לארגומנט שלו כמחרוזת בתים ומאפשר למסנני מקור לעבוד באופן רגיל; לשימוש כאשר סמנטיקת רמת־בית חשובה.$@- היכן שחריגה נתפסת נוחתת, מנוקה למחרוזת הריקה ב־evalמוצלח. עמוד משפחת השגיאות מכסה גם את הסכנה הקלאסית של דריסת$@.$^S- אומר למטפלי$SIG{__DIE__}ו־$SIG{__WARN__}האםevalאוtryפעיל כעת.$SIG{__DIE__}- הוו טרום־זריקה; נורה גם עבור חריגות ש־evalמקיף עומד לתפוס, וזו הסיבה הרגילה לlocal־איזציה שלו למשך הלכידה.