e7738de6d2
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
347 lines
18 KiB
C
347 lines
18 KiB
C
#include "program_graph_contracts.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
static bool memory_text_eq(const char *left, const char *right) { return strcmp(left ? left : "", right ? right : "") == 0; }
|
|
|
|
static bool memory_text_starts(const char *text, const char *prefix) { return text && prefix && strncmp(text, prefix, strlen(prefix)) == 0; }
|
|
|
|
static const ZProgramGraphNode *memory_node_by_id(const ZProgramGraph *graph, const char *id) {
|
|
for (size_t i = 0; graph && id && i < graph->node_len; i++) {
|
|
if (memory_text_eq(graph->nodes[i].id, id)) return &graph->nodes[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const ZProgramGraphNode *memory_child(const ZProgramGraph *graph, const ZProgramGraphNode *node, const char *kind, size_t order) {
|
|
for (size_t i = 0; graph && node && kind && i < graph->edge_len; i++) {
|
|
const ZProgramGraphEdge *edge = &graph->edges[i];
|
|
if (edge->target == Z_PROGRAM_GRAPH_EDGE_TARGET_NODE && edge->order == order && memory_text_eq(edge->from, node->id) && memory_text_eq(edge->kind, kind)) {
|
|
return memory_node_by_id(graph, edge->to);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const ZProgramGraphEdge *memory_owner_edge(const ZProgramGraph *graph, const char *node_id) {
|
|
for (size_t i = 0; graph && node_id && i < graph->edge_len; i++) {
|
|
const ZProgramGraphEdge *edge = &graph->edges[i];
|
|
if (edge->target == Z_PROGRAM_GRAPH_EDGE_TARGET_NODE && memory_text_eq(edge->to, node_id)) return edge;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const ZProgramGraphResolutionReference *memory_ref_for_node(const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *node, const char *kind) {
|
|
for (size_t i = 0; resolution && node && i < resolution->reference_len; i++) {
|
|
const ZProgramGraphResolutionReference *ref = &resolution->references[i];
|
|
if ((!kind || memory_text_eq(ref->kind, kind)) && memory_text_eq(ref->node_id, node->id)) return ref;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool memory_maybe_binding(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *subject) {
|
|
const ZProgramGraphResolutionReference *ref = memory_ref_for_node(resolution, subject, "identifier");
|
|
const ZProgramGraphNode *target = ref && ref->resolved ? memory_node_by_id(graph, ref->target_node) : NULL;
|
|
return target && memory_text_starts(target->type, "Maybe<");
|
|
}
|
|
|
|
static bool memory_subject_matches(const ZProgramGraphNode *subject, const char *name) { return subject && subject->kind == Z_PROGRAM_GRAPH_NODE_IDENTIFIER && memory_text_eq(subject->name, name); }
|
|
|
|
static bool memory_block_has_direct_return(const ZProgramGraph *graph, const ZProgramGraphNode *block) {
|
|
for (size_t i = 0; graph && block && i < graph->edge_len; i++) {
|
|
const ZProgramGraphEdge *edge = &graph->edges[i];
|
|
if (edge->target != Z_PROGRAM_GRAPH_EDGE_TARGET_NODE || !memory_text_eq(edge->from, block->id) || !memory_text_eq(edge->kind, "statement")) continue;
|
|
const ZProgramGraphNode *stmt = memory_node_by_id(graph, edge->to);
|
|
if (stmt && stmt->kind == Z_PROGRAM_GRAPH_NODE_RETURN) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool memory_expr_is_false(const ZProgramGraphNode *expr) { return expr && expr->kind == Z_PROGRAM_GRAPH_NODE_LITERAL && memory_text_eq(expr->value, "false"); }
|
|
|
|
static bool memory_expr_is_has_access_for(const ZProgramGraph *graph, const ZProgramGraphNode *expr, const char *subject_name) {
|
|
return expr && expr->kind == Z_PROGRAM_GRAPH_NODE_FIELD_ACCESS && memory_text_eq(expr->name, "has") &&
|
|
memory_subject_matches(memory_child(graph, expr, "left", 0), subject_name);
|
|
}
|
|
|
|
static bool memory_expr_has_guard_for(const ZProgramGraph *graph, const ZProgramGraphNode *expr, const char *subject_name, size_t depth) {
|
|
if (!graph || !expr || !subject_name || depth > graph->node_len) return false;
|
|
if (memory_expr_is_has_access_for(graph, expr, subject_name)) return true;
|
|
for (size_t i = 0; i < graph->edge_len; i++) {
|
|
const ZProgramGraphEdge *edge = &graph->edges[i];
|
|
if (edge->target == Z_PROGRAM_GRAPH_EDGE_TARGET_NODE && memory_text_eq(edge->from, expr->id)) {
|
|
if (memory_expr_has_guard_for(graph, memory_node_by_id(graph, edge->to), subject_name, depth + 1)) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool memory_block_has_then_guard(const ZProgramGraph *graph, const ZProgramGraphNode *block, const char *subject_name) {
|
|
const ZProgramGraphEdge *block_owner = block ? memory_owner_edge(graph, block->id) : NULL;
|
|
if (!block_owner || !memory_text_eq(block_owner->kind, "then")) return false;
|
|
const ZProgramGraphNode *if_node = memory_node_by_id(graph, block_owner->from);
|
|
if (!if_node || if_node->kind != Z_PROGRAM_GRAPH_NODE_IF) return false;
|
|
return memory_expr_has_guard_for(graph, memory_child(graph, if_node, "expr", 0), subject_name, 0);
|
|
}
|
|
|
|
static bool memory_expr_negative_has_guard_for(const ZProgramGraph *graph, const ZProgramGraphNode *expr, const char *subject_name) {
|
|
if (!expr || expr->kind != Z_PROGRAM_GRAPH_NODE_CALL) return false;
|
|
const ZProgramGraphNode *left = memory_child(graph, expr, "left", 0);
|
|
const ZProgramGraphNode *right = memory_child(graph, expr, "right", 1);
|
|
if (memory_text_eq(expr->name, "||")) {
|
|
return memory_expr_negative_has_guard_for(graph, left, subject_name) || memory_expr_negative_has_guard_for(graph, right, subject_name);
|
|
}
|
|
return memory_text_eq(expr->name, "==") &&
|
|
((memory_expr_is_has_access_for(graph, left, subject_name) && memory_expr_is_false(right)) ||
|
|
(memory_expr_is_false(left) && memory_expr_is_has_access_for(graph, right, subject_name)));
|
|
}
|
|
|
|
static bool memory_if_proves_subject_after(const ZProgramGraph *graph, const ZProgramGraphNode *stmt, const char *subject_name) {
|
|
if (!stmt || stmt->kind != Z_PROGRAM_GRAPH_NODE_IF) return false;
|
|
if (!memory_expr_negative_has_guard_for(graph, memory_child(graph, stmt, "expr", 0), subject_name)) return false;
|
|
return memory_block_has_direct_return(graph, memory_child(graph, stmt, "then", 0));
|
|
}
|
|
|
|
static bool memory_prior_negative_return_guarded_in_block(const ZProgramGraph *graph, const ZProgramGraphNode *block, size_t order, const char *subject_name) {
|
|
for (size_t i = 0; graph && block && i < graph->edge_len; i++) {
|
|
const ZProgramGraphEdge *edge = &graph->edges[i];
|
|
if (edge->target != Z_PROGRAM_GRAPH_EDGE_TARGET_NODE || edge->order >= order || !memory_text_eq(edge->from, block->id) || !memory_text_eq(edge->kind, "statement")) continue;
|
|
if (memory_if_proves_subject_after(graph, memory_node_by_id(graph, edge->to), subject_name)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool memory_prior_negative_return_guarded(const ZProgramGraph *graph, const ZProgramGraphNode *read, const char *subject_name) {
|
|
const char *current = read ? read->id : NULL;
|
|
for (size_t depth = 0; graph && current && depth < graph->node_len; depth++) {
|
|
const ZProgramGraphEdge *owner = memory_owner_edge(graph, current);
|
|
const ZProgramGraphNode *owner_node = owner ? memory_node_by_id(graph, owner->from) : NULL;
|
|
if (!owner_node) return false;
|
|
if (owner_node->kind == Z_PROGRAM_GRAPH_NODE_BLOCK && memory_text_eq(owner->kind, "statement") &&
|
|
memory_prior_negative_return_guarded_in_block(graph, owner_node, owner->order, subject_name)) return true;
|
|
current = owner_node->id;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool memory_short_circuit_guarded(const ZProgramGraph *graph, const ZProgramGraphNode *read, const char *subject_name) {
|
|
const char *current = read ? read->id : NULL;
|
|
for (size_t depth = 0; graph && current && depth < graph->node_len; depth++) {
|
|
const ZProgramGraphEdge *owner = memory_owner_edge(graph, current);
|
|
const ZProgramGraphNode *parent = owner ? memory_node_by_id(graph, owner->from) : NULL;
|
|
if (!parent) return false;
|
|
if (parent->kind == Z_PROGRAM_GRAPH_NODE_CALL && memory_text_eq(owner->kind, "right")) {
|
|
const ZProgramGraphNode *left = memory_child(graph, parent, "left", 0);
|
|
if (memory_text_eq(parent->name, "&&") && memory_expr_has_guard_for(graph, left, subject_name, 0)) return true;
|
|
if (memory_text_eq(parent->name, "||") && memory_expr_negative_has_guard_for(graph, left, subject_name)) return true;
|
|
}
|
|
current = parent->id;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool memory_value_read_guarded(const ZProgramGraph *graph, const ZProgramGraphNode *read, const char *subject_name) {
|
|
if (memory_short_circuit_guarded(graph, read, subject_name)) return true;
|
|
if (memory_prior_negative_return_guarded(graph, read, subject_name)) return true;
|
|
const char *current = read ? read->id : NULL;
|
|
for (size_t depth = 0; graph && current && depth < graph->node_len; depth++) {
|
|
const ZProgramGraphEdge *owner = memory_owner_edge(graph, current);
|
|
const ZProgramGraphNode *owner_node = owner ? memory_node_by_id(graph, owner->from) : NULL;
|
|
if (!owner_node) return false;
|
|
if (owner_node->kind == Z_PROGRAM_GRAPH_NODE_BLOCK && memory_block_has_then_guard(graph, owner_node, subject_name)) return true;
|
|
current = owner_node->id;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool fail_maybe_value_read(const ZProgramGraphNode *read, const ZProgramGraphNode *subject, const char *path, ZDiag *diag) {
|
|
if (diag) {
|
|
*diag = (ZDiag){0};
|
|
diag->code = 3051;
|
|
diag->path = read && read->path && read->path[0] ? read->path : path;
|
|
diag->line = read && read->line > 0 ? read->line : 1;
|
|
diag->column = read && read->column > 0 ? read->column : 1;
|
|
diag->length = 1;
|
|
snprintf(diag->message, sizeof(diag->message), "Maybe payload read requires a presence guard");
|
|
snprintf(diag->expected, sizeof(diag->expected), ".has proven true, check maybe_value, or rescue maybe_value err fallback");
|
|
snprintf(diag->actual, sizeof(diag->actual), "%s", subject && subject->name ? subject->name : "temporary Maybe value");
|
|
snprintf(diag->help, sizeof(diag->help), "guard the Maybe with `.has` before reading `.value`, or use `check`/`rescue` to handle absence");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool memory_node_contract_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *node, const char *path, ZDiag *diag) {
|
|
if (node->kind != Z_PROGRAM_GRAPH_NODE_FIELD_ACCESS || !memory_text_eq(node->name, "value")) return true;
|
|
const ZProgramGraphNode *subject = memory_child(graph, node, "left", 0);
|
|
if (!memory_maybe_binding(graph, resolution, subject)) return true;
|
|
if (!memory_value_read_guarded(graph, node, subject ? subject->name : "")) return fail_maybe_value_read(node, subject, path, diag);
|
|
return true;
|
|
}
|
|
|
|
bool z_program_graph_memory_contracts_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *diag) {
|
|
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
|
if (!memory_node_contract_ok(graph, resolution, &graph->nodes[i], path, diag)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
size_t z_program_graph_memory_contract_violations(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *out, size_t cap) {
|
|
size_t found = 0;
|
|
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
|
ZDiag diag = {0};
|
|
if (memory_node_contract_ok(graph, resolution, &graph->nodes[i], path, &diag)) continue;
|
|
if (out && found < cap) out[found] = diag;
|
|
found++;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
static bool memory_integer_literal_value(const char *text, unsigned long long *out) {
|
|
if (!text || !text[0]) return false;
|
|
size_t body_len = strlen(text);
|
|
const char *last_underscore = strrchr(text, '_');
|
|
if (last_underscore && (last_underscore[1] == 'i' || last_underscore[1] == 'u')) body_len = (size_t)(last_underscore - text);
|
|
if (body_len == 0) return false;
|
|
unsigned long long value = 0;
|
|
bool saw_digit = false;
|
|
for (size_t index = 0; index < body_len; index++) {
|
|
char ch = text[index];
|
|
if (ch == '_') continue;
|
|
if (ch < '0' || ch > '9') return false;
|
|
value = value * 10ull + (unsigned long long)(ch - '0');
|
|
saw_digit = true;
|
|
}
|
|
if (!saw_digit) return false;
|
|
if (out) *out = value;
|
|
return true;
|
|
}
|
|
|
|
static bool memory_fixed_array_declared_len(const char *type_text, unsigned long long *out) {
|
|
if (!type_text || type_text[0] != '[') return false;
|
|
const char *close = strchr(type_text, ']');
|
|
if (!close || close == type_text + 1 || !close[1]) return false;
|
|
unsigned long long value = 0;
|
|
bool saw_digit = false;
|
|
for (const char *cursor = type_text + 1; cursor < close; cursor++) {
|
|
char ch = *cursor;
|
|
if (ch == '_') continue;
|
|
if (ch < '0' || ch > '9') return false;
|
|
value = value * 10ull + (unsigned long long)(ch - '0');
|
|
saw_digit = true;
|
|
}
|
|
if (!saw_digit) return false;
|
|
if (out) *out = value;
|
|
return true;
|
|
}
|
|
|
|
static size_t memory_edge_count(const ZProgramGraph *graph, const ZProgramGraphNode *node, const char *kind) {
|
|
size_t count = 0;
|
|
for (size_t i = 0; graph && node && kind && i < graph->edge_len; i++) {
|
|
const ZProgramGraphEdge *edge = &graph->edges[i];
|
|
if (edge->target == Z_PROGRAM_GRAPH_EDGE_TARGET_NODE && memory_text_eq(edge->from, node->id) && memory_text_eq(edge->kind, kind)) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static const ZProgramGraphNode *memory_top_level_const(const ZProgramGraph *graph, const char *name) {
|
|
for (size_t i = 0; graph && name && name[0] && i < graph->node_len; i++) {
|
|
const ZProgramGraphNode *node = &graph->nodes[i];
|
|
if (node->kind == Z_PROGRAM_GRAPH_NODE_CONST && memory_text_eq(node->name, name)) return node;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool memory_repeat_count_value(const ZProgramGraph *graph, const ZProgramGraphNode *count_node, unsigned long long *out) {
|
|
if (!count_node) return false;
|
|
if (count_node->kind == Z_PROGRAM_GRAPH_NODE_LITERAL) return memory_integer_literal_value(count_node->value, out);
|
|
if (count_node->kind == Z_PROGRAM_GRAPH_NODE_IDENTIFIER) {
|
|
const ZProgramGraphNode *const_decl = memory_top_level_const(graph, count_node->name);
|
|
const ZProgramGraphNode *const_value = const_decl ? memory_child(graph, const_decl, "value", 0) : NULL;
|
|
if (const_value && const_value->kind == Z_PROGRAM_GRAPH_NODE_LITERAL) return memory_integer_literal_value(const_value->value, out);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool fail_fixed_array_length(const ZProgramGraphNode *literal, const char *declared_type, const char *actual_detail, const char *path, ZDiag *diag) {
|
|
if (diag) {
|
|
*diag = (ZDiag){0};
|
|
diag->code = 3006;
|
|
diag->path = literal && literal->path && literal->path[0] ? literal->path : path;
|
|
diag->line = literal && literal->line > 0 ? literal->line : 1;
|
|
diag->column = literal && literal->column > 0 ? literal->column : 1;
|
|
diag->length = 1;
|
|
snprintf(diag->message, sizeof(diag->message), "array literal length does not match expected fixed array");
|
|
snprintf(diag->expected, sizeof(diag->expected), "%s", declared_type ? declared_type : "fixed array type");
|
|
snprintf(diag->actual, sizeof(diag->actual), "%s", actual_detail ? actual_detail : "mismatched array literal length");
|
|
snprintf(diag->help, sizeof(diag->help), "make the lengths agree: resize the initializer or annotate the intended array length");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool memory_array_initializer_length_ok(const ZProgramGraph *graph, const char *declared_type, const ZProgramGraphNode *literal, const char *path, ZDiag *diag) {
|
|
unsigned long long declared_len = 0;
|
|
if (!literal || literal->kind != Z_PROGRAM_GRAPH_NODE_ARRAY_LITERAL) return true;
|
|
if (!memory_fixed_array_declared_len(declared_type, &declared_len)) return true;
|
|
if (memory_text_eq(literal->value, "repeat")) {
|
|
unsigned long long repeat_count = 0;
|
|
if (!memory_repeat_count_value(graph, memory_child(graph, literal, "arg", 1), &repeat_count)) return true;
|
|
if (repeat_count == declared_len) return true;
|
|
char actual_detail[96];
|
|
snprintf(actual_detail, sizeof(actual_detail), "repeat count %llu", repeat_count);
|
|
return fail_fixed_array_length(literal, declared_type, actual_detail, path, diag);
|
|
}
|
|
size_t element_count = memory_edge_count(graph, literal, "arg");
|
|
if ((unsigned long long)element_count == declared_len) return true;
|
|
char actual_detail[96];
|
|
snprintf(actual_detail, sizeof(actual_detail), "%zu element(s)", element_count);
|
|
return fail_fixed_array_length(literal, declared_type, actual_detail, path, diag);
|
|
}
|
|
|
|
static const char *memory_assignment_target_array_type(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *assignment) {
|
|
const ZProgramGraphNode *target = memory_child(graph, assignment, "target", 0);
|
|
if (!target || target->kind != Z_PROGRAM_GRAPH_NODE_IDENTIFIER) return NULL;
|
|
const ZProgramGraphResolutionReference *ref = memory_ref_for_node(resolution, target, "identifier");
|
|
const ZProgramGraphNode *binding = ref && ref->resolved ? memory_node_by_id(graph, ref->target_node) : NULL;
|
|
if (!binding || binding->kind != Z_PROGRAM_GRAPH_NODE_LET) return NULL;
|
|
return binding->type;
|
|
}
|
|
|
|
static bool memory_fixed_array_node_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *node, const char *path, ZDiag *diag) {
|
|
const char *declared_type = NULL;
|
|
const char *init_edge = NULL;
|
|
if (node->kind == Z_PROGRAM_GRAPH_NODE_LET) {
|
|
declared_type = node->type;
|
|
init_edge = "expr";
|
|
} else if (node->kind == Z_PROGRAM_GRAPH_NODE_CONST) {
|
|
declared_type = node->type;
|
|
init_edge = "value";
|
|
} else if (node->kind == Z_PROGRAM_GRAPH_NODE_ASSIGNMENT) {
|
|
declared_type = memory_assignment_target_array_type(graph, resolution, node);
|
|
init_edge = "expr";
|
|
} else {
|
|
return true;
|
|
}
|
|
if (!declared_type || declared_type[0] != '[') return true;
|
|
const ZProgramGraphNode *init = memory_child(graph, node, init_edge, 0);
|
|
return memory_array_initializer_length_ok(graph, declared_type, init, path, diag);
|
|
}
|
|
|
|
bool z_program_graph_fixed_array_length_contracts_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *diag) {
|
|
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
|
if (!memory_fixed_array_node_ok(graph, resolution, &graph->nodes[i], path, diag)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
size_t z_program_graph_fixed_array_length_contract_violations(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *out, size_t cap) {
|
|
size_t found = 0;
|
|
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
|
ZDiag diag = {0};
|
|
if (memory_fixed_array_node_ok(graph, resolution, &graph->nodes[i], path, &diag)) continue;
|
|
if (out && found < cap) out[found] = diag;
|
|
found++;
|
|
}
|
|
return found;
|
|
}
|