
مدقق للأعمار وأنواع التنقيح الأخرى
C مُتحقِّق من
L الأعمار وأنواع أخرى من
R التحسين (Refinement)
لـ Zig
فيديو: https://www.youtube.com/watch?v=mf0WzTOe-40 دعم مالي: https://buymeacoffee.com/dnautics
ناقش على HN: https://news.ycombinator.com/item?id=42923829
ناقش على lobste.rs: https://lobste.rs/s/9sitsj/clr_checker_for_lifetimes_other
فيديو عرض مباشر: https://www.youtube.com/watch?v=ZY_Z-aGbYm8
ينشئ هذا المشروع مُحوِّلًا (transpiler) بلغة Zig لمُصرِّف Zig، يحوّل AIR (التمثيل الوسيط المجرَّد — Abstract Intermediate Representation) إلى كود مصدري بلغة Zig يُجري تحليلًا ثابتًا (static analysis) في وقت الترجمة. يكتشف المُحلِّل المُولَّد مشكلات سلامة الذاكرة مثل الاستخدام قبل الإسناد (use-before-assignment)، والاستخدام بعد التحرير (use-after-free)، وهروب مؤشرات المكدس (stack pointer escapes)، بالإضافة إلى سلوكيات غير محددة (UB) خاصة بلغة Zig مثل تأكيدات عدم الصفرية (non-nullness assertions)، وانتهاكات الاتحادات الموسومة (tagged unions)، أو إساءة استخدام fieldParentPtr.
الهدف هو جلب ضمانات سلامة الذاكرة بمستوى Rust إلى Zig عبر التحليل الثابت لـ AIR، دون تغيير اللغة نفسها.
يعتمد CLR على نسخة مشتقّة من مُصرِّف Zig (مضمَّنة كوحدة فرعية في zig/) تضيف دعم توجيه AIR إلى إضافات خارجية. عند الاستدعاء مع -ofmt=air -fair-out=<plugin.so>، يقوم المُصرِّف بتحميل المكتبة المشتركة المحددة ويمرر إليها AIR المُولَّد لمعالجته.
تهدف CLR إلى دفع البرامج نحو أنماط دورة حياة صريحة وقابلة للتحقق محليًا، وليس فقط إلى التعرف على كل برنامج Zig صالح تقنيًا. عندما يكون تمثيلان ممكنين، تفضّل CLR التمثيل الذي يجعل حالة الموارد ظاهرة في النوع وبنية تدفق التحكم.
على سبيل المثال، تجنَّب الإغلاق الشرطي لوصف ملف (file descriptor) غير اختياري:
const file = try std.fs.cwd().openFile(path, .{});
if (should_close) {
file.close(); // Bad: file is ambiguously open after this branch.
}
فضِّل تمثيل الملكية الشرطية بنوع اختياري (optional):
var file: ?std.fs.File = null;
if (should_open) {
file = try std.fs.cwd().openFile(path, .{});
}
if (file) |open_file| {
open_file.close();
}
الإغلاق الشرطي لوصف غير اختياري يترك دورة حياته غامضة بعد الفرع. السياسة المقصودة لـ CLR هي رفض هذا النمط بدلًا من حمل حالة «ربما مُغلق» دائمة.
ينطبق المبدأ نفسه على المؤشرات المُخصَّصة. لا تحرِّر عبر مؤشر مشتق:
const allocation = try allocator.alloc(u8, size);
const payload = allocation[header_size..];
allocator.free(payload); // Bad: payload is not the allocation base.
أبقِ مؤشر قاعدة التخصيص متاحًا للتحرير، واستخدم المؤشرات المشتقة للوصول فقط:
const allocation = try allocator.alloc(u8, size);
defer allocator.free(allocation);
const payload = allocation[header_size..];
use(payload);
يُرفض تحرير مؤشر حقل (field pointer)، أو شريحة فرعية (subslice)، أو مؤشر ناتج عن عمليات حسابية ما لم تعِد قاعدة داخلية موثّقة إنشاء سلسلة ملكية (provenance) لقاعدة التخصيص.
هذه السياسات صارمة افتراضيًا لأنها تُنتج كودًا بدورات حياة موارد أبسط وأسهل مراجعة. ستسمح آلية تعليقات unsafe مستقبلية لمجموعات GIDs أو عمليات محددة بالانسحاب من تحليلات فردية. سيدعم ذلك الكود الذي يقبل عن قصد فحصًا أضعف مقابل الأداء، دون إضعاف النموذج الافتراضي لبقية البرنامج.
هذه إعادة كتابة نشطة لإثبات المفهوم الأصلي المبني على Elixir بلغة Zig. يُحمَّل تنفيذ Zig كإضافة (plugin) للمُصرِّف ويحلّل AIR مباشرة.
المُنجَز حاليًا:
std.mem.Allocator:
create/destroy - تخصيص عنصر واحدalloc/free - تخصيص الشرائح (بما في ذلك alignedAlloc و allocSentinel وغيرها)realloc/remap - إعادة تخصيص الشرائح مع تتبّع تحرير الشريحة القديمةdupe/dupeZ - تكرار الشرائحinit// - دورة حياة arena كاملةمُخطَّط له (انظر LIMITATIONS.md للتفاصيل):
sudo apt install batsيجب بناء مُصرِّف Zig المضمَّن (vendored) وإضافة libclr بمستويات تحسين متطابقة. ستؤدي مستويات التحسين غير المتطابقة إلى أعطال تجزئة (segfaults).
# Build the custom Zig compiler with ReleaseFast (first time only, or after submodule changes)
cd zig && zig build --zig-lib-dir lib -Doptimize=ReleaseFast && cd ..
# Build the CLR plugin with matching optimization
zig build -Doptimize=ReleaseFast
للتطوير/التصحيح، استخدم ReleaseSafe أو Debug لكليهما:
# ReleaseSafe (with safety checks, slightly slower)
cd zig && zig build --zig-lib-dir lib -Doptimize=ReleaseSafe && cd ..
zig build -Doptimize=ReleaseSafe
# Debug (full debug info, slowest)
cd zig && zig build --zig-lib-dir lib && cd ..
zig build
# Compile a Zig file using the AIR backend
zig/zig-out/bin/zig build-exe -fair-out=zig-out/lib/libclr.so -ofmt=air -femit-bin=output.air.zig your_file.zig
# Run the generated analyzer
zig run --dep clr -Mroot=output.air.zig -Mclr=lib/lib.zig
يُرسَل المخرَج إلى stderr.
# Unit tests (codegen/DLL)
zig build test
# Unit tests (runtime library)
zig test lib/lib.zig
# A focused integration test file
bats test/integration/fd.bats
# Integration tests (requires BATS)
# Defaults to ReleaseFast; override with OPTIMIZE=ReleaseSafe or OPTIMIZE=Debug
./run_integration.sh
# Manual test of a single file
./run_one.sh test/cases/undefined/use_before_assign.zig
ملاحظة: تعيد اختبارات التكامل بناء libclr بمستوى التحسين المحدد (الافتراضي: ReleaseFast). تأكد من أن مُصرِّف Zig المضمَّن لديك بُني بمستوى تحسين مطابق.
clr/
├── src/ # DLL/plugin code (generates .air.zig)
│ ├── clr.zig # Main CLR plugin entry point
│ ├── codegen.zig # Generates .air.zig source from AIR instructions
│ └── allocator.zig # DLL-safe allocator wrapper
├── lib/ # Runtime analysis library
│ ├── lib.zig # Library entry point
│ ├── tag.zig # AnyTag union, Type, tag handlers, splat dispatch
│ ├── Inst.zig # Instruction results and interprocedural analysis
│ ├── Refinements.zig # Refinement types (pointer, struct, optional, etc.)
│ ├── Analyte.zig # Analysis state container
│ ├── Context.zig # Execution context (metadata, error reporting)
│ └── analysis/ # Analysis modules
│ ├── undefined_safety.zig # Use-before-assign tracking
│ ├── memory_safety.zig # Allocation/free tracking
│ ├── null_safety.zig # Optional unwrap checking
│ ├── variant_safety.zig # Tagged union field access
│ └── fd_safety.zig # File descriptor tracking
├── test/
│ ├── integration/ # BATS integration tests
│ │ ├── test_helper.bash
│ │ └── *.bats
│ └── cases/ # Test input files (.zig)
├── zig/ # Zig compiler submodule (instrumented fork)
├── build.zig # Build configuration
└── build.zig.zon # Package dependencies

Zig لغة «غير آمنة» بشكل معروف. تتم إدارة الذاكرة يدويًا، مما يفتح الباب لاحتمال أخطاء التنفيذ. بينما تقلّل Zig المشكلات الأمنية مقارنة بـ C من خلال إزالة الوصول إلى المصفوفات خارج الحدود وإلغاء الإشارة إلى المؤشرات الصفرية في الكود المُتحقَّق من سلامته، إلا أنها ما تزال أقل أمانًا من Rust، التي تزيل الاستخدام بعد التحرير والتحرير المزدوج وسباقات البيانات عبر التحليل الثابت.
مستلهمًا من مشروع MIRI في Rust، يُجري CLR تحليلًا ثابتًا على التمثيل الوسيط AIR الخاص بـ Zig لتحقيق درجة أمان أعلى مما توفره Zig خارج الصندوق. وبخلاف MIRI، الذي يفسّر MIR الخاص بـ Rust في بيئة تشغيل زائفة معزولة (sandbox)، يحوّل CLR كود AIR إلى كود مصدري بلغة Zig يُشغّل التحليل بشكل ثابت. لاحظ أن كود Zig الناتج عن AIR في CLR يمكن من حيث المبدأ تشغيله في وقت الترجمة، لكن بتمريره عبر وسيط Zig، نُنتج تدفقًا منطقيًا سهل الفهم وسهل التصحيح. قد يستخدم شخص طموح هذا النهج العام لإنتاج هدف مخرَج مختلف، مثل لغة مساعد إثبات (proof assistant)، أو يعيد هيكلته ليعمل بالكامل داخل مُصرِّف Zig!
الفكرة الجوهرية: إذا كنت تحتاج MIRI لمشاريع Rust الحساسة أمنيًا على أي حال، فلماذا لا تختار لغة أبسط وتُجري تحليلًا بأسلوب MIRI للحصول على فحص الاقتراض (borrow checking) وأنواع أخرى من تحليلات التحسين (refinement)؟ يوضح هذا المشروع أن مثل هذا المستقبل ممكن فعليًا لـ Zig.
خط أنابيب الترجمة في Zig هو:
AIR هو المستوى المثالي للتحليل لأنه مُنمَّط، ويمكن تفسيره كقائمة «الحد الأدنى القابل للتطبيق» من تعليمات برمجية معمَّمة، ويسمح بتوسيع الأنواع ببيانات وصفية للتحسين (refinement metadata).
للاطلاع المتعمق على كيفية عمل AIR في Zig، راجع منشور مدونة Mitchell Hashimoto: https://mitchellh.com/zig/sema
رخصة MIT - انظر LICENSE للتفاصيل.
deinitallocatorstd.process.args و std.mem.asBytes و std.HashMap وواجهات المُخصِّصات والملفاتstd.HashMap بهوية تخزين معيارية (canonical) للبيانات الوصفية/المفاتيح/القيم عبر put و get و getPtr وتكرار القيمposix.open/close/dup/dup2/socket/accept/epoll_create/pipe