פתרון חקירת השחתה ודליפות¶
הטעות הבסיסית היא הפרדה בין כתובת לבין הגבול שהכתובת מייצגת. פונקציה שמקבלת רק char* אינה יודעת כמה בתים מותר לה לכתוב. נשתמש ב־span ונבחר חוזה שבו יעד קצר נדחה לפני הכתיבה הראשונה. הקוד הבא מדגים את הפונקציה המתוקנת ואת מקרי הקצה, ללא מסלול פגום פעיל:
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <span>
bool copy_bytes(std::span<std::byte> destination,
std::span<const std::byte> source) {
if (destination.size() < source.size()) return false;
std::copy(source.begin(), source.end(), destination.begin());
return true;
}
int main() {
const std::array source{std::byte{1}, std::byte{2}};
std::array<std::byte, 2> exact{};
std::array<std::byte, 1> short_buffer{std::byte{9}};
assert(copy_bytes(exact, source));
assert(exact == source);
assert(!copy_bytes(short_buffer, source));
assert(short_buffer[0] == std::byte{9});
assert(copy_bytes({}, {}));
assert(!copy_bytes({}, source));
}
הקוד מניח שאין חפיפה בעייתית בין המקור ליעד; בדוגמה המערכים נפרדים. אם הממשק אמור לתמוך בחפיפה, צריך להגדיר זאת ולהשתמש במנגנון העתקה המתאים לכך. העובדה שהחלפנו pointer ב־span אינה מגדירה בעצמה את כל החוזה.
שחזור הפגם והסבר הדוח¶
במסלול הפגום אפשר ליצור יעד באורך n ולהפעיל לולאה שתנאיה i <= n, כאשר המקור מכיל לפחות n + 1 בתים. כך מבודדים כתיבה מחוץ ליעד בלי להוסיף גם קריאה מחוץ למקור. עבור אורך שש עשרה, הגישה הראשונה מחוץ ליעד היא בדיוק במרחק שש עשרה בתים מתחילתו. ההסבר אינו נשען על כתובת מספרית מסוימת, שמשתנה בין הרצות.
הדוח הנדרש צריך לזהות כתיבה, את אתר הכתיבה ואת אתר ההקצאה. אם הדוח מצביע על קריאה ממקור קצר, הניסוי אינו מבודד את הפגם שהתבקש. במצב כזה מתקנים את הכנת המקור ומריצים מחדש, במקום לבחור הודעת שגיאה שנראית מתאימה.
הקלט המספרי ראוי לניתוח באמצעות std::from_chars, תוך בדיקת קוד השגיאה, צריכת כל התווים והגבול העליון. אין להמיר ערך שלילי ל־size_t ולקוות שהמקצה ידחה אותו. בדיקת הגבול נעשית לפני החישוב n + 1, כך שבניסוי המוגבל אין גלישה.
אימות שהיעד לא השתנה¶
במקרה היעד הקצר שמרנו בו ערך ידוע ובדקנו שהוא נשאר תשע. בדיקה שמסתפקת בערך ההחזרה אינה בודקת את ערבות אי־השינוי. ביישום כללי אפשר לשמור עותק של היעד לפני הקריאה ולהשוות לאחריה. הבדיקה הזו בוחנת חוזה חיצוני, ולכן אינה רק העתקה של תנאי ה־if מתוך המימוש.
בדיקת חיים לאורך מחזורים¶
בכל מחזור יוצרים מקור ויעד בתחום מקומי, מפעילים את ההעתקה ויוצאים מהתחום. אחרי חימום קצר מודדים מספר הקצאות חיות באמצעות מקצה מדידה או כלי שמחזיר אתרי הקצאה. בסוף כל מחזור אמורות להישאר רק ההקצאות שהוגדרו כחלק מתשתית המדידה. הגידול בקיבולת זמנית במהלך מחזור אינו דליפה כל עוד הבעלות מסתיימת כנדרש.
אם מספר ההקצאות החיות מתייצב, אך ה־working set נשאר גבוה, הממצא אינו מוכיח דליפה. אם בכל מחזור נשאר בלוק חדש מאותו אתר והחוזה אינו דורש שיישאר, יש לנו מסלול חקירה ממוקד. נבדוק מי שומר את הבעלות, האם יש מעגל של shared_ptrים והאם התור שמחזיק את הבקשות באמת התרוקן.
הפתרון אינו מצרף דוח הרצה מומצא. לאחר הרצה אמיתית מוסיפים את הדוח ואת גרסת הכלי, ומבחינים בין הפגם שאותר לבין מגבלות הבדיקה.