diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-08-01 16:05:51 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-08-01 16:05:51 +0200 |
| commit | 71b3bbb7698b13b42c99a4ca66c27db4a6189ff8 (patch) | |
| tree | 1fc6ed246ecc0cf561f38ad774a4815ba357ea7b | |
| parent | 8539c10542a4be1e1127daea60bf1b1e4c37e092 (diff) | |
| download | elna-71b3bbb7698b13b42c99a4ca66c27db4a6189ff8.tar.gz | |
Enforce integral values in slice ranges
| -rw-r--r-- | boot/evaluator.cc | 35 | ||||
| -rw-r--r-- | boot/lexer.ll | 8 | ||||
| -rw-r--r-- | boot/parser.yy | 4 | ||||
| -rw-r--r-- | boot/result.cc | 57 | ||||
| -rw-r--r-- | boot/type_check.cc | 34 | ||||
| -rw-r--r-- | gcc/gcc/elna-tree.cc | 12 | ||||
| -rw-r--r-- | include/elna/boot/result.h | 28 | ||||
| -rw-r--r-- | include/elna/boot/type_check.h | 15 | ||||
| -rw-r--r-- | testsuite/fail_compilation/constant_enum_to_int.elna | 10 |
9 files changed, 152 insertions, 51 deletions
diff --git a/boot/evaluator.cc b/boot/evaluator.cc index 14b8ea3..56ba270 100644 --- a/boot/evaluator.cc +++ b/boot/evaluator.cc @@ -21,7 +21,6 @@ along with GCC; see the file COPYING3. If not see #include <algorithm> #include <cstddef> -#include <cstdint> #include <limits> #include <ranges> @@ -212,27 +211,23 @@ namespace elna::boot { type const decoration = subject.type_decoration; - if (is_primitive_type(decoration, "Int")) + if (is_primitive_type(decoration, "Int") || is_primitive_type(decoration, "Word")) { return constant_value{ static_cast<literal<integer_literal>&>(subject).value }; } - if (is_primitive_type(decoration, "Word")) - { - return constant_value{ static_cast<literal<integer_literal>&>(subject).value }; - } - if (is_primitive_type(decoration, "Float")) + else if (is_primitive_type(decoration, "Float")) { return constant_value{ static_cast<literal<double>&>(subject).value }; } - if (is_primitive_type(decoration, "Bool")) + else if (is_primitive_type(decoration, "Bool")) { return constant_value{ static_cast<literal<bool>&>(subject).value }; } - if (is_primitive_type(decoration, "Char")) + else if (is_primitive_type(decoration, "Char")) { return constant_value{ static_cast<literal<unsigned char>&>(subject).value }; } - if (is_primitive_type(decoration, "Pointer")) + else if (is_primitive_type(decoration, "Pointer")) { return constant_value{ std::nullptr_t{} }; } @@ -289,7 +284,7 @@ namespace elna::boot { auto enumeration_position = std::distance(enumeration->members.begin(), member_iterator) + 1; return constant_value{ - integer_literal::from(static_cast<std::int32_t>(enumeration_position)) + integer_literal::from(static_cast<std::size_t>(enumeration_position)) }; } return std::nullopt; @@ -704,14 +699,14 @@ namespace elna::boot { if (auto size = evaluate_traits_size(subject.types.front())) { - return constant_value{ integer_literal::from(static_cast<std::uint32_t>(size.value())) }; + return constant_value{ integer_literal::from(size.value()) }; } } else if (subject.name.name() == "alignment") { if (auto alignment = evaluate_traits_alignment(subject.types.front())) { - return constant_value{ integer_literal::from(static_cast<std::uint32_t>(alignment.value())) }; + return constant_value{ integer_literal::from(alignment.value()) }; } } else if (subject.name.name() == "min") @@ -720,11 +715,11 @@ namespace elna::boot if (is_primitive_type(resolved, "Int")) { - return constant_value{ integer_literal::from(std::numeric_limits<std::int32_t>::min()) }; + return constant_value{ integer_literal::from(std::numeric_limits<std::ptrdiff_t>::min()) }; } if (is_primitive_type(resolved, "Word")) { - return constant_value{ integer_literal::from<std::uint32_t>(0) }; + return constant_value{ integer_literal::from<std::size_t>(0) }; } if (is_primitive_type(resolved, "Char")) { @@ -740,7 +735,7 @@ namespace elna::boot } if (auto enumeration = resolved.get<enumeration_type>()) { - return constant_value{ integer_literal::from<std::int32_t>(1) }; + return constant_value{ integer_literal::from<std::size_t>(1) }; } } else if (subject.name.name() == "max") @@ -749,11 +744,11 @@ namespace elna::boot if (is_primitive_type(resolved, "Int")) { - return constant_value{ integer_literal::from(std::numeric_limits<std::int32_t>::max()) }; + return constant_value{ integer_literal::from(std::numeric_limits<std::ptrdiff_t>::max()) }; } if (is_primitive_type(resolved, "Word")) { - return constant_value{ integer_literal::from(std::numeric_limits<std::uint32_t>::max()) }; + return constant_value{ integer_literal::from(std::numeric_limits<std::size_t>::max()) }; } if (is_primitive_type(resolved, "Char")) { @@ -769,7 +764,7 @@ namespace elna::boot } if (auto enumeration = resolved.get<enumeration_type>()) { - return constant_value{ integer_literal::from(static_cast<std::int32_t>(enumeration->members.size())) }; + return constant_value{ integer_literal::from(enumeration->members.size()) }; } } else if (subject.name.name() == "offset") @@ -786,7 +781,7 @@ namespace elna::boot if (field_search != std::cend(record_layout.value().offset_map)) { - return constant_value{ integer_literal::from(static_cast<std::uint32_t>(field_search->second)) }; + return constant_value{ integer_literal::from(field_search->second) }; } } } diff --git a/boot/lexer.ll b/boot/lexer.ll index 89fe85c..ba54433 100644 --- a/boot/lexer.ll +++ b/boot/lexer.ll @@ -174,7 +174,7 @@ to { return yy::parser::make_TRAIT(yytext + 1, this->location); } [[:digit:]]+u { - unsigned long result = strtoul(yytext, NULL, 10); + std::uint64_t result = strtoull(yytext, NULL, 10); if (errno == ERANGE) { @@ -186,7 +186,7 @@ to { } } [[:digit:]]+ { - long result = strtol(yytext, NULL, 10); + std::int64_t result = strtoll(yytext, NULL, 10); if (errno == ERANGE) { @@ -198,7 +198,7 @@ to { } } 0[x|X]{HIGIT}+ { - unsigned long result = strtoul(yytext, NULL, 16); + std::uint64_t result = strtoull(yytext, NULL, 16); if (errno == ERANGE) { @@ -210,7 +210,7 @@ to { } } 0[b|B]{BIGIT}+ { - unsigned long result = strtoul(yytext, NULL, 2); + std::uint64_t result = strtoull(yytext, NULL, 2); if (errno == ERANGE) { diff --git a/boot/parser.yy b/boot/parser.yy index 670a265..bc0cb53 100644 --- a/boot/parser.yy +++ b/boot/parser.yy @@ -78,8 +78,8 @@ along with GCC; see the file COPYING3. If not see %token <std::string> IDENTIFIER %token <std::string> TRAIT -%token <std::int32_t> INTEGER -%token <std::uint32_t> WORD +%token <std::int64_t> INTEGER +%token <std::uint64_t> WORD %token <double> FLOAT %token <std::string> CHARACTER %token <std::string> STRING diff --git a/boot/result.cc b/boot/result.cc index 4d4c20f..f769625 100644 --- a/boot/result.cc +++ b/boot/result.cc @@ -17,6 +17,7 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/result.h" +#include <cstring> #include <numeric> namespace elna::boot @@ -340,6 +341,16 @@ namespace elna::boot return written > 1 ? std::nullopt : std::make_optional(rop); } + bool integer_literal::fit_into(const std::size_t target_size) + { + if (fits_in(target_size * CHAR_BIT)) + { + this->m_size = target_size; + return true; + } + return false; + } + bool integer_literal::is_signed() const { return this->m_signed; @@ -350,6 +361,18 @@ namespace elna::boot return this->m_size; } + std::string integer_literal::to_string(const std::uint8_t base) const + { + // +1 sign, +1 null terminator + const size_t buffer_size = mpz_sizeinbase(this->raw, static_cast<int>(base)) + 2; + std::string result(buffer_size, '\0'); + + mpz_get_str(result.data(), base, this->raw); + result.resize(std::strlen(result.c_str())); + + return result; + } + void swap(integer_literal& lhs, integer_literal& rhs) noexcept { mpz_swap(lhs.raw, rhs.raw); @@ -357,22 +380,20 @@ namespace elna::boot std::swap(lhs.m_size, rhs.m_size); } - std::optional<integer_literal> integer_literal::check() && + bool integer_literal::fits_in(const std::size_t bits) const { std::size_t required_bits = mpz_sizeinbase(this->raw, 2); - if (!is_negative_minimum()) + if (!is_negative_minimum(bits)) { ++required_bits; // Add one bit for the sign. } - if (required_bits > bits() || (mpz_sgn(this->raw) < 0 && !is_signed())) - { - return std::nullopt; - } - else - { - return std::move(*this); - } + return required_bits <= bits && (mpz_sgn(this->raw) >= 0 || is_signed()); + } + + std::optional<integer_literal> integer_literal::check() && + { + return fits_in(bits()) ? std::make_optional(std::move(*this)) : std::nullopt; } bool integer_literal::is_negative_minimum(const std::size_t bits) const @@ -454,5 +475,19 @@ std::size_t std::hash<elna::boot::constant_aggregate<elna::boot::ordered_map>>:: std::size_t std::hash<elna::boot::integer_literal>::operator()(const elna::boot::integer_literal& key) const noexcept { - return std::hash<std::uint32_t>{}(key.to<std::uint32_t>()); + if (key.is_signed()) + { + if (auto converted = key.to_signed()) + { + return std::hash<std::ptrdiff_t>{}(*converted); + } + } + else + { + if (auto converted = key.to_unsigned()) + { + return std::hash<std::size_t>{}(*converted); + } + } + return 0; } diff --git a/boot/type_check.cc b/boot/type_check.cc index 07deb2d..529f66a 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -165,6 +165,11 @@ namespace elna::boot return "Type '" + this->actual.to_string() + "' cannot be converted to '" + payload.target.to_string() + "'"; } + else if constexpr (std::is_same_v<T, integer_literal_overflow>) + { + return "The number " + payload.overflow.to_string() + + " does not fit into the type '" + actual.to_string() + "'"; + } else if constexpr (std::is_same_v<T, kind>) { switch (payload) @@ -762,6 +767,17 @@ namespace elna::boot void type_analysis_visitor::visit(slicing_expression *expression) { walking_visitor::visit(expression); + + if (!is_integral_type(resolve_underlying_type(expression->start().type_decoration))) + { + add_error<type_mismatch_error>(expression->start().position(), + expression->start().type_decoration, type_mismatch_error::kind::array_index); + } + if (!is_integral_type(resolve_underlying_type(expression->end().type_decoration))) + { + add_error<type_mismatch_error>(expression->end().position(), + expression->end().type_decoration, type_mismatch_error::kind::array_index); + } } void type_analysis_visitor::visit(array_access_expression *expression) @@ -1008,4 +1024,22 @@ namespace elna::boot } } } + + void type_analysis_visitor::visit(literal<integer_literal> *expression) + { + bool narrowed{ false }; + if (expression->value.is_signed()) + { + narrowed = expression->value.fit_into(target.int_properties.size); + } + else + { + narrowed = expression->value.fit_into(target.word_properties.size); + } + if (!narrowed) + { + add_error<type_mismatch_error>(expression->position(), expression->type_decoration, + type_mismatch_error::integer_literal_overflow{ expression->value }); + } + } } diff --git a/gcc/gcc/elna-tree.cc b/gcc/gcc/elna-tree.cc index 55e359b..6aaf6f9 100644 --- a/gcc/gcc/elna-tree.cc +++ b/gcc/gcc/elna-tree.cc @@ -289,11 +289,19 @@ namespace elna::gcc if (literal_value.is_signed()) { - return build_int_cst(elna_int_type_node, literal_value.to<std::int32_t>()); + if (auto converted = literal_value.to_signed()) + { + return build_int_cst(elna_int_type_node, *converted); + } + return NULL_TREE; } else { - return build_int_cstu(elna_word_type_node, literal_value.to<std::uint32_t>()); + if (auto converted = literal_value.to_unsigned()) + { + return build_int_cstu(elna_word_type_node, *converted); + } + return NULL_TREE; } } else if (std::holds_alternative<double>(constant_value)) diff --git a/include/elna/boot/result.h b/include/elna/boot/result.h index 4c6046d..56884ff 100644 --- a/include/elna/boot/result.h +++ b/include/elna/boot/result.h @@ -268,17 +268,7 @@ namespace elna::boot bool is_signed() const; std::size_t size() const; - - template<typename T> - T to() const - requires is_integral<T> - { - T result{}; - - mpz_export(&result, nullptr, 1, sizeof(T), 0, 0, this->raw); - - return result; - } + std::string to_string(const std::uint8_t base = 10U) const; /** * Exports the stored value as a host \c std::ptrdiff_t. @@ -294,6 +284,21 @@ namespace elna::boot */ std::optional<std::size_t> to_unsigned() const; + /** + * Attempts to narrow this literal's internally stored size to + * \p target_size bytes, in place. + * + * If the current value can be represented within \p target_size bytes + * (given the literal's existing signedness), the internal storage size + * is updated to \p target_size and the function returns \c true. If the + * value would not fit, the literal is left unmodified and \c false is + * returned. + * + * \param target_size Size of the target type in bytes. + * \return Whether the value fits and has been changed. + */ + bool fit_into(const std::size_t target_size); + template<typename T> static integer_literal from(T initial) requires is_integral<T> @@ -313,6 +318,7 @@ namespace elna::boot mpz_t raw; integer_literal(bool is_signed, std::size_t size); + bool fits_in(const std::size_t bits) const; std::optional<integer_literal> check() &&; bool is_negative_minimum(const std::size_t bits) const; bool is_negative_minimum() const; diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h index 628af06..82f0092 100644 --- a/include/elna/boot/type_check.h +++ b/include/elna/boot/type_check.h @@ -54,6 +54,10 @@ namespace elna::boot { type target; }; + struct integer_literal_overflow + { + integer_literal overflow; + }; enum class kind { record_base, for_range, @@ -63,7 +67,15 @@ namespace elna::boot non_indexable, dereference_of_non_pointer }; - using payload_type = std::variant<expected_type, return_type, unary, binary, invalid_cast, kind>; + using payload_type = std::variant< + expected_type, + return_type, + unary, + binary, + invalid_cast, + integer_literal_overflow, + kind + >; type_mismatch_error(const source_position position, type actual, payload_type payload); @@ -213,5 +225,6 @@ namespace elna::boot void visit(cast_expression *expression) override; void visit(unary_expression *expression) override; void visit(binary_expression *expression) override; + void visit(literal<integer_literal> *expression) override; }; } diff --git a/testsuite/fail_compilation/constant_enum_to_int.elna b/testsuite/fail_compilation/constant_enum_to_int.elna new file mode 100644 index 0000000..f97bd3f --- /dev/null +++ b/testsuite/fail_compilation/constant_enum_to_int.elna @@ -0,0 +1,10 @@ +type + Enumeration = (one, two, three) + +var + x: [3]Int := [3]Int{ 1, 2, 3 } + y: []Int + +begin + y := x[Enumeration.one to Enumeration.two] (* @Error Array index must be an integral type, but got 'Enumeration' *) +end. |
