diff --git a/CHANGES b/CHANGES index 7986d48aa..47a7b02fa 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,16 @@ +1.12.0-dev.242 | 2024-12-13 09:26:36 +0100 + + * GH-1901: Add consistent validation for attributes. (Evan Typanski, Corelight) + + This updates attribute validation to see if attributes are in the + right places. The goal here is to have one place (ie a big set) to + answer the question "Can I use X attribute on Y node?" + + There are a lot more moving parts with attribute validation, but those + generally have to do with behavior. A lot of that requires extra context + in the validator, which is exactly what the validator is meant to do. + Much of that is pretty ad-hoc, so it could get cleaned up as well. + 1.12.0-dev.240 | 2024-12-12 13:09:05 +0100 * Make sure autogen-docs pre-commit hook can always run in CI. (Benjamin Bannier, Corelight) diff --git a/VERSION b/VERSION index 6f568f07e..39b42bcc7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.12.0-dev.240 +1.12.0-dev.242 diff --git a/hilti/toolchain/include/ast/node.h b/hilti/toolchain/include/ast/node.h index fd1b39a07..7f9f56906 100644 --- a/hilti/toolchain/include/ast/node.h +++ b/hilti/toolchain/include/ast/node.h @@ -240,7 +240,15 @@ class Node { virtual ~Node(); /** Returns the node tag associated with the instance's class. */ - auto nodeTag() const { return _node_tags.back(); } + node::Tag nodeTag() const { + // Get the last non-zero tag. The last element(s) may be unset + for ( auto it = _node_tags.rbegin(); it != _node_tags.rend(); it++ ) { + if ( *it != 0 ) + return *it; + } + + return 0; + } /** Returns true if the node has a parent (i.e., it's part of an AST). */ bool hasParent() const { return _parent; } diff --git a/hilti/toolchain/src/compiler/validator.cc b/hilti/toolchain/src/compiler/validator.cc index 0beca1a6e..13fef2135 100644 --- a/hilti/toolchain/src/compiler/validator.cc +++ b/hilti/toolchain/src/compiler/validator.cc @@ -60,6 +60,18 @@ using namespace hilti; using util::fmt; +/** + * A mapping of node tags to any attributes that node allows. When a new + * attribute is added, this map must be updated to accept that attribute on any + * nodes it applies to. + */ +static std::unordered_map> allowed_attributes{ + {node::tag::Function, + {Attribute::Kind::Cxxname, Attribute::Kind::HavePrototype, Attribute::Kind::Priority, Attribute::Kind::Static, + Attribute::Kind::NeededByFeature, Attribute::Kind::Debug, Attribute::Kind::Foreach, Attribute::Kind::Error}}, + {node::tag::declaration::Parameter, {Attribute::Kind::RequiresTypeFeature}}, +}; + void hilti::validator::VisitorMixIn::deprecated(const std::string& msg, const Location& l) const { hilti::logger().deprecated(msg, l); } @@ -124,6 +136,30 @@ struct VisitorPre : visitor::PreOrder, public validator::VisitorMixIn { struct VisitorPost : visitor::PreOrder, public validator::VisitorMixIn { using hilti::validator::VisitorMixIn::VisitorMixIn; + // Ensures that the node represented by tag is allowed to have all of the + // provided attributes. This does not use any context, if more information + // is needed, then do the check elsewhere. + void checkNodeAttributes(node::Tag tag, AttributeSet* attributes, const std::string_view& where) { + if ( ! attributes ) + return; + + auto it = allowed_attributes.find(tag); + + if ( it == allowed_attributes.end() ) { + if ( ! attributes->attributes().empty() ) + error(hilti::util::fmt("No attributes expected in %s", where), attributes); + + return; + } + + auto allowed = it->second; + + for ( const auto& attr : attributes->attributes() ) { + if ( allowed.find(attr->kind()) == allowed.end() ) + error(hilti::util::fmt("invalid attribute '%s' in %s", attr->attributeName(), where), attr); + } + } + // Returns an error if the given type cannot be used for ordering at // runtime. Result isSortable(QualifiedType* t) { @@ -176,6 +212,8 @@ struct VisitorPost : visitor::PreOrder, public validator::VisitorMixIn { } void operator()(Function* n) final { + checkNodeAttributes(n->nodeTag(), n->attributes(), "function"); + if ( auto attrs = n->attributes() ) { if ( auto prio = attrs->find(hilti::Attribute::Kind::Priority) ) { if ( n->ftype()->flavor() != type::function::Flavor::Hook ) @@ -260,6 +298,8 @@ struct VisitorPost : visitor::PreOrder, public validator::VisitorMixIn { } void operator()(declaration::Parameter* n) final { + checkNodeAttributes(n->nodeTag(), n->attributes(), n->displayName()); + if ( ! n->type()->type()->isA() ) { if ( ! n->type()->type()->isAllocable() && ! n->type()->type()->isA() ) error(fmt("type '%s' cannot be used for function parameter", *n->type()), n); @@ -285,10 +325,7 @@ struct VisitorPost : visitor::PreOrder, public validator::VisitorMixIn { if ( auto attrs = n->attributes() ) for ( const auto& attr : attrs->attributes() ) { - if ( attr->kind() != hilti::Attribute::Kind::RequiresTypeFeature ) - error(fmt("invalid attribute '%s' for function parameter", attr->attributeName()), n); - - else { + if ( attr->kind() == hilti::Attribute::Kind::RequiresTypeFeature ) { if ( auto x = attr->valueAsString(); ! x ) error(x.error(), n); } diff --git a/spicy/toolchain/src/compiler/validator.cc b/spicy/toolchain/src/compiler/validator.cc index f02bc4db4..d91070caa 100644 --- a/spicy/toolchain/src/compiler/validator.cc +++ b/spicy/toolchain/src/compiler/validator.cc @@ -1,5 +1,7 @@ // Copyright (c) 2020-2023 by the Zeek Project. See LICENSE for details. +#include + #include #include @@ -21,6 +23,61 @@ using namespace spicy; using hilti::util::fmt; namespace { +/** + * A mapping of node tags to any attributes that node allows. When a new + * attribute is added, this map must be updated to accept that attribute on any + * nodes it applies to. + * + * This also includes many types that cannot themselves contain attributes. Those + * types ensure that they can be within a field with the provided attributes. + */ +std::unordered_map> allowed_attributes{ + {hilti::node::tag::declaration::Hook, + {hilti::Attribute::Kind::Foreach, hilti::Attribute::Kind::Error, hilti::Attribute::Kind::Debug, + hilti::Attribute::Kind::Priority}}, + {hilti::node::tag::declaration::Type, {hilti::Attribute::Kind::Cxxname, hilti::Attribute::Kind::BitOrder}}, + {hilti::node::tag::type::Enum, {hilti::Attribute::Kind::Cxxname}}, + {hilti::node::tag::type::Unit, + {hilti::Attribute::Kind::ByteOrder, hilti::Attribute::Kind::Convert, hilti::Attribute::Kind::Size, + hilti::Attribute::Kind::MaxSize, hilti::Attribute::Kind::Requires}}, + {hilti::node::tag::type::unit::item::Variable, {hilti::Attribute::Kind::Optional}}, + {hilti::node::tag::type::unit::item::Field, + {hilti::Attribute::Kind::Count, hilti::Attribute::Kind::Convert, hilti::Attribute::Kind::Chunked, + hilti::Attribute::Kind::Synchronize, hilti::Attribute::Kind::Size, hilti::Attribute::Kind::ParseAt, + hilti::Attribute::Kind::MaxSize, hilti::Attribute::Kind::ParseFrom, hilti::Attribute::Kind::Type, + hilti::Attribute::Kind::Until, hilti::Attribute::Kind::UntilIncluding, hilti::Attribute::Kind::While, + hilti::Attribute::Kind::IPv4, hilti::Attribute::Kind::IPv6, hilti::Attribute::Kind::Eod, + hilti::Attribute::Kind::ByteOrder, hilti::Attribute::Kind::BitOrder, hilti::Attribute::Kind::Requires, + hilti::Attribute::Kind::Try, hilti::Attribute::Kind::Nosub, hilti::Attribute::Kind::Default}}, + {hilti::node::tag::type::unit::item::Block, + {hilti::Attribute::Kind::Size, hilti::Attribute::Kind::ParseAt, hilti::Attribute::Kind::ParseFrom}}, + {hilti::node::tag::type::unit::item::Switch, + {hilti::Attribute::Kind::Size, hilti::Attribute::Kind::ParseAt, hilti::Attribute::Kind::ParseFrom}}, + {hilti::node::tag::type::unit::item::Property, + {hilti::Attribute::Kind::Originator, hilti::Attribute::Kind::Responder}}, + + // The following apply only to types within a field + {hilti::node::tag::type::Address, + {hilti::Attribute::Kind::IPv4, hilti::Attribute::Kind::IPv6, hilti::Attribute::Kind::ByteOrder}}, + {hilti::node::tag::type::Bitfield, {hilti::Attribute::Kind::ByteOrder, hilti::Attribute::Kind::BitOrder}}, + {hilti::node::tag::type::Bytes, + {hilti::Attribute::Kind::Eod, hilti::Attribute::Kind::Until, hilti::Attribute::Kind::UntilIncluding, + hilti::Attribute::Kind::Chunked, hilti::Attribute::Kind::Nosub}}, + {hilti::node::tag::type::Real, {hilti::Attribute::Kind::Type, hilti::Attribute::Kind::ByteOrder}}, + {hilti::node::tag::type::RegExp, {hilti::Attribute::Kind::Nosub}}, + {hilti::node::tag::type::SignedInteger, {hilti::Attribute::Kind::ByteOrder, hilti::Attribute::Kind::BitOrder}}, + {hilti::node::tag::type::Unit, {hilti::Attribute::Kind::ParseAt}}, + {hilti::node::tag::type::UnsignedInteger, {hilti::Attribute::Kind::ByteOrder, hilti::Attribute::Kind::BitOrder}}, + {hilti::node::tag::type::Vector, + {hilti::Attribute::Kind::UntilIncluding, hilti::Attribute::Kind::While, hilti::Attribute::Kind::Until, + hilti::Attribute::Kind::Count, hilti::Attribute::Kind::Eod}}, +}; + +std::unordered_set allowed_attributes_for_any_field = + {hilti::Attribute::Kind::Synchronize, hilti::Attribute::Kind::Convert, hilti::Attribute::Kind::Requires, + hilti::Attribute::Kind::Default, hilti::Attribute::Kind::Size, hilti::Attribute::Kind::MaxSize, + hilti::Attribute::Kind::Try, hilti::Attribute::Kind::ParseAt, hilti::Attribute::Kind::ParseFrom}; + bool isEnumType(QualifiedType* t, const char* expected_id) { return t->type()->typeID() && t->type()->typeID() == ID(expected_id); @@ -36,8 +93,8 @@ bool supportsLiterals(QualifiedType* t) { // Helper to make sure a field's attributes are consistent. This is type-independent. hilti::Result checkFieldAttributes(type::unit::item::Field* f) { // Can't combine ipv4 and ipv6 - auto v4 = f->attributes()->find(hilti::Attribute::Kind::IPv4); - auto v6 = f->attributes()->find(hilti::Attribute::Kind::IPv6); + auto v4 = f->attributes()->has(hilti::Attribute::Kind::IPv4); + auto v6 = f->attributes()->has(hilti::Attribute::Kind::IPv6); if ( v4 && v6 ) return hilti::result::Error("field cannot have both &ipv4 and &ipv6 attributes"); @@ -112,8 +169,8 @@ hilti::Result isParseableType(QualifiedType* pt, type::unit::ite } if ( pt->type()->isA() ) { - auto v4 = f->attributes()->find(hilti::Attribute::Kind::IPv4); - auto v6 = f->attributes()->find(hilti::Attribute::Kind::IPv6); + auto v4 = f->attributes()->has(hilti::Attribute::Kind::IPv4); + auto v6 = f->attributes()->has(hilti::Attribute::Kind::IPv6); if ( ! (v4 || v6) ) return hilti::result::Error("address field must come with either &ipv4 or &ipv6 attribute"); @@ -149,16 +206,9 @@ hilti::Result isParseableType(QualifiedType* pt, type::unit::ite return hilti::Nothing(); } - if ( pt->type()->isA() ) { - if ( f->attributes() ) { - for ( const auto& a : f->attributes()->attributes() ) { - if ( a->kind() != hilti::Attribute::Kind::Requires ) - return hilti::result::Error("no parsing attributes supported for void field"); - } - } - + if ( pt->type()->isA() ) + // Already validated that Void only has allowed attributes return hilti::Nothing(); - } // A vector can contain a sub-item if ( f->item() ) { @@ -198,6 +248,50 @@ struct VisitorPre : visitor::PreOrder, hilti::validator::VisitorMixIn { }; struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { + // Ensures that the node represented by tag is allowed to have all of the + // provided attributes. This does not use any context, if more information + // is needed, then do the check elsewhere. + void checkNodeAttributes(node::Tag tag, AttributeSet* attributes, const std::string_view& where) { + if ( ! attributes ) + return; + + auto it = allowed_attributes.find(tag); + + if ( it == allowed_attributes.end() ) { + if ( ! attributes->attributes().empty() ) + error(hilti::util::fmt("No attributes expected in %s", where), attributes); + + return; + } + + auto allowed = it->second; + + for ( const auto& attr : attributes->attributes() ) + if ( allowed.find(attr->kind()) == allowed.end() ) + error(hilti::util::fmt("invalid attribute '%s' in %s", attr->attributeName(), where), attr); + } + + // Ensures that the type represented by typeTag can be within a field with + // the provided attributes. This is necessary since most attributes will apply + // to the field but not its type, so this gives a bit more context-sensitive + // validation for a common case. + void validateFieldTypeAttributes(node::Tag typeTag, AttributeSet* attributes, const std::string_view& clazz) { + if ( ! attributes ) + return; + + std::unordered_set type_specific_attrs = {}; + auto it = allowed_attributes.find(typeTag); + if ( it != allowed_attributes.end() ) + type_specific_attrs = it->second; + + for ( const auto& attr : attributes->attributes() ) { + if ( allowed_attributes_for_any_field.find(attr->kind()) == allowed_attributes_for_any_field.end() && + type_specific_attrs.find(attr->kind()) == type_specific_attrs.end() ) + error(hilti::util::fmt("invalid attribute '%s' for field with type '%s'", attr->attributeName(), clazz), + attr); + } + } + using hilti::validator::VisitorMixIn::VisitorMixIn; template @@ -368,6 +462,8 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { } void operator()(hilti::declaration::Type* n) final { + checkNodeAttributes(n->nodeTag(), n->attributes(), "type declaration"); + if ( n->linkage() == hilti::declaration::Linkage::Public && n->type()->alias() ) { if ( n->type()->alias()->resolvedDeclaration()->linkage() != hilti::declaration::Linkage::Public ) error("public unit alias cannot refer to a non-public type", n); @@ -375,6 +471,8 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { } void operator()(spicy::type::unit::item::Property* n) final { + checkNodeAttributes(n->nodeTag(), n->attributes(), "unit property"); + if ( n->id().str() == "%random-access" ) { if ( n->expression() ) error("%random-access does not accept an argument", n); @@ -492,25 +590,33 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { } void operator()(spicy::declaration::Hook* n) final { - int count = 0; - for ( const auto& a : n->attributes()->attributes() ) { - if ( a->kind() == hilti::Attribute::Kind::Foreach ) { - if ( auto field = n->parent(); field && ! field->isContainer() ) - error("'foreach' can only be used with containers", n); + checkNodeAttributes(n->nodeTag(), n->attributes(), "hook declaration"); - ++count; - } - else if ( a->kind() == hilti::Attribute::Kind::Error ) - ++count; - else if ( a->kind() == hilti::Attribute::Kind::Debug || a->kind() == hilti::Attribute::Kind::Priority ) { - // ok on any hook + if ( auto field = n->parent(); + field && n->attributes()->has(hilti::Attribute::Kind::Foreach) && ! field->isContainer() ) + error("'foreach' can only be used with containers", n); + + if ( n->attributes()->has(hilti::Attribute::Kind::Foreach) && + n->attributes()->has(hilti::Attribute::Kind::Error) ) + error("hook cannot have both 'foreach' and '%error'", n); + + // Ensure we only have one foreach or one %error + int foreach_count = 0; + int err_count = 0; + if ( auto attrs = n->attributes() ) { + for ( const auto& attr : attrs->attributes() ) { + if ( attr->kind() == hilti::Attribute::Kind::Foreach ) + foreach_count++; + else if ( attr->kind() == hilti::Attribute::Kind::Error ) + err_count++; } - else - error(fmt("unknown hook type '%s'", a->attributeName()), n); } - if ( count > 1 ) - error("hook cannot have be both types, 'foreach' and '%error'", n); + if ( foreach_count > 1 ) + error("hook can only have one 'foreach'", n); + + if ( err_count > 1 ) + error("hook can only have one '%error'", n); } void operator()(spicy::type::unit::item::UnitHook* n) final { @@ -660,6 +766,8 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { } void operator()(spicy::type::Unit* n) final { + checkNodeAttributes(n->nodeTag(), n->attributes(), "unit type"); + if ( auto attrs = n->attributes() ) { if ( attrs->has(hilti::Attribute::Kind::Size) && attrs->has(hilti::Attribute::Kind::MaxSize) ) error(("attributes cannot be combined: &size, &max-size"), n); @@ -710,8 +818,6 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { if ( ! a->hasValue() ) error("&convert must provide an expression", n); } - else - error(fmt("attribute %s not supported for unit types", a->attributeName()), n); } } @@ -769,35 +875,27 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { error("units cannot be compared with !=", n); } - // Check attributes for items containing sequence of sub-items (Block, Switch). - void checkAttributesForAggregateItem(spicy::type::unit::Item* n, AttributeSet* attrs) { - if ( ! attrs ) - return; - - for ( const auto& attr : attrs->attributes() ) { - if ( ! hilti::Attribute::isOneOf(attr->kind(), - {hilti::Attribute::Kind::Size, hilti::Attribute::Kind::ParseAt, - hilti::Attribute::Kind::ParseFrom}) ) - error(fmt("attribute '%s' is not supported here", attr->attributeName()), n); - } - } - void operator()(spicy::type::unit::item::Block* n) final { + checkNodeAttributes(n->nodeTag(), n->attributes(), "unit block"); + if ( n->condition() && ! n->condition()->type()->type()->isA() ) error("block condition must be of type bool", n); - - checkAttributesForAggregateItem(n, n->attributes()); } void operator()(spicy::type::unit::item::Field* n) final { - const auto count_attr = n->attributes()->find(hilti::Attribute::Kind::Count); - const auto repeat = n->repeatCount(); + checkNodeAttributes(n->nodeTag(), n->attributes(), "field"); + + auto type = n->parseType()->type(); + validateFieldTypeAttributes(type->nodeTag(), n->attributes(), type->typeClass()); if ( n->isSkip() ) { if ( ! n->sinks().empty() ) error("skip field cannot have sinks attached", n); } + + const auto count_attr = n->attributes()->find(hilti::Attribute::Kind::Count); + const auto repeat = n->repeatCount(); if ( count_attr && (repeat && ! repeat->type()->type()->isA()) ) error("cannot have both '[..]' and &count", n); @@ -811,7 +909,7 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { "removed in future versions", n->meta().location()); - if ( n->sinks().size() && ! n->parseType()->type()->isA() ) + if ( n->sinks().size() && ! type->isA() ) error("only a bytes field can have sinks attached", n); for ( auto* s : n->sinks() ) { @@ -901,6 +999,8 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { } void operator()(spicy::type::unit::item::Switch* n) final { + checkNodeAttributes(n->nodeTag(), n->attributes(), "unit switch"); + if ( n->cases().empty() ) { error("switch without cases", n); return; @@ -948,7 +1048,7 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { } } - if ( f->attributes()->find(hilti::Attribute::Kind::Synchronize) ) + if ( f->attributes()->has(hilti::Attribute::Kind::Synchronize) ) error(fmt("unit switch branches cannot be &synchronize"), n); seen_fields.emplace_back(f); @@ -958,21 +1058,14 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { if ( defaults > 1 ) error("more than one default case", n); - - checkAttributesForAggregateItem(n, n->attributes()); } void operator()(spicy::type::unit::item::Variable* n) final { + checkNodeAttributes(n->nodeTag(), n->attributes(), "unit variable"); + if ( ! n->parent()->isA() ) error("unit variables must be declared at the top-level of a unit", n); - if ( auto attrs = n->attributes() ) { - for ( const auto& attr : attrs->attributes() ) { - if ( attr->kind() != hilti::Attribute::Kind::Optional ) - error(fmt("attribute '%s' not supported for unit variables", attr->attributeName()), n); - } - } - if ( n->itemType()->type()->isA() ) error( "cannot use type 'sink' for unit variables; use either a 'sink' item or a reference to a sink " @@ -980,6 +1073,10 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn { n); } + void operator()(spicy::type::unit::item::Sink* n) final { + checkNodeAttributes(n->nodeTag(), n->attributes(), "unit sink"); + } + void operator()(spicy::declaration::UnitHook* n) final { if ( auto t = builder()->context()->lookup(n->hook()->unitTypeIndex()) ) { auto ut = t->as(); diff --git a/tests/Baseline/hilti.types.function.param-attrs-fail/output b/tests/Baseline/hilti.types.function.param-attrs-fail/output index d096dd06d..52241ed1c 100644 --- a/tests/Baseline/hilti.types.function.param-attrs-fail/output +++ b/tests/Baseline/hilti.types.function.param-attrs-fail/output @@ -1,4 +1,5 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. [error] <...>/param-attrs-fail.hlt:8:18-8:48: attribute '&requires-type-feature' requires a string [error] <...>/param-attrs-fail.hlt:9:18-9:50: value for attribute '&requires-type-feature' must be a string +[error] <...>/param-attrs-fail.hlt:10:27-10:33: invalid attribute '&size' in parameter [error] hiltic: aborting after errors diff --git a/tests/Baseline/spicy.types.unit.attr-multiple-fail/output b/tests/Baseline/spicy.types.unit.attr-multiple-fail/output index 58b2a3902..c844a84c6 100644 --- a/tests/Baseline/spicy.types.unit.attr-multiple-fail/output +++ b/tests/Baseline/spicy.types.unit.attr-multiple-fail/output @@ -8,8 +8,6 @@ [error] <...>/attr-multiple-fail.spicy:21:5-21:89: '&type' can be used at most once [error] <...>/attr-multiple-fail.spicy:23:5-23:44: '&until' can be used at most once [error] <...>/attr-multiple-fail.spicy:24:5-24:64: '&until-including' can be used at most once -[error] <...>/attr-multiple-fail.spicy:25:5-25:45: '&while' can be used at most once +[error] <...>/attr-multiple-fail.spicy:27:11-27:38: attributes cannot be combined: &size, &max-size [error] <...>/attr-multiple-fail.spicy:28:11-28:38: attributes cannot be combined: &size, &max-size -[error] <...>/attr-multiple-fail.spicy:29:11-29:38: attributes cannot be combined: &size, &max-size -[error] <...>/attr-multiple-fail.spicy:33:5-33:27: field cannot have both &ipv4 and &ipv6 attributes [error] spicyc: aborting after errors diff --git a/tests/Baseline/spicy.types.unit.attr-type-fail/output b/tests/Baseline/spicy.types.unit.attr-type-fail/output new file mode 100644 index 000000000..25f989f87 --- /dev/null +++ b/tests/Baseline/spicy.types.unit.attr-type-fail/output @@ -0,0 +1,10 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +[warning] <...>/attr-type-fail.spicy:16:5-16:19: using `void` fields with attributes is deprecated and support will be removed in a future release; replace 'void ...' with 'skip bytes ...' +[error] <...>/attr-type-fail.spicy:11:15-11:19: invalid attribute '&ipv4' for field with type 'uint' +[error] <...>/attr-type-fail.spicy:12:15-12:19: invalid attribute '&ipv6' for field with type 'uint' +[error] <...>/attr-type-fail.spicy:13:15-13:19: invalid attribute '&ipv4' for field with type 'uint' +[error] <...>/attr-type-fail.spicy:13:21-13:25: invalid attribute '&ipv6' for field with type 'uint' +[error] <...>/attr-type-fail.spicy:14:15-14:51: invalid attribute '&type' for field with type 'bytes' +[error] <...>/attr-type-fail.spicy:15:15-15:50: invalid attribute '&byte-order' for field with type 'bytes' +[error] <...>/attr-type-fail.spicy:16:14-16:18: invalid attribute '&ipv4' for field with type 'bytes' +[error] spicyc: aborting after errors diff --git a/tests/Baseline/spicy.types.unit.error-hook-field-fail/output b/tests/Baseline/spicy.types.unit.error-hook-field-fail/output index 94e681f87..23128bb32 100644 --- a/tests/Baseline/spicy.types.unit.error-hook-field-fail/output +++ b/tests/Baseline/spicy.types.unit.error-hook-field-fail/output @@ -1,7 +1,14 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. -[error] <...>/error-hook-field-fail.spicy:8:16-8:32: hook cannot have be both types, 'foreach' and '%error' -[error] <...>/error-hook-field-fail.spicy:10:10-10:26: hook cannot have be both types, 'foreach' and '%error' +[error] <...>/error-hook-field-fail.spicy:8:16-8:32: hook cannot have both 'foreach' and '%error' +[error] <...>/error-hook-field-fail.spicy:10:10-10:26: hook cannot have both 'foreach' and '%error' [error] <...>/error-hook-field-fail.spicy:13:12-14:1: %error hook must only take a string parameter [error] <...>/error-hook-field-fail.spicy:16:12-17:1: %error hook must only take a string parameter -[error] <...>/error-hook-field-fail.spicy:19:13-20:1: hook cannot have be both types, 'foreach' and '%error' +[error] <...>/error-hook-field-fail.spicy:19:13-20:1: hook cannot have both 'foreach' and '%error' +[error] <...>/error-hook-field-fail.spicy:23:17-23:32: hook can only have one '%error' +[error] <...>/error-hook-field-fail.spicy:24:17-24:34: hook can only have one 'foreach' +[error] <...>/error-hook-field-fail.spicy:25:17-25:40: hook cannot have both 'foreach' and '%error' +[error] <...>/error-hook-field-fail.spicy:25:17-25:40: hook can only have one '%error' +[error] <...>/error-hook-field-fail.spicy:26:17-26:48: hook cannot have both 'foreach' and '%error' +[error] <...>/error-hook-field-fail.spicy:26:17-26:48: hook can only have one 'foreach' +[error] <...>/error-hook-field-fail.spicy:26:17-26:48: hook can only have one '%error' [error] spicyc: aborting after errors diff --git a/tests/Baseline/spicy.types.unit.switch-attributes-fail/output b/tests/Baseline/spicy.types.unit.switch-attributes-fail/output index 564636c90..c34d6caea 100644 --- a/tests/Baseline/spicy.types.unit.switch-attributes-fail/output +++ b/tests/Baseline/spicy.types.unit.switch-attributes-fail/output @@ -1,4 +1,4 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. -[error] <...>/switch-attributes-fail.spicy:9:5-11:31: attribute '&eod' is not supported here -[error] <...>/switch-attributes-fail.spicy:9:5-11:31: attribute '&until' is not supported here +[error] <...>/switch-attributes-fail.spicy:11:7-11:10: invalid attribute '&eod' in unit switch +[error] <...>/switch-attributes-fail.spicy:11:20-11:30: invalid attribute '&until' in unit switch [error] spicyc: aborting after errors diff --git a/tests/Baseline/spicy.types.unit.unsupported-attribute/output b/tests/Baseline/spicy.types.unit.unsupported-attribute/output index f560238d8..3cbe17a14 100644 --- a/tests/Baseline/spicy.types.unit.unsupported-attribute/output +++ b/tests/Baseline/spicy.types.unit.unsupported-attribute/output @@ -1,3 +1,3 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. -[error] <...>/unsupported-attribute.spicy:8:12-10:10: attribute &no-emit not supported for unit types +[error] <...>/unsupported-attribute.spicy:10:3-10:10: invalid attribute '&no-emit' in unit type [error] spicyc: aborting after errors diff --git a/tests/Baseline/spicy.types.unit.variable-attributes/output b/tests/Baseline/spicy.types.unit.variable-attributes/output index 38592ca06..160b6c75d 100644 --- a/tests/Baseline/spicy.types.unit.variable-attributes/output +++ b/tests/Baseline/spicy.types.unit.variable-attributes/output @@ -1,3 +1,3 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. -[error] <...>/variable-attributes.spicy:9:5-9:39: attribute '&default' not supported for unit variables +[error] <...>/variable-attributes.spicy:9:28-9:38: invalid attribute '&default' in unit variable [error] spicyc: aborting after errors diff --git a/tests/hilti/types/function/param-attrs-fail.hlt b/tests/hilti/types/function/param-attrs-fail.hlt index 164666c04..89e60ede7 100644 --- a/tests/hilti/types/function/param-attrs-fail.hlt +++ b/tests/hilti/types/function/param-attrs-fail.hlt @@ -7,5 +7,6 @@ module Foo { function void f1(string s &requires-type-feature) {} function void f2(string s &requires-type-feature=1) {} +function void f3(string s &size=1) {} } diff --git a/tests/spicy/types/unit/attr-multiple-fail.spicy b/tests/spicy/types/unit/attr-multiple-fail.spicy index be2613cbf..6c52eae21 100644 --- a/tests/spicy/types/unit/attr-multiple-fail.spicy +++ b/tests/spicy/types/unit/attr-multiple-fail.spicy @@ -22,13 +22,8 @@ public type X = unit { n8: bytes &eod &until=True &until=False; n9: bytes &eod &until-including=True &until-including=False; - n10: bytes &eod &while=True &while=False; # Regex with an inner field - n11: (/a{1,3}/ &max-size=4 &size=3); - n12: (/a{1,3}/ &max-size=4 &size=3)[]; - - # While it doesn't make sense to use these attributes on a uint8, they should still - # fail just because the attributes are invalid. - n13: uint8 &ipv4 &ipv6; + n10: (/a{1,3}/ &max-size=4 &size=3); + n11: (/a{1,3}/ &max-size=4 &size=3)[]; } &byte-order=spicy::ByteOrder::Little &byte-order=spicy::ByteOrder::Big; diff --git a/tests/spicy/types/unit/attr-type-fail.spicy b/tests/spicy/types/unit/attr-type-fail.spicy new file mode 100644 index 000000000..688417db5 --- /dev/null +++ b/tests/spicy/types/unit/attr-type-fail.spicy @@ -0,0 +1,17 @@ +# @TEST-DOC: Checks that only certain types can get certain attributes, otherwise there is an error +# +# @TEST-EXEC-FAIL: spicyc -d -p %INPUT >output 2>&1 +# @TEST-EXEC: btest-diff output +# +module foo; + +import spicy; + +public type X = unit { + n1: uint8 &ipv4; + n2: uint8 &ipv6; + n3: uint8 &ipv4 &ipv6; + n4: bytes &type=spicy::RealType::IEEE754_Single &size=1; + n5: bytes &byte-order=spicy::ByteOrder::Little; + n6: void &ipv4; +}; diff --git a/tests/spicy/types/unit/error-hook-field-fail.spicy b/tests/spicy/types/unit/error-hook-field-fail.spicy index eac42d9bb..05ce86484 100644 --- a/tests/spicy/types/unit/error-hook-field-fail.spicy +++ b/tests/spicy/types/unit/error-hook-field-fail.spicy @@ -18,3 +18,10 @@ on Test1::x(msg: string, x: bool) %error { # wrong parameter number on Test1::x %error foreach { # cannot use both } + +public type Multiple = unit { + x1: uint8[] %error %error {} + x2: uint8[] foreach foreach {} + x3: uint8[] %error %error foreach {} + x4: uint8[] %error %error foreach foreach {} +};