Skip to content

Commit 00ad003

Browse files
congwang-mkclaude
andcommitted
docs(examples,README,SPEC): adopt if (var x = expr) for remaining presence checks
Migrates the `var x = expr; if (x != null) { ... uses x ... }` shape to the IfLet form wherever the bound name is used inside the truthy branch. Examples (9 sites): - `safety_demo.ks`: scalar map count update. - `ringbuf_demo.ks`: two `network_events.reserve()` / `security_events.reserve()` patterns. - `map_operations_demo.ks`: `shared_stats[batch_key]` in-place mutation (drops the redundant write-back) and `event_stream.reserve()`. - `type_checking.ks`: `connection_stats[header.src_ip]` scalar update. - `types_demo.ks`: four sites — `packet_filter[info]`, two scalar `connection_count` / `protocol_stats` updates, and the `extract_packet_info` pointer-returning helper. README: - The `lookup_or_create` snippet, which still showed the old shape, now uses IfLet directly. SPEC (8 sites): - §3.1.2 (TC programs): `connection_tracker` helper. - §4.6.7 (Map Integration with Pointers): `map_pointer_operations` — the canonical example for "map lookup returns pointer" now uses IfLet. - §5.2 (Global Maps): two `flow_stats` lookups now collapse from a presence check + redundant lookup to a single IfLet. - §9.2 (Top-Level Userspace Coordination): `process_events` ringbuf read. - §10.2 (Transparent Dynptr Integration): two `reserve()`-and-write examples — drops the explicit `: *u8` annotation since IfLet's grammar doesn't support a binding type annotation; the type is inferred from `reserve()`'s return type and noted in a comment. - §10.4 (Memory Lifetime): `cache_map[key]` lifetime-safety example. - §10.6 (Cross-Context Memory Safety): two `shared_map[0]` examples. Sites left intentionally untouched in both `examples/` and SPEC: - `m[k] != null` with the body using only `m[k] op= rhs` (rate_limiter, maps_demo:87, map_operations_demo:60) — IfLet adds an unused binding. - All `== null` shapes (object_allocation, ringbuf_demo program-load checks, test_error_handling) — IfLet branches on presence, not absence. - `tcp_info != null && tcp_info.flags & TCP_SYN` (SPEC §3.1.2 ddos example) — compound condition; IfLet doesn't desugar to `&&`. - `var buffer = kzalloc(...); if (buffer != null) { mark_secure } return buffer` (SPEC §3.6.5) — `return buffer` is outside the if; IfLet would put the binding out of scope at the return. - `return cache_entry != null && cache_entry.is_valid` (SPEC cache-checksum example) — return-as-expression, not an if. Pre-existing safety_demo stack-overflow (608 bytes > 512) is unrelated and predates this PR; verified by stashing the migrations and re-compiling — same 608-byte error. 84 alcotest suites still green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e0ddcd9 commit 00ad003

7 files changed

Lines changed: 42 additions & 62 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,11 @@ fn handle_action(action: FilterAction) -> xdp_action {
215215
}
216216
}
217217
218-
// Map lookup and update patterns
218+
// Map lookup and update patterns — declaration-as-condition binds
219+
// `count` only inside the truthy branch; one map lookup, no extra
220+
// presence-check variable.
219221
fn lookup_or_create(ip: IpAddress) -> Counter {
220-
var count = connection_count[ip]
221-
if (count != null) {
222+
if (var count = connection_count[ip]) {
222223
return count // Entry exists
223224
} else {
224225
connection_count[ip] = 1 // Create new entry

SPEC.md

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,8 +1476,7 @@ fn ddos_protection(ctx: *xdp_md) -> xdp_action {
14761476
14771477
@tc("ingress")
14781478
fn connection_tracker(ctx: *__sk_buff) -> i32 {
1479-
var tcp_info = extract_tcp_info(ctx) // Reuse same helper
1480-
if (tcp_info != null) {
1479+
if (var tcp_info = extract_tcp_info(ctx)) { // Reuse same helper
14811480
track_connection(tcp_info.src_port, tcp_info.dst_port)
14821481
}
14831482
return 0 // TC_ACT_OK
@@ -2357,9 +2356,9 @@ fn ebpf_pointer_usage(ctx: *xdp_md) -> xdp_action {
23572356
}
23582357
}
23592358
2360-
// Dynptr-backed pointers (transparent to user)
2361-
var log_buffer: *u8 = event_log.reserve(256) // Returns dynptr-backed pointer
2362-
if (log_buffer != null) {
2359+
// Dynptr-backed pointers (transparent to user) — `log_buffer` is the
2360+
// *u8 returned by reserve(), in scope only inside the truthy branch.
2361+
if (var log_buffer = event_log.reserve(256)) {
23632362
// Regular pointer operations - compiler uses dynptr API internally
23642363
log_buffer[0] = EVENT_TYPE_PACKET
23652364
write_packet_summary(log_buffer + 1, packet_data, 255)
@@ -2428,15 +2427,14 @@ var flow_map : hash<FlowKey, FlowData>(1024)
24282427
24292428
@helper
24302429
fn map_pointer_operations(flow_key: FlowKey) {
2431-
// Map lookup returns pointer to value
2432-
var flow_data = flow_map[flow_key]
2433-
2434-
if (flow_data != null) {
2430+
// Declaration-as-condition: a single map lookup; `flow_data` is the
2431+
// returned pointer, in scope only inside the truthy branch.
2432+
if (var flow_data = flow_map[flow_key]) {
24352433
// Direct modification through pointer
24362434
flow_data->packet_count += 1
24372435
flow_data->byte_count += packet_size
24382436
flow_data->last_seen = bpf_ktime_get_ns()
2439-
2437+
24402438
// Compiler tracks map value lifetime
24412439
// flow_data becomes invalid after certain map operations
24422440
}
@@ -2605,9 +2603,8 @@ fn egress_monitor(ctx: *__sk_buff) -> i32 {
26052603
fn security_analyzer(ctx: LsmContext) -> i32 {
26062604
var flow_key = extract_flow_key_from_socket(ctx)?
26072605
2608-
// Check global flow statistics
2609-
if (global_flows[flow_key] != null) {
2610-
var flow_stats = global_flows[flow_key]
2606+
// Check global flow statistics — single lookup via IfLet
2607+
if (var flow_stats = global_flows[flow_key]) {
26112608
if (flow_stats.is_suspicious()) {
26122609
security_events.submit(SecurityEvent {
26132610
event_type: EVENT_TYPE_SUSPICIOUS_CONNECTION,
@@ -2617,7 +2614,7 @@ fn security_analyzer(ctx: LsmContext) -> i32 {
26172614
return -EPERM // Block connection
26182615
}
26192616
}
2620-
2617+
26212618
return 0 // Allow connection
26222619
}
26232620
```
@@ -3736,9 +3733,8 @@ pin var global_config : array<ConfigKey, ConfigValue>(64)
37363733
fn security_filter(ctx: LsmContext) -> i32 {
37373734
var flow_key = extract_flow_key_from_socket(ctx)
37383735
3739-
// Check global flow statistics for threat detection
3740-
if (global_flows[flow_key] != null) {
3741-
var flow_stats = global_flows[flow_key]
3736+
// Check global flow statistics for threat detection — single lookup
3737+
if (var flow_stats = global_flows[flow_key]) {
37423738
if (flow_stats.is_suspicious()) {
37433739
global_events.submit(EVENT_THREAT_DETECTED { flow_key })
37443740
return -EPERM // Block connection
@@ -3779,8 +3775,7 @@ fn start_coordinator() -> i32 {
37793775
37803776
fn process_events(coordinator: *SystemCoordinator) {
37813777
// Process events from all programs
3782-
var event = coordinator->global_events.read()
3783-
if (event != null) {
3778+
if (var event = coordinator->global_events.read()) {
37843779
if (event.event_type == EVENT_PACKET_PROCESSED) {
37853780
print("Processed packet for flow: ", event.flow_key)
37863781
} else if (event.event_type == EVENT_THREAT_DETECTED) {
@@ -3931,17 +3926,17 @@ var event_log : hash<u32, Event>(1024)
39313926
39323927
@helper
39333928
fn transparent_dynptr_usage(event_data: *u8, data_len: u32) {
3934-
// User writes simple pointer code
3935-
var log_entry: *u8 = event_log.reserve(data_len + 16) // Dynptr-backed pointer
3936-
if (log_entry != null) {
3929+
// User writes simple pointer code — IfLet binds the *u8 returned by
3930+
// reserve() only inside the truthy branch.
3931+
if (var log_entry = event_log.reserve(data_len + 16)) {
39373932
// Regular pointer operations - compiler uses dynptr API internally
39383933
var header = log_entry as *EventHeader
39393934
header->timestamp = bpf_ktime_get_ns()
39403935
header->data_len = data_len
3941-
3936+
39423937
// Memory copy using pointer arithmetic
39433938
memory_copy(event_data, log_entry + 16, data_len)
3944-
3939+
39453940
event_log.submit(log_entry) // Compiler ensures proper cleanup
39463941
}
39473942
}
@@ -4028,15 +4023,14 @@ var cache_map : hash<u32, DataCache>(1024)
40284023
40294024
@helper
40304025
fn map_lifetime_safety(key: u32) {
4031-
var cache_entry = cache_map[key]
4032-
if (cache_entry != null) {
4026+
if (var cache_entry = cache_map[key]) {
40334027
// Compiler tracks that cache_entry is valid here
40344028
cache_entry->access_count += 1
40354029
cache_entry->last_access = bpf_ktime_get_ns()
4036-
4030+
40374031
// Compiler warns/errors if cache_entry used after invalidating operations
40384032
cache_map[other_key] = other_value // Invalidates cache_entry
4039-
4033+
40404034
// ❌ Compiler error: "Use of potentially invalidated map value pointer"
40414035
// cache_entry->access_count += 1
40424036
}
@@ -4084,27 +4078,25 @@ fn kernel_side_processing(ctx: *xdp_md) -> xdp_action {
40844078
var packet_data = ctx->data()
40854079
40864080
// Shared memory through maps - safe across contexts
4087-
var shared_buffer = shared_map[0]
4088-
if (shared_buffer != null) {
4081+
if (var shared_buffer = shared_map[0]) {
40894082
shared_buffer->kernel_processed_count += 1
40904083
memory_copy(packet_data, shared_buffer->data, min(packet_len, 64))
40914084
}
4092-
4085+
40934086
return XDP_PASS
40944087
}
40954088
40964089
// Userspace cannot directly access kernel pointers
40974090
fn userspace_processing() -> i32 {
40984091
// ❌ Cannot access kernel context pointers directly
40994092
// var packet_data = some_kernel_context.data() // Compilation error
4100-
4093+
41014094
// ✅ Access through shared maps
4102-
var shared_buffer = shared_map[0]
4103-
if (shared_buffer != null) {
4095+
if (var shared_buffer = shared_map[0]) {
41044096
shared_buffer->userspace_processed_count += 1
41054097
process_shared_data(shared_buffer->data)
41064098
}
4107-
4099+
41084100
return 0
41094101
}
41104102
```

examples/map_operations_demo.ks

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,8 @@ fn stats_updater(ctx: *__sk_buff) -> i32 {
113113
// Batch operation pattern - will be detected as batch access
114114
for (i in 0..20) {
115115
var batch_key = ifindex + i
116-
var entry = shared_stats[batch_key]
117-
if (entry != null) {
116+
if (var entry = shared_stats[batch_key]) {
118117
entry.packet_count = entry.packet_count + 1
119-
shared_stats[batch_key] = entry
120118
}
121119
}
122120

@@ -129,8 +127,7 @@ fn event_logger(ctx: *trace_event_raw_sys_enter) -> i32 {
129127
// Ring buffer output - single writer recommended
130128
try {
131129
// Reserve space in the ring buffer
132-
var reserved = event_stream.reserve()
133-
if (reserved != null) {
130+
if (var reserved = event_stream.reserve()) {
134131
// Successfully reserved space - populate event data inline
135132
reserved->timestamp = 123456 // Fake timestamp
136133
reserved->event_type = ctx->id // Use syscall ID from sys_enter context

examples/ringbuf_demo.ks

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@ fn get_timestamp() -> u64 {
5151
}
5252

5353
// Try to reserve space in ring buffer
54-
var reserved = network_events.reserve()
55-
if (reserved != null) {
54+
if (var reserved = network_events.reserve()) {
5655
// Successfully reserved space - populate event data inline
5756
reserved->timestamp = get_timestamp()
5857
reserved->event_type = 1 // PACKET_RECEIVED
@@ -77,8 +76,7 @@ fn get_timestamp() -> u64 {
7776
// Security monitoring program
7877
@probe("sys_openat")
7978
fn security_monitor(dfd: i32, filename: *u8, flags: i32, mode: u16) -> i32 {
80-
var reserved = security_events.reserve()
81-
if (reserved != null) {
79+
if (var reserved = security_events.reserve()) {
8280
// Successfully reserved space - populate security event inline
8381
reserved->timestamp = get_timestamp()
8482
reserved->severity = 2 // Medium severity

examples/safety_demo.ks

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ fn array_validation_demo(ctx: *xdp_md) -> xdp_action {
109109

110110
// Safe map access
111111
var key: u32 = 1
112-
var count = packet_stats[key]
113-
if (count != null) {
112+
if (var count = packet_stats[key]) {
114113
packet_stats[key] = count + 1
115114
} else {
116115
packet_stats[key] = 1

examples/type_checking.ks

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,7 @@ fn classify_protocol(proto: u8) -> ProtocolType {
6666
@helper
6767
fn update_statistics(header: PacketHeader) {
6868
// Type checker validates map operations and key/value types
69-
var current_count = connection_stats[header.src_ip]
70-
71-
if (current_count != null) {
69+
if (var current_count = connection_stats[header.src_ip]) {
7270
// Type checker ensures arithmetic on compatible types
7371
connection_stats[header.src_ip] = current_count + 1
7472
} else {

examples/types_demo.ks

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ fn extract_packet_info(ctx: *xdp_md) -> *PacketInfo {
6363
@helper
6464
fn get_filter_action(info: PacketInfo) -> FilterAction {
6565
// Look up in the filter map
66-
var action = packet_filter[info]
67-
if (action != null) {
66+
if (var action = packet_filter[info]) {
6867
return action
6968
} else {
7069
return FILTER_ACTION_ALLOW
@@ -85,17 +84,15 @@ fn protocol_from_u8(proto_num: u8) -> Protocol {
8584
@helper
8685
fn update_stats(info: PacketInfo) {
8786
// Update connection count
88-
var current_count = connection_count[info.src_ip]
89-
if (current_count != null) {
87+
if (var current_count = connection_count[info.src_ip]) {
9088
connection_count[info.src_ip] = current_count + 1
9189
} else {
9290
connection_count[info.src_ip] = 1
9391
}
94-
92+
9593
// Update protocol stats
9694
var proto = protocol_from_u8(info.protocol)
97-
var stats = protocol_stats[proto]
98-
if (stats != null) {
95+
if (var stats = protocol_stats[proto]) {
9996
protocol_stats[proto] = stats + 1
10097
} else {
10198
protocol_stats[proto] = 1
@@ -105,9 +102,7 @@ fn update_stats(info: PacketInfo) {
105102
// Program using all the new types
106103
@xdp fn packet_inspector(ctx: *xdp_md) -> xdp_action {
107104
// Extract packet information
108-
var packet_info = extract_packet_info(ctx)
109-
110-
if (packet_info != null) {
105+
if (var packet_info = extract_packet_info(ctx)) {
111106
// Update statistics
112107
update_stats(*packet_info)
113108

0 commit comments

Comments
 (0)