diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-08-10 14:44:03 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-08-10 14:44:03 -0400 |
| commit | 611ebeab13b64d505ad4b44d0f80b3d0e9fb8dba (patch) | |
| tree | f0edc3737d3e7366aad896355f2f7628bc2d8840 /src | |
| parent | 98cadc2135be65abcf9dff53d87af9ea549757a2 (diff) | |
Add full protection against accessing fields and methods that start with
underscores outside of the scope where the type is defined.
Diffstat (limited to 'src')
| -rw-r--r-- | src/compile.c | 23 | ||||
| -rw-r--r-- | src/environment.c | 14 | ||||
| -rw-r--r-- | src/environment.h | 3 | ||||
| -rw-r--r-- | src/typecheck.c | 23 |
4 files changed, 51 insertions, 12 deletions
diff --git a/src/compile.c b/src/compile.c index 95ee6e3a..1795bb38 100644 --- a/src/compile.c +++ b/src/compile.c @@ -134,7 +134,8 @@ static bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t // Numeric promotions/demotions if ((is_numeric_type(actual) || actual->tag == BoolType) && (is_numeric_type(needed) || needed->tag == BoolType)) { arg_ast_t *args = new(arg_ast_t, .value=LiteralCode(*code, .type=actual)); - binding_t *constructor = get_constructor(env, needed, args); + binding_t *constructor = get_constructor(env, needed, args, + env->current_type != NULL && type_eq(env->current_type, value_type(needed))); if (constructor) { DeclareMatch(fn, constructor->type, FunctionType); if (fn->args->next == NULL) { @@ -2939,7 +2940,8 @@ Text_t compile(env_t *env, ast_t *ast) if (chunk->ast->tag == TextLiteral || type_eq(chunk_t, text_t)) { chunk_code = compile(env, chunk->ast); } else { - binding_t *constructor = get_constructor(env, text_t, new(arg_ast_t, .value=chunk->ast)); + binding_t *constructor = get_constructor(env, text_t, new(arg_ast_t, .value=chunk->ast), + env->current_type != NULL && type_eq(env->current_type, text_t)); if (constructor) { arg_t *arg_spec = Match(constructor->type, FunctionType)->args; arg_ast_t *args = new(arg_ast_t, .value=chunk->ast); @@ -3487,7 +3489,8 @@ Text_t compile(env_t *env, ast_t *ast) else if (t->tag == NumType && call->args && !call->args->next && call->args->value->tag == Num) return compile_to_type(env, call->args->value, t); - binding_t *constructor = get_constructor(env, t, call->args); + binding_t *constructor = get_constructor(env, t, call->args, + env->current_type != NULL && type_eq(env->current_type, t)); if (constructor) { arg_t *arg_spec = Match(constructor->type, FunctionType)->args; return Texts(constructor->code, "(", compile_arguments(env, ast, arg_spec, call->args), ")"); @@ -3518,6 +3521,12 @@ Text_t compile(env_t *env, ast_t *ast) } else if (t->tag == StructType) { DeclareMatch(struct_, t, StructType); if (!struct_->opaque && is_valid_call(env, struct_->fields, call->args, true)) { + if (env->current_type == NULL || !type_eq(env->current_type, t)) { + for (arg_t *field = struct_->fields; field; field = field->next) { + if (field->name[0] == '_') + code_err(ast, "This struct can't be initialized directly because it has private fields (starting with underscore)"); + } + } return Texts("((", compile_type(t), "){", compile_arguments(env, ast, struct_->fields, call->args), "})"); } @@ -3797,12 +3806,8 @@ Text_t compile(env_t *env, ast_t *ast) case TypeInfoType: { DeclareMatch(info, value_t, TypeInfoType); if (f->field[0] == '_') { - for (Table_t *locals = env->locals; locals; locals = locals->fallback) { - if (locals == info->env->locals) - goto is_inside_type; - } - code_err(ast, "Fields that start with underscores are not accessible on types outside of the type definition."); - is_inside_type:; + if (!type_eq(env->current_type, info->type)) + code_err(ast, "Fields that start with underscores are not accessible on types outside of the type definition."); } binding_t *b = get_binding(info->env, f->field); if (!b) code_err(ast, "I couldn't find the field '", f->field, "' on this type"); diff --git a/src/environment.c b/src/environment.c index 0bcdd592..93b5a16c 100644 --- a/src/environment.c +++ b/src/environment.c @@ -749,7 +749,7 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name) return ns_env ? get_binding(ns_env, name) : NULL; } -PUREFUNC binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args) +PUREFUNC binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args, bool allow_underscores) { env_t *type_env = get_namespace_by_type(env, t); if (!type_env) return NULL; @@ -758,15 +758,27 @@ PUREFUNC binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args) for (int64_t i = constructors.length-1; i >= 0; i--) { binding_t *b = constructors.data + i*constructors.stride; DeclareMatch(fn, b->type, FunctionType); + if (!allow_underscores) { + for (arg_t *arg = fn->args; arg; arg = arg->next) + if (arg->name[0] == '_') + goto next_constructor; + } if (type_eq(fn->ret, t) && is_valid_call(env, fn->args, args, false)) return b; + next_constructor: continue; } // Fall back to promotion: for (int64_t i = constructors.length-1; i >= 0; i--) { binding_t *b = constructors.data + i*constructors.stride; DeclareMatch(fn, b->type, FunctionType); + if (!allow_underscores) { + for (arg_t *arg = fn->args; arg; arg = arg->next) + if (arg->name[0] == '_') + goto next_constructor2; + } if (type_eq(fn->ret, t) && is_valid_call(env, fn->args, args, true)) return b; + next_constructor2: continue; } return NULL; } diff --git a/src/environment.h b/src/environment.h index 2d4e822c..18b749ed 100644 --- a/src/environment.h +++ b/src/environment.h @@ -50,6 +50,7 @@ typedef struct env_s { deferral_t *deferred; Closure_t *comprehension_action; bool do_source_mapping:1; + type_t *current_type; } env_t; typedef struct { @@ -84,7 +85,7 @@ env_t *namespace_env(env_t *env, const char *namespace_name); exit(1); \ }) binding_t *get_binding(env_t *env, const char *name); -binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args); +binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args, bool allow_underscores); PUREFUNC binding_t *get_metamethod_binding(env_t *env, ast_e tag, ast_t *lhs, ast_t *rhs, type_t *ret); void set_binding(env_t *env, const char *name, type_t *type, Text_t code); binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); diff --git a/src/typecheck.c b/src/typecheck.c index d4ff0e7d..494c73d8 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -353,6 +353,7 @@ void bind_statement(env_t *env, ast_t *statement) type_t *type = Table$str_get(*env->types, def->name); if (!type) code_err(statement, "Couldn't find type!"); assert(type); + ns_env->current_type = type; if (!def->opaque) { arg_t *fields = NULL; for (arg_ast_t *field_ast = def->fields; field_ast; field_ast = field_ast->next) { @@ -394,6 +395,7 @@ void bind_statement(env_t *env, ast_t *statement) env_t *ns_env = namespace_env(env, def->name); type_t *type = Table$str_get(*env->types, def->name); assert(type); + ns_env->current_type = type; tag_t *tags = NULL; int64_t next_tag = 1; bool has_any_tags_with_fields = false; @@ -458,6 +460,7 @@ void bind_statement(env_t *env, ast_t *statement) DeclareMatch(def, statement, LangDef); env_t *ns_env = namespace_env(env, def->name); type_t *type = Type(TextType, .lang=def->name, .env=ns_env); + ns_env->current_type = type; Table$str_set(env->types, def->name, type); set_binding(ns_env, "from_text", NewFunctionType(type, {.name="text", .type=TEXT_TYPE}), @@ -823,6 +826,19 @@ type_t *get_type(env_t *env, ast_t *ast) case FieldAccess: { DeclareMatch(access, ast, FieldAccess); type_t *fielded_t = get_type(env, access->fielded); + if (access->field[0] == '_') { + if (!env->current_type || !type_eq(env->current_type, + fielded_t->tag == TypeInfoType + ? Match(fielded_t, TypeInfoType)->type + : value_type(fielded_t))) + code_err(ast, "Fields beginning with underscores like '", access->field, + "' can't be accessed outside the scope where the type (", + type_to_text( + fielded_t->tag == TypeInfoType + ? Match(fielded_t, TypeInfoType)->type + : value_type(fielded_t)), + ") is defined."); + } if (fielded_t->tag == ModuleType) { const char *name = Match(fielded_t, ModuleType)->name; env_t *module_env = Table$str_get(*env->imports, name); @@ -876,7 +892,7 @@ type_t *get_type(env_t *env, ast_t *ast) if (fn_type_t->tag == TypeInfoType) { type_t *t = Match(fn_type_t, TypeInfoType)->type; - binding_t *constructor = get_constructor(env, t, call->args); + binding_t *constructor = get_constructor(env, t, call->args, env->current_type != NULL && type_eq(env->current_type, t)); if (constructor) return t; else if (t->tag == StructType || t->tag == IntType || t->tag == BigIntType || t->tag == NumType @@ -964,6 +980,11 @@ type_t *get_type(env_t *env, ast_t *ast) code_err(ast, "There is no '", call->name, "' method for ", type_to_str(self_value_t), " tables"); } default: { + if (call->name[0] == '_') { + if (env->current_type == NULL || !type_eq(env->current_type, self_value_t)) + code_err(ast, "You can't call private methods starting with underscore (like '", call->name, "') " + "outside of the place where the type (", type_to_text(self_value_t), ") is defined."); + } type_t *field_type = get_field_type(self_value_t, call->name); if (field_type && field_type->tag == ClosureType) field_type = Match(field_type, ClosureType)->fn; |
