Skip to content

Commit

Permalink
bindings: Comprehensive checks for IDL extended attributes
Browse files Browse the repository at this point in the history
Introduces the syntax checks for IDL extended attributes.
- Spelling
- Applied targets (attribute, operation, etc.)
- Format of extended attributes (no arg, single arg, etc.)
will be checked in order to prevent misuse of IDL extended
attributes. Finer checks can be added later.

Change-Id: I6e66471ea21066112033d4a3c1c7195bc9f003ae
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4546828
Commit-Queue: Yuki Shiino <yukishiino@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1150389}
  • Loading branch information
yuki3 authored and Chromium LUCI CQ committed May 30, 2023
1 parent 71cd839 commit 51513d9
Show file tree
Hide file tree
Showing 5 changed files with 359 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ validator/framework/target_store.py
validator/framework/target_type.py
validator/framework/validator.py
validator/rules/__init__.py
validator/rules/extended_attribute_descriptor.py
validator/rules/rules_attributes.py
validator/rules/rules_constants.py
validator/rules/rules_dictionaries.py
validator/rules/rules_extended_attributes.py
validator/rules/rules_function_like.py
validator/rules/supported_extended_attributes.py
web_idl/__init__.py
web_idl/argument.py
web_idl/ast_group.py
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Copyright 2023 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import enum

import web_idl


class ExtendedAttributeDescriptor(object):
class Target(enum.Enum):
ATTRIBUTE = web_idl.Attribute
CALLBACK_FUNCTION = web_idl.CallbackFunction
CALLBACK_INTERFACE = web_idl.CallbackInterface
CONSTANT = web_idl.Constant
CONSTRUCTOR = web_idl.Constructor
DICTIONARY = web_idl.Dictionary
DICTIONARY_MEMBER = web_idl.DictionaryMember
INTERFACE = web_idl.Interface
LEGACY_WINDOW_ALIAS = web_idl.LegacyWindowAlias
NAMESPACE = web_idl.Namespace
OPERATION = web_idl.Operation
TYPE = web_idl.IdlType

class Form(enum.Enum):
# https://webidl.spec.whatwg.org/#idl-extended-attributes
NO_ARGS = enum.auto() # [ExtAttr]
IDENT = enum.auto() # [ExtAttr=Value]
IDENT_LIST = enum.auto() # [ExtAttr=(Value1, ...)]
ARG_LIST = enum.auto() # [ExtAttr(V1L V1R, ...)]
NAMED_ARG_LIST = enum.auto() # [ExtAttr=Name(V1L V1R, ...)]

def __init__(self,
name,
applicable_to=None,
forms=None,
values=None,
post_validate=None):
assert isinstance(name, str)
assert isinstance(applicable_to, list) and all(
isinstance(target, ExtendedAttributeDescriptor.Target)
for target in applicable_to)
assert forms is None or isinstance(
forms, ExtendedAttributeDescriptor.Form) or (isinstance(
forms, list) and all(
isinstance(form, ExtendedAttributeDescriptor.Form)
for form in forms))
assert values is None or (isinstance(values, list) and all(
isinstance(value, str) for value in values))
assert post_validate is None or callable(post_validate)

self._name = name
# self._applicable_to is a list of valid target object's types, e.g.
# web_idl.Attribute, web_idl.Constant, etc.
self._applicable_to = tuple(map(lambda e: e.value, applicable_to))
# self._forms is a list of valid forms.
if forms is None:
self._forms = [ExtendedAttributeDescriptor.Form.NO_ARGS]
elif not isinstance(forms, list):
self._forms = [forms]
else:
self._forms = forms
# self._values is a list of valid "ident" values
if values is None:
self._values = None
else:
assert (ExtendedAttributeDescriptor.Form.IDENT in self._forms or
ExtendedAttributeDescriptor.Form.IDENT_LIST in self._forms)
self._values = values
# self._post_validate is a callable or None.
self._post_validate = post_validate

@property
def name(self):
return self._name

def validate(self, assert_, target_object, ext_attr):
T = ExtendedAttributeDescriptor.Target
F = ExtendedAttributeDescriptor.Form

failure_count = [0]

def _assert(condition, text, *args, **kwargs):
if not condition:
failure_count[0] = failure_count[0] + 1
assert_(condition, text, *args, **kwargs)

# applicable_to
_assert(isinstance(target_object, self._applicable_to),
"[{}] is not applicable to {}.", self._name,
target_object.__class__.__name__)

# forms
if ext_attr.has_values:
if not ext_attr.values:
_assert(F.NO_ARGS in self._forms,
"[{}] needs an identifier or an argument list.",
self._name)
elif F.IDENT_LIST in self._forms:
pass
elif F.IDENT in self._forms:
_assert(
len(ext_attr.values) == 1,
"[{}] doesn't take an identifier list.", self._name)
elif F.ARG_LIST in self._forms or F.NAMED_ARG_LIST in self._forms:
_assert(False, "[{}] needs an argument list.", self._name)
else: # F.NO_ARGS only
_assert(False, "[{}] doesn't take an identifier.", self._name)
if ext_attr.has_arguments:
_assert(
F.ARG_LIST in self._forms or F.NAMED_ARG_LIST in self._forms,
"[{}] doesn't take an argument list.", self._name)
if ext_attr.has_name:
_assert(F.NAMED_ARG_LIST in self._forms,
"[{}] doesn't take an named argument list.", self._name)

# values
if self._values:
for value in ext_attr.values:
_assert(value in self._values, "[{}={}] is not supported.",
self._name, value)

# post_validate
if self._post_validate:
if failure_count[0] == 0:
self._post_validate(assert_, target_object, ext_attr)
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,26 @@
Each rule class must inherit RuleBase.
"""

from validator.framework import target
from . import supported_extended_attributes
from validator.framework import RuleBase
from validator.framework import target


_web_idl_extended_attributes_applicable_to_types = [
"AllowShared",
"Clamp",
"EnforceRange",
"LegacyNullToEmptyString",
]
_blink_specific_extended_attributes_applicable_to_types = [
"BufferSourceTypeNoSizeLimit",
"FlexibleArrayBufferView",
"StringContext",
# "TreatNullAs" is the old version of "LegacyNullToEmptyString".
"TreatNullAs",
]
_extended_attributes_applicable_to_types = (
_web_idl_extended_attributes_applicable_to_types +
_blink_specific_extended_attributes_applicable_to_types)


class ExtendedAttributesApplicableToTypes(RuleBase):
class ExtendedAttributesOnNonType(RuleBase):
def validate(self, assert_, target_object):
web_idl_link = "https://webidl.spec.whatwg.org/#extended-attributes-applicable-to-types"
for extended_attribute in target_object.extended_attributes.keys():
assert_(
extended_attribute not in
_extended_attributes_applicable_to_types,
("Extended attribute '{}' is applicable to types, "
"but applied in the wrong context. See {}"),
extended_attribute, web_idl_link)
for ext_attr in target_object.extended_attributes:
supported_extended_attributes.validate(assert_, target_object,
ext_attr)


class ExtendedAttributesApplicableToTypesForIdlType(RuleBase):
class ExtendedAttributesOnType(RuleBase):
def validate(self, assert_, target_object):
web_idl_link = "https://webidl.spec.whatwg.org/#extended-attributes-applicable-to-types"
for annotation in target_object.effective_annotations:
assert_(annotation.key in _extended_attributes_applicable_to_types,
("Extended attribute '{}' is not applicable to types, "
"but applied to a type. See {}"), annotation.key,
web_idl_link)
for ext_attr in target_object.effective_annotations:
supported_extended_attributes.validate(assert_, target_object,
ext_attr)


def register_rules(rule_store):
rule_store.register(target.OBJECTS_WITH_EXTENDED_ATTRIBUTES,
ExtendedAttributesApplicableToTypes())
rule_store.register(target.IDL_TYPES,
ExtendedAttributesApplicableToTypesForIdlType())
ExtendedAttributesOnNonType())
rule_store.register(target.IDL_TYPES, ExtendedAttributesOnType())

0 comments on commit 51513d9

Please sign in to comment.