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