Skip to content

Commit 2d0ce53

Browse files
committed
Merge remote-tracking branch 'origin/topic/etyp/attribute-consistent-validation'
(Merge commit includes some small additional tweaks to style and code placement.) * origin/topic/etyp/attribute-consistent-validation: Add consistent validation for attributes.
2 parents d59f245 + 9ad62e5 commit 2d0ce53

File tree

16 files changed

+272
-81
lines changed

16 files changed

+272
-81
lines changed

CHANGES

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
1.12.0-dev.242 | 2024-12-13 09:26:36 +0100
2+
3+
* GH-1901: Add consistent validation for attributes. (Evan Typanski, Corelight)
4+
5+
This updates attribute validation to see if attributes are in the
6+
right places. The goal here is to have one place (ie a big set) to
7+
answer the question "Can I use X attribute on Y node?"
8+
9+
There are a lot more moving parts with attribute validation, but those
10+
generally have to do with behavior. A lot of that requires extra context
11+
in the validator, which is exactly what the validator is meant to do.
12+
Much of that is pretty ad-hoc, so it could get cleaned up as well.
13+
114
1.12.0-dev.240 | 2024-12-12 13:09:05 +0100
215

316
* Make sure autogen-docs pre-commit hook can always run in CI. (Benjamin Bannier, Corelight)

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.12.0-dev.240
1+
1.12.0-dev.242

hilti/toolchain/include/ast/node.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,15 @@ class Node {
240240
virtual ~Node();
241241

242242
/** Returns the node tag associated with the instance's class. */
243-
auto nodeTag() const { return _node_tags.back(); }
243+
node::Tag nodeTag() const {
244+
// Get the last non-zero tag. The last element(s) may be unset
245+
for ( auto it = _node_tags.rbegin(); it != _node_tags.rend(); it++ ) {
246+
if ( *it != 0 )
247+
return *it;
248+
}
249+
250+
return 0;
251+
}
244252

245253
/** Returns true if the node has a parent (i.e., it's part of an AST). */
246254
bool hasParent() const { return _parent; }

hilti/toolchain/src/compiler/validator.cc

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@
6060
using namespace hilti;
6161
using util::fmt;
6262

63+
/**
64+
* A mapping of node tags to any attributes that node allows. When a new
65+
* attribute is added, this map must be updated to accept that attribute on any
66+
* nodes it applies to.
67+
*/
68+
static std::unordered_map<node::Tag, std::unordered_set<Attribute::Kind>> allowed_attributes{
69+
{node::tag::Function,
70+
{Attribute::Kind::Cxxname, Attribute::Kind::HavePrototype, Attribute::Kind::Priority, Attribute::Kind::Static,
71+
Attribute::Kind::NeededByFeature, Attribute::Kind::Debug, Attribute::Kind::Foreach, Attribute::Kind::Error}},
72+
{node::tag::declaration::Parameter, {Attribute::Kind::RequiresTypeFeature}},
73+
};
74+
6375
void hilti::validator::VisitorMixIn::deprecated(const std::string& msg, const Location& l) const {
6476
hilti::logger().deprecated(msg, l);
6577
}
@@ -124,6 +136,30 @@ struct VisitorPre : visitor::PreOrder, public validator::VisitorMixIn {
124136
struct VisitorPost : visitor::PreOrder, public validator::VisitorMixIn {
125137
using hilti::validator::VisitorMixIn::VisitorMixIn;
126138

139+
// Ensures that the node represented by tag is allowed to have all of the
140+
// provided attributes. This does not use any context, if more information
141+
// is needed, then do the check elsewhere.
142+
void checkNodeAttributes(node::Tag tag, AttributeSet* attributes, const std::string_view& where) {
143+
if ( ! attributes )
144+
return;
145+
146+
auto it = allowed_attributes.find(tag);
147+
148+
if ( it == allowed_attributes.end() ) {
149+
if ( ! attributes->attributes().empty() )
150+
error(hilti::util::fmt("No attributes expected in %s", where), attributes);
151+
152+
return;
153+
}
154+
155+
auto allowed = it->second;
156+
157+
for ( const auto& attr : attributes->attributes() ) {
158+
if ( allowed.find(attr->kind()) == allowed.end() )
159+
error(hilti::util::fmt("invalid attribute '%s' in %s", attr->attributeName(), where), attr);
160+
}
161+
}
162+
127163
// Returns an error if the given type cannot be used for ordering at
128164
// runtime.
129165
Result<Nothing> isSortable(QualifiedType* t) {
@@ -176,6 +212,8 @@ struct VisitorPost : visitor::PreOrder, public validator::VisitorMixIn {
176212
}
177213

178214
void operator()(Function* n) final {
215+
checkNodeAttributes(n->nodeTag(), n->attributes(), "function");
216+
179217
if ( auto attrs = n->attributes() ) {
180218
if ( auto prio = attrs->find(hilti::Attribute::Kind::Priority) ) {
181219
if ( n->ftype()->flavor() != type::function::Flavor::Hook )
@@ -260,6 +298,8 @@ struct VisitorPost : visitor::PreOrder, public validator::VisitorMixIn {
260298
}
261299

262300
void operator()(declaration::Parameter* n) final {
301+
checkNodeAttributes(n->nodeTag(), n->attributes(), n->displayName());
302+
263303
if ( ! n->type()->type()->isA<type::Auto>() ) {
264304
if ( ! n->type()->type()->isAllocable() && ! n->type()->type()->isA<type::Any>() )
265305
error(fmt("type '%s' cannot be used for function parameter", *n->type()), n);
@@ -285,10 +325,7 @@ struct VisitorPost : visitor::PreOrder, public validator::VisitorMixIn {
285325

286326
if ( auto attrs = n->attributes() )
287327
for ( const auto& attr : attrs->attributes() ) {
288-
if ( attr->kind() != hilti::Attribute::Kind::RequiresTypeFeature )
289-
error(fmt("invalid attribute '%s' for function parameter", attr->attributeName()), n);
290-
291-
else {
328+
if ( attr->kind() == hilti::Attribute::Kind::RequiresTypeFeature ) {
292329
if ( auto x = attr->valueAsString(); ! x )
293330
error(x.error(), n);
294331
}

0 commit comments

Comments
 (0)