1*99e0aae7SDavid Rees# Copyright 2019 Google LLC 2*99e0aae7SDavid Rees# 3*99e0aae7SDavid Rees# Licensed under the Apache License, Version 2.0 (the "License"); 4*99e0aae7SDavid Rees# you may not use this file except in compliance with the License. 5*99e0aae7SDavid Rees# You may obtain a copy of the License at 6*99e0aae7SDavid Rees# 7*99e0aae7SDavid Rees# https://www.apache.org/licenses/LICENSE-2.0 8*99e0aae7SDavid Rees# 9*99e0aae7SDavid Rees# Unless required by applicable law or agreed to in writing, software 10*99e0aae7SDavid Rees# distributed under the License is distributed on an "AS IS" BASIS, 11*99e0aae7SDavid Rees# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*99e0aae7SDavid Rees# See the License for the specific language governing permissions and 13*99e0aae7SDavid Rees# limitations under the License. 14*99e0aae7SDavid Rees 15*99e0aae7SDavid Rees"""Module which verifies attributes in an Emboss IR. 16*99e0aae7SDavid Rees 17*99e0aae7SDavid ReesThe main entry point is check_attributes_in_ir(), which checks attributes in an 18*99e0aae7SDavid ReesIR. 19*99e0aae7SDavid Rees""" 20*99e0aae7SDavid Rees 21*99e0aae7SDavid Reesfrom compiler.util import error 22*99e0aae7SDavid Reesfrom compiler.util import ir_data 23*99e0aae7SDavid Reesfrom compiler.util import ir_data_utils 24*99e0aae7SDavid Reesfrom compiler.util import ir_util 25*99e0aae7SDavid Reesfrom compiler.util import traverse_ir 26*99e0aae7SDavid Rees 27*99e0aae7SDavid Rees 28*99e0aae7SDavid Rees# Error messages used by multiple attribute type checkers. 29*99e0aae7SDavid Rees_BAD_TYPE_MESSAGE = "Attribute '{name}' must have {type} value." 30*99e0aae7SDavid Rees_MUST_BE_CONSTANT_MESSAGE = "Attribute '{name}' must have a constant value." 31*99e0aae7SDavid Rees 32*99e0aae7SDavid Rees 33*99e0aae7SDavid Reesdef _attribute_name_for_errors(attr): 34*99e0aae7SDavid Rees if ir_data_utils.reader(attr).back_end.text: 35*99e0aae7SDavid Rees return f"({attr.back_end.text}) {attr.name.text}" 36*99e0aae7SDavid Rees else: 37*99e0aae7SDavid Rees return attr.name.text 38*99e0aae7SDavid Rees 39*99e0aae7SDavid Rees 40*99e0aae7SDavid Rees# Attribute type checkers 41*99e0aae7SDavid Reesdef _is_constant_boolean(attr, module_source_file): 42*99e0aae7SDavid Rees """Checks if the given attr is a constant boolean.""" 43*99e0aae7SDavid Rees if not attr.value.expression.type.boolean.HasField("value"): 44*99e0aae7SDavid Rees return [[error.error(module_source_file, 45*99e0aae7SDavid Rees attr.value.source_location, 46*99e0aae7SDavid Rees _BAD_TYPE_MESSAGE.format( 47*99e0aae7SDavid Rees name=_attribute_name_for_errors(attr), 48*99e0aae7SDavid Rees type="a constant boolean"))]] 49*99e0aae7SDavid Rees return [] 50*99e0aae7SDavid Rees 51*99e0aae7SDavid Rees 52*99e0aae7SDavid Reesdef _is_boolean(attr, module_source_file): 53*99e0aae7SDavid Rees """Checks if the given attr is a boolean.""" 54*99e0aae7SDavid Rees if attr.value.expression.type.WhichOneof("type") != "boolean": 55*99e0aae7SDavid Rees return [[error.error(module_source_file, 56*99e0aae7SDavid Rees attr.value.source_location, 57*99e0aae7SDavid Rees _BAD_TYPE_MESSAGE.format( 58*99e0aae7SDavid Rees name=_attribute_name_for_errors(attr), 59*99e0aae7SDavid Rees type="a boolean"))]] 60*99e0aae7SDavid Rees return [] 61*99e0aae7SDavid Rees 62*99e0aae7SDavid Rees 63*99e0aae7SDavid Reesdef _is_constant_integer(attr, module_source_file): 64*99e0aae7SDavid Rees """Checks if the given attr is an integer constant expression.""" 65*99e0aae7SDavid Rees if (not attr.value.HasField("expression") or 66*99e0aae7SDavid Rees attr.value.expression.type.WhichOneof("type") != "integer"): 67*99e0aae7SDavid Rees return [[error.error(module_source_file, 68*99e0aae7SDavid Rees attr.value.source_location, 69*99e0aae7SDavid Rees _BAD_TYPE_MESSAGE.format( 70*99e0aae7SDavid Rees name=_attribute_name_for_errors(attr), 71*99e0aae7SDavid Rees type="an integer"))]] 72*99e0aae7SDavid Rees if not ir_util.is_constant(attr.value.expression): 73*99e0aae7SDavid Rees return [[error.error(module_source_file, 74*99e0aae7SDavid Rees attr.value.source_location, 75*99e0aae7SDavid Rees _MUST_BE_CONSTANT_MESSAGE.format( 76*99e0aae7SDavid Rees name=_attribute_name_for_errors(attr)))]] 77*99e0aae7SDavid Rees return [] 78*99e0aae7SDavid Rees 79*99e0aae7SDavid Rees 80*99e0aae7SDavid Reesdef _is_string(attr, module_source_file): 81*99e0aae7SDavid Rees """Checks if the given attr is a string.""" 82*99e0aae7SDavid Rees if not attr.value.HasField("string_constant"): 83*99e0aae7SDavid Rees return [[error.error(module_source_file, 84*99e0aae7SDavid Rees attr.value.source_location, 85*99e0aae7SDavid Rees _BAD_TYPE_MESSAGE.format( 86*99e0aae7SDavid Rees name=_attribute_name_for_errors(attr), 87*99e0aae7SDavid Rees type="a string"))]] 88*99e0aae7SDavid Rees return [] 89*99e0aae7SDavid Rees 90*99e0aae7SDavid Rees 91*99e0aae7SDavid Rees# Provide more readable names for these functions when used in attribute type 92*99e0aae7SDavid Rees# specifiers. 93*99e0aae7SDavid ReesBOOLEAN_CONSTANT = _is_constant_boolean 94*99e0aae7SDavid ReesBOOLEAN = _is_boolean 95*99e0aae7SDavid ReesINTEGER_CONSTANT = _is_constant_integer 96*99e0aae7SDavid ReesSTRING = _is_string 97*99e0aae7SDavid Rees 98*99e0aae7SDavid Rees 99*99e0aae7SDavid Reesdef string_from_list(valid_values): 100*99e0aae7SDavid Rees """Checks if the given attr has one of the valid_values.""" 101*99e0aae7SDavid Rees def _string_from_list(attr, module_source_file): 102*99e0aae7SDavid Rees if ir_data_utils.reader(attr).value.string_constant.text not in valid_values: 103*99e0aae7SDavid Rees return [[error.error(module_source_file, 104*99e0aae7SDavid Rees attr.value.source_location, 105*99e0aae7SDavid Rees "Attribute '{name}' must be '{options}'.".format( 106*99e0aae7SDavid Rees name=_attribute_name_for_errors(attr), 107*99e0aae7SDavid Rees options="' or '".join(sorted(valid_values))))]] 108*99e0aae7SDavid Rees return [] 109*99e0aae7SDavid Rees return _string_from_list 110*99e0aae7SDavid Rees 111*99e0aae7SDavid Rees 112*99e0aae7SDavid Reesdef check_attributes_in_ir(ir, 113*99e0aae7SDavid Rees *, 114*99e0aae7SDavid Rees back_end=None, 115*99e0aae7SDavid Rees types=None, 116*99e0aae7SDavid Rees module_attributes=None, 117*99e0aae7SDavid Rees struct_attributes=None, 118*99e0aae7SDavid Rees bits_attributes=None, 119*99e0aae7SDavid Rees enum_attributes=None, 120*99e0aae7SDavid Rees enum_value_attributes=None, 121*99e0aae7SDavid Rees external_attributes=None, 122*99e0aae7SDavid Rees structure_virtual_field_attributes=None, 123*99e0aae7SDavid Rees structure_physical_field_attributes=None): 124*99e0aae7SDavid Rees """Performs basic checks on all attributes in the given ir. 125*99e0aae7SDavid Rees 126*99e0aae7SDavid Rees This function calls _check_attributes on each attribute list in ir. 127*99e0aae7SDavid Rees 128*99e0aae7SDavid Rees Arguments: 129*99e0aae7SDavid Rees ir: An ir_data.EmbossIr to check. 130*99e0aae7SDavid Rees back_end: A string specifying the attribute qualifier to check (such as 131*99e0aae7SDavid Rees `cpp` for `[(cpp) namespace = "foo"]`), or None to check unqualified 132*99e0aae7SDavid Rees attributes. 133*99e0aae7SDavid Rees 134*99e0aae7SDavid Rees Attributes with a different qualifier will not be checked. 135*99e0aae7SDavid Rees types: A map from attribute names to validators, such as: 136*99e0aae7SDavid Rees { 137*99e0aae7SDavid Rees "maximum_bits": attribute_util.INTEGER_CONSTANT, 138*99e0aae7SDavid Rees "requires": attribute_util.BOOLEAN, 139*99e0aae7SDavid Rees } 140*99e0aae7SDavid Rees module_attributes: A set of (attribute_name, is_default) tuples specifying 141*99e0aae7SDavid Rees the attributes that are allowed at module scope. 142*99e0aae7SDavid Rees struct_attributes: A set of (attribute_name, is_default) tuples specifying 143*99e0aae7SDavid Rees the attributes that are allowed at `struct` scope. 144*99e0aae7SDavid Rees bits_attributes: A set of (attribute_name, is_default) tuples specifying 145*99e0aae7SDavid Rees the attributes that are allowed at `bits` scope. 146*99e0aae7SDavid Rees enum_attributes: A set of (attribute_name, is_default) tuples specifying 147*99e0aae7SDavid Rees the attributes that are allowed at `enum` scope. 148*99e0aae7SDavid Rees enum_value_attributes: A set of (attribute_name, is_default) tuples 149*99e0aae7SDavid Rees specifying the attributes that are allowed at the scope of enum values. 150*99e0aae7SDavid Rees external_attributes: A set of (attribute_name, is_default) tuples 151*99e0aae7SDavid Rees specifying the attributes that are allowed at `external` scope. 152*99e0aae7SDavid Rees structure_virtual_field_attributes: A set of (attribute_name, is_default) 153*99e0aae7SDavid Rees tuples specifying the attributes that are allowed at the scope of 154*99e0aae7SDavid Rees virtual fields (`let` fields) in structures (both `struct` and `bits`). 155*99e0aae7SDavid Rees structure_physical_field_attributes: A set of (attribute_name, is_default) 156*99e0aae7SDavid Rees tuples specifying the attributes that are allowed at the scope of 157*99e0aae7SDavid Rees physical fields in structures (both `struct` and `bits`). 158*99e0aae7SDavid Rees 159*99e0aae7SDavid Rees Returns: 160*99e0aae7SDavid Rees A list of lists of error.error, or an empty list if there were no errors. 161*99e0aae7SDavid Rees """ 162*99e0aae7SDavid Rees 163*99e0aae7SDavid Rees def check_module(module, errors): 164*99e0aae7SDavid Rees errors.extend(_check_attributes( 165*99e0aae7SDavid Rees module.attribute, types, back_end, module_attributes, 166*99e0aae7SDavid Rees "module '{}'".format( 167*99e0aae7SDavid Rees module.source_file_name), module.source_file_name)) 168*99e0aae7SDavid Rees 169*99e0aae7SDavid Rees def check_type_definition(type_definition, source_file_name, errors): 170*99e0aae7SDavid Rees if type_definition.HasField("structure"): 171*99e0aae7SDavid Rees if type_definition.addressable_unit == ir_data.AddressableUnit.BYTE: 172*99e0aae7SDavid Rees errors.extend(_check_attributes( 173*99e0aae7SDavid Rees type_definition.attribute, types, back_end, struct_attributes, 174*99e0aae7SDavid Rees "struct '{}'".format( 175*99e0aae7SDavid Rees type_definition.name.name.text), source_file_name)) 176*99e0aae7SDavid Rees elif type_definition.addressable_unit == ir_data.AddressableUnit.BIT: 177*99e0aae7SDavid Rees errors.extend(_check_attributes( 178*99e0aae7SDavid Rees type_definition.attribute, types, back_end, bits_attributes, 179*99e0aae7SDavid Rees "bits '{}'".format( 180*99e0aae7SDavid Rees type_definition.name.name.text), source_file_name)) 181*99e0aae7SDavid Rees else: 182*99e0aae7SDavid Rees assert False, "Unexpected addressable_unit '{}'".format( 183*99e0aae7SDavid Rees type_definition.addressable_unit) 184*99e0aae7SDavid Rees elif type_definition.HasField("enumeration"): 185*99e0aae7SDavid Rees errors.extend(_check_attributes( 186*99e0aae7SDavid Rees type_definition.attribute, types, back_end, enum_attributes, 187*99e0aae7SDavid Rees "enum '{}'".format( 188*99e0aae7SDavid Rees type_definition.name.name.text), source_file_name)) 189*99e0aae7SDavid Rees elif type_definition.HasField("external"): 190*99e0aae7SDavid Rees errors.extend(_check_attributes( 191*99e0aae7SDavid Rees type_definition.attribute, types, back_end, external_attributes, 192*99e0aae7SDavid Rees "external '{}'".format( 193*99e0aae7SDavid Rees type_definition.name.name.text), source_file_name)) 194*99e0aae7SDavid Rees 195*99e0aae7SDavid Rees def check_struct_field(field, source_file_name, errors): 196*99e0aae7SDavid Rees if ir_util.field_is_virtual(field): 197*99e0aae7SDavid Rees field_attributes = structure_virtual_field_attributes 198*99e0aae7SDavid Rees field_adjective = "virtual " 199*99e0aae7SDavid Rees else: 200*99e0aae7SDavid Rees field_attributes = structure_physical_field_attributes 201*99e0aae7SDavid Rees field_adjective = "" 202*99e0aae7SDavid Rees errors.extend(_check_attributes( 203*99e0aae7SDavid Rees field.attribute, types, back_end, field_attributes, 204*99e0aae7SDavid Rees "{}struct field '{}'".format(field_adjective, field.name.name.text), 205*99e0aae7SDavid Rees source_file_name)) 206*99e0aae7SDavid Rees 207*99e0aae7SDavid Rees def check_enum_value(value, source_file_name, errors): 208*99e0aae7SDavid Rees errors.extend(_check_attributes( 209*99e0aae7SDavid Rees value.attribute, types, back_end, enum_value_attributes, 210*99e0aae7SDavid Rees "enum value '{}'".format(value.name.name.text), source_file_name)) 211*99e0aae7SDavid Rees 212*99e0aae7SDavid Rees errors = [] 213*99e0aae7SDavid Rees # TODO(bolms): Add a check that only known $default'ed attributes are 214*99e0aae7SDavid Rees # used. 215*99e0aae7SDavid Rees traverse_ir.fast_traverse_ir_top_down( 216*99e0aae7SDavid Rees ir, [ir_data.Module], check_module, 217*99e0aae7SDavid Rees parameters={"errors": errors}) 218*99e0aae7SDavid Rees traverse_ir.fast_traverse_ir_top_down( 219*99e0aae7SDavid Rees ir, [ir_data.TypeDefinition], check_type_definition, 220*99e0aae7SDavid Rees parameters={"errors": errors}) 221*99e0aae7SDavid Rees traverse_ir.fast_traverse_ir_top_down( 222*99e0aae7SDavid Rees ir, [ir_data.Field], check_struct_field, 223*99e0aae7SDavid Rees parameters={"errors": errors}) 224*99e0aae7SDavid Rees traverse_ir.fast_traverse_ir_top_down( 225*99e0aae7SDavid Rees ir, [ir_data.EnumValue], check_enum_value, 226*99e0aae7SDavid Rees parameters={"errors": errors}) 227*99e0aae7SDavid Rees return errors 228*99e0aae7SDavid Rees 229*99e0aae7SDavid Rees 230*99e0aae7SDavid Reesdef _check_attributes(attribute_list, types, back_end, attribute_specs, 231*99e0aae7SDavid Rees context_name, module_source_file): 232*99e0aae7SDavid Rees """Performs basic checks on the given list of attributes. 233*99e0aae7SDavid Rees 234*99e0aae7SDavid Rees Checks the given attribute_list for duplicates, unknown attributes, attributes 235*99e0aae7SDavid Rees with incorrect type, and attributes whose values are not constant. 236*99e0aae7SDavid Rees 237*99e0aae7SDavid Rees Arguments: 238*99e0aae7SDavid Rees attribute_list: An iterable of ir_data.Attribute. 239*99e0aae7SDavid Rees back_end: The qualifier for attributes to check, or None. 240*99e0aae7SDavid Rees attribute_specs: A dict of attribute names to _Attribute structures 241*99e0aae7SDavid Rees specifying the allowed attributes. 242*99e0aae7SDavid Rees context_name: A name for the context of these attributes, such as "struct 243*99e0aae7SDavid Rees 'Foo'" or "module 'm.emb'". Used in error messages. 244*99e0aae7SDavid Rees module_source_file: The value of module.source_file_name from the module 245*99e0aae7SDavid Rees containing 'attribute_list'. Used in error messages. 246*99e0aae7SDavid Rees 247*99e0aae7SDavid Rees Returns: 248*99e0aae7SDavid Rees A list of lists of error.Errors. An empty list indicates no errors were 249*99e0aae7SDavid Rees found. 250*99e0aae7SDavid Rees """ 251*99e0aae7SDavid Rees if attribute_specs is None: 252*99e0aae7SDavid Rees attribute_specs = [] 253*99e0aae7SDavid Rees errors = [] 254*99e0aae7SDavid Rees already_seen_attributes = {} 255*99e0aae7SDavid Rees for attr in attribute_list: 256*99e0aae7SDavid Rees field_checker = ir_data_utils.reader(attr) 257*99e0aae7SDavid Rees if field_checker.back_end.text: 258*99e0aae7SDavid Rees if attr.back_end.text != back_end: 259*99e0aae7SDavid Rees continue 260*99e0aae7SDavid Rees else: 261*99e0aae7SDavid Rees if back_end is not None: 262*99e0aae7SDavid Rees continue 263*99e0aae7SDavid Rees attribute_name = _attribute_name_for_errors(attr) 264*99e0aae7SDavid Rees attr_key = (field_checker.name.text, field_checker.is_default) 265*99e0aae7SDavid Rees if attr_key in already_seen_attributes: 266*99e0aae7SDavid Rees original_attr = already_seen_attributes[attr_key] 267*99e0aae7SDavid Rees errors.append([ 268*99e0aae7SDavid Rees error.error(module_source_file, 269*99e0aae7SDavid Rees attr.source_location, 270*99e0aae7SDavid Rees "Duplicate attribute '{}'.".format(attribute_name)), 271*99e0aae7SDavid Rees error.note(module_source_file, 272*99e0aae7SDavid Rees original_attr.source_location, 273*99e0aae7SDavid Rees "Original attribute")]) 274*99e0aae7SDavid Rees continue 275*99e0aae7SDavid Rees already_seen_attributes[attr_key] = attr 276*99e0aae7SDavid Rees 277*99e0aae7SDavid Rees if attr_key not in attribute_specs: 278*99e0aae7SDavid Rees if attr.is_default: 279*99e0aae7SDavid Rees error_message = "Attribute '{}' may not be defaulted on {}.".format( 280*99e0aae7SDavid Rees attribute_name, context_name) 281*99e0aae7SDavid Rees else: 282*99e0aae7SDavid Rees error_message = "Unknown attribute '{}' on {}.".format(attribute_name, 283*99e0aae7SDavid Rees context_name) 284*99e0aae7SDavid Rees errors.append([error.error(module_source_file, 285*99e0aae7SDavid Rees attr.name.source_location, 286*99e0aae7SDavid Rees error_message)]) 287*99e0aae7SDavid Rees else: 288*99e0aae7SDavid Rees errors.extend(types[attr.name.text](attr, module_source_file)) 289*99e0aae7SDavid Rees return errors 290*99e0aae7SDavid Rees 291*99e0aae7SDavid Rees 292*99e0aae7SDavid Reesdef gather_default_attributes(obj, defaults): 293*99e0aae7SDavid Rees """Gathers default attributes for an IR object 294*99e0aae7SDavid Rees 295*99e0aae7SDavid Rees This is designed to be able to be used as-is as an incidental action in an IR 296*99e0aae7SDavid Rees traversal to accumulate defaults for child nodes. 297*99e0aae7SDavid Rees 298*99e0aae7SDavid Rees Arguments: 299*99e0aae7SDavid Rees defaults: A dict of `{ "defaults": { attr.name.text: attr } }` 300*99e0aae7SDavid Rees 301*99e0aae7SDavid Rees Returns: 302*99e0aae7SDavid Rees A dict of `{ "defaults": { attr.name.text: attr } }` with any defaults 303*99e0aae7SDavid Rees provided by `obj` added/overridden. 304*99e0aae7SDavid Rees """ 305*99e0aae7SDavid Rees defaults = defaults.copy() 306*99e0aae7SDavid Rees for attr in obj.attribute: 307*99e0aae7SDavid Rees if attr.is_default: 308*99e0aae7SDavid Rees defaulted_attr = ir_data_utils.copy(attr) 309*99e0aae7SDavid Rees defaulted_attr.is_default = False 310*99e0aae7SDavid Rees defaults[attr.name.text] = defaulted_attr 311*99e0aae7SDavid Rees return {"defaults": defaults} 312