commit 11711a528c4ec4f17a4e6ea64f3215bb0e3c87f5
parent 61ea954772ceb838326bb5275635961c0ead460e
Author: Bor Grošelj Simić <bgs@turminal.net>
Date: Thu, 25 Aug 2022 01:26:51 +0200
type_store: make type validity checks consistent
Before this change we didn't properly check for presence of null type in
some aggregate types and types of undefined or zero size weren't
reported in places where they should be.
Fixes: https://todo.sr.ht/~sircmpwn/hare/668
Fixes: https://todo.sr.ht/~sircmpwn/hare/736
References: https://todo.sr.ht/~sircmpwn/hare/580
Signed-off-by: Bor Grošelj Simić <bgs@turminal.net>
Diffstat:
7 files changed, 180 insertions(+), 56 deletions(-)
diff --git a/include/type_store.h b/include/type_store.h
@@ -37,13 +37,14 @@ const struct type *type_store_lookup_with_flags(struct type_store *store,
const struct type *type, unsigned int flags);
const struct type *type_store_lookup_pointer(struct type_store *store,
- const struct type *referent, unsigned int ptrflags);
+ struct location loc, const struct type *referent, unsigned int ptrflags);
const struct type *type_store_lookup_array(struct type_store *store,
- const struct type *members, size_t len, bool expandable);
+ struct location loc, const struct type *members, size_t len,
+ bool expandable);
const struct type *type_store_lookup_slice(struct type_store *store,
- const struct type *members);
+ struct location loc, const struct type *members);
const struct type *type_store_lookup_alias(struct type_store *store,
const struct type *secondary);
@@ -56,7 +57,7 @@ const struct type *type_store_tagged_to_union(
struct type_store *store, const struct type *tagged);
const struct type *type_store_lookup_tuple(struct type_store *store,
- struct type_tuple *values, struct location loc);
+ struct location loc, struct type_tuple *values);
const struct type *type_store_lookup_enum(struct type_store *store,
const struct ast_type *atype, bool exported);
diff --git a/rt/rtmain.ha b/rt/rtmain.ha
@@ -5,7 +5,7 @@ const @symbol("__init_array_end") init_end: [*]*fn() void;
const @symbol("__fini_array_start") fini_start: [*]*fn() void;
const @symbol("__fini_array_end") fini_end: [*]*fn() void;
-export @noreturn fn start_ha(iv: [*]uintptr) void = {
+export @noreturn fn start_ha(iv: *[*]uintptr) void = {
const ninit = (&init_end: uintptr - &init_start: uintptr): size
/ size(*fn() void);
for (let i = 0z; i < ninit; i += 1) {
diff --git a/src/check.c b/src/check.c
@@ -333,7 +333,8 @@ check_expr_alloc_init(struct context *ctx,
assert(htype->array.members == atype->array.members);
objtype = hint;
}
- expr->result = type_store_lookup_pointer(ctx->store, objtype, ptrflags);
+ expr->result = type_store_lookup_pointer(ctx->store, aexpr->loc,
+ objtype, ptrflags);
if (expr->result->size == 0 || expr->result->size == SIZE_UNDEFINED) {
error(ctx, aexpr->loc, expr,
"Cannot allocate object of zero or undefined size");
@@ -386,7 +387,8 @@ check_expr_alloc_slice(struct context *ctx,
}
const struct type *membtype = type_dealias(objtype)->array.members;
- expr->result = type_store_lookup_slice(ctx->store, membtype);
+ expr->result = type_store_lookup_slice(ctx->store,
+ aexpr->alloc.init->loc, membtype);
if (objtype->storage == STORAGE_ARRAY
&& objtype->array.expandable) {
@@ -424,7 +426,8 @@ check_expr_alloc_copy(struct context *ctx,
check_expression(ctx, aexpr->alloc.init, expr->alloc.init, hint);
result = type_dealias(expr->alloc.init->result);
- expr->result = type_store_lookup_slice(ctx->store, result->array.members);
+ expr->result = type_store_lookup_slice(ctx->store,
+ aexpr->alloc.init->loc, result->array.members);
}
static void
@@ -1258,8 +1261,8 @@ lower_vaargs(struct context *ctx,
}
// XXX: This error handling is minimum-effort and bad
- const struct type *hint = type_store_lookup_array(
- ctx->store, type, SIZE_UNDEFINED, false);
+ const struct type *hint = type_store_lookup_array(ctx->store,
+ val.loc, type, SIZE_UNDEFINED, false);
check_expression(ctx, &val, vaargs, hint);
if (vaargs->result->storage != STORAGE_ARRAY
|| vaargs->result->array.members != type) {
@@ -1561,7 +1564,8 @@ check_expr_array(struct context *ctx,
error(ctx, aexpr->loc, expr, "Cannot infer array type from context, try casting it to the desired type");
return;
}
- expr->result = type_store_lookup_array(ctx->store, type, len, expand);
+ expr->result = type_store_lookup_array(ctx->store, aexpr->loc,
+ type, len, expand);
}
static void
@@ -2457,7 +2461,7 @@ check_expr_slice(struct context *ctx,
if (dtype->storage == STORAGE_SLICE) {
expr->result = atype;
} else {
- expr->result = type_store_lookup_slice(ctx->store,
+ expr->result = type_store_lookup_slice(ctx->store, aexpr->loc,
dtype->array.members);
}
}
@@ -2842,8 +2846,12 @@ check_expr_tuple(struct context *ctx,
return;
}
} else {
- expr->result = type_store_lookup_tuple(ctx->store, &result,
- aexpr->loc);
+ expr->result = type_store_lookup_tuple(ctx->store,
+ aexpr->loc, &result);
+ if (expr->result == &builtin_type_void) {
+ // an error occured
+ return;
+ }
}
ttuple = &type_dealias(expr->result)->tuple;
@@ -2924,7 +2932,7 @@ check_expr_unarithm(struct context *ctx,
break;
case UN_ADDRESS:
expr->result = type_store_lookup_pointer(
- ctx->store, operand->result, 0);
+ ctx->store, aexpr->loc, operand->result, 0);
break;
case UN_DEREF:
if (type_dealias(operand->result)->storage != STORAGE_POINTER) {
@@ -3183,7 +3191,8 @@ check_function(struct context *ctx,
ctx->store, params->type);
if (fntype->func.variadism == VARIADISM_HARE
&& !params->next) {
- type = type_store_lookup_slice(ctx->store, type);
+ type = type_store_lookup_slice(ctx->store,
+ params->loc, type);
}
scope_insert(decl->func.scope, O_BIND,
&ident, &ident, type, NULL);
diff --git a/src/type_store.c b/src/type_store.c
@@ -147,7 +147,8 @@ builtin_for_type(const struct type *type)
static struct struct_field *
struct_insert_field(struct type_store *store, struct struct_field **fields,
enum type_storage storage, size_t *size, size_t *usize, size_t *align,
- const struct ast_struct_union_type *atype, bool *ccompat, bool size_only)
+ const struct ast_struct_union_type *atype, bool *ccompat, bool size_only,
+ bool last)
{
while (*fields && (!atype->name || !(*fields)->name || strcmp((*fields)->name, atype->name) < 0)) {
fields = &(*fields)->next;
@@ -174,13 +175,16 @@ struct_insert_field(struct type_store *store, struct struct_field **fields,
}
if (dim.size == 0) {
error(store->check_context, atype->type->loc,
- "Struct field size cannot be zero");
+ "Type of size 0 is not a valid struct/union member");
return NULL;
- } else if (dim.align == 0) {
+ }
+ if (!last && dim.size == SIZE_UNDEFINED) {
error(store->check_context, atype->type->loc,
- "Struct field alignment cannot be zero");
+ "Type of undefined size is not a valid struct/union member");
return NULL;
}
+ assert(dim.align != ALIGN_UNDEFINED);
+ assert(dim.align != 0);
if (atype->offset) {
*ccompat = false;
@@ -284,7 +288,8 @@ struct_init_from_atype(struct type_store *store, enum type_storage storage,
assert(storage == STORAGE_STRUCT || storage == STORAGE_UNION);
while (atype) {
struct struct_field *field = struct_insert_field(store, fields,
- storage, size, &usize, align, atype, ccompat, size_only);
+ storage, size, &usize, align, atype, ccompat, size_only,
+ atype->next == NULL);
if (field == NULL) {
return;
}
@@ -577,31 +582,35 @@ tuple_init_from_atype(struct type_store *store,
struct dimensions dim = {0};
while (atuple) {
struct dimensions memb = {0};
+ size_t offset = 0;
if (type) {
memb = lookup_atype_with_dimensions(store, &cur->type, atuple->type);
- if (memb.size == 0 || memb.align == 0) {
- error(store->check_context, atuple->type->loc,
- "Tuple member types must have nonzero size and alignment");
- return dim;
- }
- cur->offset = dim.size % memb.align + dim.size;
} else {
memb = lookup_atype_with_dimensions(store, NULL, atuple->type);
- if (memb.size == 0 || memb.align == 0) {
- error(store->check_context, atuple->type->loc,
- "Tuple member types must have nonzero size and alignment");
- return dim;
- }
}
+ if (memb.size == 0) {
+ error(store->check_context, atype->loc,
+ "Type of size 0 is not a valid tuple member");
+ return (struct dimensions){0};
+ }
+ if (memb.size == SIZE_UNDEFINED) {
+ error(store->check_context, atype->loc,
+ "Type of undefined size is not a valid tuple member");
+ return (struct dimensions){0};
+ }
+ offset = dim.size % memb.align + dim.size;
dim.size += dim.size % memb.align + memb.size;
if (dim.align < memb.align) {
dim.align = memb.align;
}
atuple = atuple->next;
- if (atuple && type) {
- cur->next = xcalloc(1, sizeof(struct type_tuple));
- cur = cur->next;
+ if (type) {
+ cur->offset = offset;
+ if (atuple) {
+ cur->next = xcalloc(1, sizeof(struct type_tuple));
+ cur = cur->next;
+ }
}
}
if (type) {
@@ -637,6 +646,7 @@ type_init_from_atype(struct type_store *store,
case STORAGE_ICONST:
case STORAGE_RCONST:
case STORAGE_ENUM:
+ case STORAGE_NULL:
assert(0); // Invariant
case STORAGE_BOOL:
case STORAGE_CHAR:
@@ -720,9 +730,17 @@ type_init_from_atype(struct type_store *store,
memb = lookup_atype_with_dimensions(store,
&type->array.members, atype->array.members);
}
- if (memb.size == SIZE_UNDEFINED) {
+ // XXX: I'm not sure these checks are *exactly* right, we might
+ // still be letting some invalid stuff through
+ if (type->array.length != SIZE_UNDEFINED && memb.size == 0) {
error(store->check_context, atype->loc,
- "Array member must have defined size");
+ "Type of size 0 is not a valid array member");
+ *type = builtin_type_void;
+ return (struct dimensions){0};
+ }
+ if (type->array.length != SIZE_UNDEFINED && memb.size == SIZE_UNDEFINED) {
+ error(store->check_context, atype->loc,
+ "Type of undefined size is not a valid array member");
*type = builtin_type_void;
return (struct dimensions){0};
}
@@ -749,10 +767,22 @@ type_init_from_atype(struct type_store *store,
aparam; aparam = aparam->next) {
param = *next = xcalloc(1, sizeof(struct type_func_param));
param->type = lookup_atype(store, aparam->type);
+ if (param->type->size == 0) {
+ error(store->check_context, atype->loc,
+ "Function parameter types must have nonzero size");
+ *type = builtin_type_void;
+ return (struct dimensions){0};
+ }
+ if (param->type->size == SIZE_UNDEFINED) {
+ error(store->check_context, atype->loc,
+ "Function parameter types must have defined size");
+ *type = builtin_type_void;
+ return (struct dimensions){0};
+ }
if (atype->func.variadism == VARIADISM_HARE
&& !aparam->next) {
param->type = type_store_lookup_slice(
- store, param->type);
+ store, aparam->loc, param->type);
}
next = ¶m->next;
}
@@ -815,11 +845,6 @@ type_init_from_atype(struct type_store *store,
tuple_init_from_atype(store, type, atype);
}
break;
- case STORAGE_NULL:
- error(store->check_context, atype->loc,
- "Type null used in invalid context");
- *type = builtin_type_void;
- return (struct dimensions){0};
}
return (struct dimensions){ .size = type->size, .align = type->align };
}
@@ -931,10 +956,16 @@ type_store_lookup_with_flags(struct type_store *store,
}
const struct type *
-type_store_lookup_pointer(struct type_store *store,
+type_store_lookup_pointer(struct type_store *store, struct location loc,
const struct type *referent, unsigned int ptrflags)
{
+ if (referent->storage == STORAGE_NULL) {
+ error(store->check_context, loc,
+ "Null type not allowed in this context");
+ return &builtin_type_void;
+ }
referent = lower_const(referent, NULL);
+
struct type ptr = {
.storage = STORAGE_POINTER,
.pointer = {
@@ -948,10 +979,30 @@ type_store_lookup_pointer(struct type_store *store,
}
const struct type *
-type_store_lookup_array(struct type_store *store,
+type_store_lookup_array(struct type_store *store, struct location loc,
const struct type *members, size_t len, bool expandable)
{
+ if (members->storage == STORAGE_NULL) {
+ error(store->check_context, loc,
+ "Null type not allowed in this context");
+ return &builtin_type_void;
+ }
members = lower_const(members, NULL);
+ // XXX: I'm not sure these checks are *exactly* right, we might still
+ // be letting some invalid stuff pass
+ if (len != SIZE_UNDEFINED && members->size == 0) {
+ error(store->check_context, loc,
+ "Type of size 0 is not a valid array member");
+ return &builtin_type_void;
+ }
+ if (len != SIZE_UNDEFINED && members->size == SIZE_UNDEFINED) {
+ error(store->check_context, loc,
+ "Type of undefined size is not a valid member of a bounded array");
+ return &builtin_type_void;
+ }
+ assert(members->align != 0);
+ assert(members->align != ALIGN_UNDEFINED);
+
struct type array = {
.storage = STORAGE_ARRAY,
.array = {
@@ -968,9 +1019,28 @@ type_store_lookup_array(struct type_store *store,
}
const struct type *
-type_store_lookup_slice(struct type_store *store, const struct type *members)
+type_store_lookup_slice(struct type_store *store, struct location loc,
+ const struct type *members)
{
+ if (members->storage == STORAGE_NULL) {
+ error(store->check_context, loc,
+ "Null type not allowed in this context");
+ return &builtin_type_void;
+ }
members = lower_const(members, NULL);
+ if (members->size == 0) {
+ error(store->check_context, loc,
+ "Type of size 0 is not a valid slice member");
+ return &builtin_type_void;
+ }
+ if (members->size == SIZE_UNDEFINED) {
+ error(store->check_context, loc,
+ "Type of undefined size is not a valid slice member");
+ return &builtin_type_void;
+ }
+ assert(members->align != 0);
+ assert(members->align != ALIGN_UNDEFINED);
+
struct type slice = {
.storage = STORAGE_SLICE,
.array = {
@@ -1070,21 +1140,34 @@ type_store_tagged_to_union(struct type_store *store, const struct type *tagged)
}
const struct type *
-type_store_lookup_tuple(struct type_store *store, struct type_tuple *values,
- struct location loc)
+type_store_lookup_tuple(struct type_store *store, struct location loc,
+ struct type_tuple *values)
{
struct type type = {
.storage = STORAGE_TUPLE,
};
for (struct type_tuple *t = values; t; t = t->next) {
+ if (t->type->storage == STORAGE_NULL) {
+ error(store->check_context, loc,
+ "Null type not allowed in this context");
+ return &builtin_type_void;
+ }
t->type = lower_const(t->type, NULL);
- if (t->type->align > type.align) {
- type.align = t->type->align;
+ if (t->type->size == 0) {
+ error(store->check_context, loc,
+ "Type of size 0 is not a valid tuple member");
+ return &builtin_type_void;
}
- if (t->type->size == 0 || t->type->align == 0) {
+ if (t->type->size == SIZE_UNDEFINED) {
error(store->check_context, loc,
- "Tuple values must have nonzero size and alignment");
- break;
+ "Type of undefined size is not a valid tuple member");
+ return &builtin_type_void;
+ }
+ assert(t->type->align != 0);
+ assert(t->type->align != ALIGN_UNDEFINED);
+
+ if (t->type->align > type.align) {
+ type.align = t->type->align;
}
t->offset = type.size % t->type->align + type.size;
type.size += type.size % t->type->align + t->type->size;
@@ -1181,9 +1264,9 @@ type_store_reduce_result(struct type_store *store, struct location loc,
}
if ((it->pointer.flags & PTR_NULLABLE)
|| (jt->pointer.flags & PTR_NULLABLE)) {
- it = type_store_lookup_pointer(store,
+ it = type_store_lookup_pointer(store, loc,
it->pointer.referent, PTR_NULLABLE);
- jt = type_store_lookup_pointer(store,
+ jt = type_store_lookup_pointer(store, loc,
jt->pointer.referent, PTR_NULLABLE);
if (it == jt) {
dropped = true;
@@ -1214,7 +1297,7 @@ type_store_reduce_result(struct type_store *store, struct location loc,
if (null != NULL && ptr != NULL) {
*null = (*null)->next;
- ptr->type = type_store_lookup_pointer(store,
+ ptr->type = type_store_lookup_pointer(store, loc,
ptr->type->pointer.referent, PTR_NULLABLE);
}
diff --git a/tests/03-pointers.ha b/tests/03-pointers.ha
@@ -55,6 +55,17 @@ fn reject() void = {
};
") != 0);
assert(rt::compile("
+ type s = *null;
+ fn test() void = {
+ void;
+ };
+ ") != 0);
+ assert(rt::compile("
+ fn test() void = {
+ let a = &null;
+ };
+ ") != 0);
+ assert(rt::compile("
fn test() void = {
let a = &3: null;
};
diff --git a/tests/06-structs.ha b/tests/06-structs.ha
@@ -177,6 +177,10 @@ fn invariants() void = {
"fn test() void = { let x: struct { y: int } = struct { y: int = 10 }; x.z; };",
// Untyped field for unnamed struct:
"fn test() void = { let x = struct { x = 10 }; };",
+ // Members of undefined size before last
+ "type s = struct { x: [*]int, a: str };",
+ // Members of size 0
+ "type s = struct { x: void, a: str };"
];
for (let i = 0z; i < len(failures); i += 1) {
assert(rt::compile(failures[i]) != 0);
diff --git a/tests/09-funcs.ha b/tests/09-funcs.ha
@@ -69,10 +69,26 @@ fn cvaargs() void = {
cvafn(3, 1, 2, 3);
};
+fn reject() void = {
+ let failures: [_]str = [
+ // parameter of size 0
+ "fn f(x: void) void = void;",
+ // parameter of undefined size
+ "fn f(x: [*]int) void = void;",
+ // return value of undefined size
+ "fn f(x: int) [*]int = void;",
+ ];
+
+ for (let i = 0z; i < len(failures); i += 1) {
+ assert(rt::compile(failures[i]) != 0);
+ };
+};
+
export fn main() void = {
assert(simple() == 69);
pointers();
vaargs();
cvaargs();
assert(x == 1337);
+ reject();
};