# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 """CBMC properties checked during property checking. The Result module describes the results of CBMC property checking, the Coverage module describes the results of CBMC coverage checking, and the Property module describes the properties checked during property checking. """ import json import logging import voluptuous import voluptuous.humanize from cbmc_viewer import filet from cbmc_viewer import parse from cbmc_viewer import srcloct from cbmc_viewer import util JSON_TAG = "viewer-property" ################################################################ # Property validator VALID_PROPERTY_DEFINITION = voluptuous.Schema({ 'class': str, # eg, "pointer dereference" 'description': str, # eg, "pointer outside dynamic object" 'expression': str, 'location': srcloct.VALID_SRCLOC }, required=True) VALID_PROPERTY = voluptuous.Schema({ 'properties': { # property name -> property definition voluptuous.Optional(str): VALID_PROPERTY_DEFINITION } }, required=True) ################################################################ def key(name): """A key for sorting property names like function.property.index""" try: dot = name.rindex('.') return (name[:dot].lower(), int(name[dot+1:])) except ValueError: logging.warning("Property name not of the form STRING.INTEGER: %s", name) return (name, 0) ################################################################ class Property: """CBMC properties checked during property checking.""" def __init__(self, property_lists=None): """Load CBMC properties from lists of properties.""" property_lists = property_lists or [] def handle_duplicates(name, defn1, defn2): logging.warning("Found duplicate property definition: " "%s: %s %s <- %s %s", name, defn1["location"]["file"], defn1["location"]["line"], defn2["location"]["file"], defn2["location"]["line"]) self.properties = util.merge_dicts(property_lists, handle_duplicates) self.validate() def __repr__(self): """A dict representation of an property table.""" self.validate() return self.__dict__ def __str__(self): """A string representation of an property table.""" return json.dumps({JSON_TAG: self.__repr__()}, indent=2, sort_keys=True) def validate(self): """Validate properties.""" return voluptuous.humanize.validate_with_humanized_errors( self.__dict__, VALID_PROPERTY ) def dump(self, filename=None, directory=None): """Write properties to a file or stdout.""" util.dump(self, filename, directory) def names(self): """Names of known properties.""" return self.properties.keys() def lookup(self, name): """Look up all known information for named property.""" return self.properties.get(name) def get_description(self, name): """Get description of named property.""" return (self.lookup(name) or {}).get('description') or name def get_srcloc(self, name): """Get source location of named property.""" return (self.lookup(name) or {}).get('location') ################################################################ class PropertyFromJson(Property): """Load property table from the output of make-property. Given a list of json files containing property definitions produced by make-property, merge these lists into a single list of properties. """ def __init__(self, json_files): super().__init__( [parse.parse_json_file(json_file)[JSON_TAG]["properties"] for json_file in json_files] ) ################################################################ class PropertyFromCbmcJson(Property): """Load properties from output of 'cbmc --show-properties --json-ui'.""" def __init__(self, json_files, root): super().__init__( [load_cbmc_json(json_file, root) for json_file in json_files] ) def load_cbmc_json(jsonfile, root): """Load a json file produced by cbmc --show-properties --json-ui.""" json_data = parse.parse_json_file(jsonfile) assert json_data is not None # Search cbmc output for {"properties": [ PROPERTY ]} asserts = [json_map for json_map in json_data if "properties" in json_map] if len(asserts) != 1: raise UserWarning(f"Expected 1 set of properties in cbmc output, found {len(asserts)}") # Each PROPERTY a loop property and definition root = srcloct.abspath(root) return { property['name']: { 'class': property['class'], 'description': property['description'], 'expression': property['expression'], 'location': srcloct.json_srcloc(property['sourceLocation'], root) } for property in asserts[0]["properties"] } ################################################################ class PropertyFromCbmcXml(Property): """Load properties from output of 'cbmc --show-properties --xml-ui'.""" def __init__(self, xml_files, root): super().__init__( [load_cbmc_xml(xml_file, root) for xml_file in xml_files] ) def load_cbmc_xml(xmlfile, root): """Load a json file produced by cbmc --show-properties --xml-ui.""" xml_data = parse.parse_xml_file(xmlfile) assert xml_data is not None root = srcloct.abspath(root) return { property.get("name"): { "class": property.get("class"), "description": property.find("description").text, "expression": property.find("expression").text, 'location': srcloct.xml_srcloc(property.find("location"), root) } for property in xml_data.iter("property") } ################################################################ # make-property # pylint: disable=inconsistent-return-statements def fail(msg): """Log failure and raise exception.""" logging.info(msg) raise UserWarning(msg) def make_property(args): """The implementation of make-property.""" viewer_property, cbmc_property, srcdir = args.viewer_property, args.property, args.srcdir if viewer_property: if filet.all_json_files(viewer_property): return PropertyFromJson(viewer_property) fail(f"Expected json files: {viewer_property}") if cbmc_property and srcdir: if filet.all_json_files(cbmc_property): return PropertyFromCbmcJson(cbmc_property, srcdir) if filet.all_xml_files(cbmc_property): return PropertyFromCbmcXml(cbmc_property, srcdir) fail(f"Expected json files or xml files, not both: {cbmc_property}") logging.info("make-property: nothing to do: need " "cbmc property listing results (cbmc --show-properties) and --srcdir or " "--viewer-property") return Property() def make_and_save_property(args, path=None): """Make property object and write to file or stdout""" obj = make_property(args) util.save(obj, path) return obj ################################################################