175 lines
5.1 KiB
Python
175 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (C), 2022 Intel Corporation.
|
|
# Copyright (c), 2018-2021, SISSA (International School for Advanced Studies).
|
|
#
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
#
|
|
|
|
import sys, os
|
|
from decimal import Decimal
|
|
from copy import copy
|
|
import operator
|
|
import elementpath
|
|
|
|
# Allow this script to find the library module at misc/config_tools/library.
|
|
#
|
|
# TODO: Reshuffle the module structure of the configuration toolset for clearer imports.
|
|
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
|
|
import library.rdt as rdt
|
|
|
|
BaseParser = elementpath.XPath2Parser
|
|
|
|
class CustomParser(BaseParser):
|
|
if hasattr(BaseParser, "SYMBOLS"):
|
|
SYMBOLS = BaseParser.SYMBOLS | {
|
|
# Bit-wise operations
|
|
'bitwise-and',
|
|
|
|
'bits-of',
|
|
'has',
|
|
'duplicate-values',
|
|
|
|
'number-of-clos-id-needed',
|
|
}
|
|
|
|
method = CustomParser.method
|
|
function = CustomParser.function
|
|
|
|
###
|
|
# Custom functions
|
|
|
|
OPERATORS_MAP = {
|
|
'bitwise-and': operator.and_
|
|
}
|
|
|
|
def hex_to_int(value):
|
|
if hasattr(value, 'text'):
|
|
value = value.text
|
|
if isinstance(value, int):
|
|
return value
|
|
elif isinstance(value, (float, Decimal)):
|
|
return int(value)
|
|
elif isinstance(value, str) and value.startswith("0x"):
|
|
return int(value, base=16)
|
|
else:
|
|
raise TypeError('invalid type {!r} for integer'.format(type(value)))
|
|
|
|
@method(function('bitwise-and', nargs=2))
|
|
def evaluate(self, context=None):
|
|
def aux(op):
|
|
op1 = self.get_argument(context, 0)
|
|
op2 = self.get_argument(context, 1)
|
|
|
|
try:
|
|
return op(hex_to_int(op1), hex_to_int(op2))
|
|
except ValueError as err:
|
|
raise self.error('FORG0001', err) from None
|
|
except TypeError as err:
|
|
raise self.error('XPTY0004', err)
|
|
|
|
return aux(OPERATORS_MAP[self.symbol])
|
|
|
|
@method(function('bits-of', nargs=1))
|
|
def evaluate_bits_of(self, context=None):
|
|
op = self.get_argument(context)
|
|
|
|
try:
|
|
value = hex_to_int(op)
|
|
for idx, bit in enumerate(reversed(bin(value)[2:])):
|
|
if bit == '1':
|
|
yield idx
|
|
except TypeError as err:
|
|
raise self.error('XPTY0004', err)
|
|
|
|
@method(function('has', nargs=2))
|
|
def evaluate_has_function(self, context=None):
|
|
arg2 = self.get_argument(context, index=1, cls=str)
|
|
for item in self[0].select(context):
|
|
value = self.data_value(item)
|
|
if value == arg2:
|
|
return True
|
|
return False
|
|
|
|
@method(function('duplicate-values', nargs=1))
|
|
def select_duplicate_values_function(self, context=None):
|
|
def duplicate_values():
|
|
results = []
|
|
reported = []
|
|
for item in self[0].select(context):
|
|
value = self.data_value(item)
|
|
if context is not None:
|
|
context.item = value
|
|
|
|
if value in results:
|
|
if value not in reported:
|
|
yield value
|
|
reported.append(value)
|
|
else:
|
|
results.append(value)
|
|
|
|
yield from duplicate_values()
|
|
|
|
@method(function('number-of-clos-id-needed', nargs=1))
|
|
def evaluate_number_of_clos_id_needed(self, context=None):
|
|
op = self.get_argument(context, index=0)
|
|
if op is not None:
|
|
if isinstance(op, elementpath.TypedElement):
|
|
op = op.elem
|
|
|
|
# This function may be invoked when the xmlschema library parses the data check schemas, in which case `op` will
|
|
# be an object of class Xsd11Element. Only attempt to calculate the needed CLOS IDs when a real acrn-config node
|
|
# is given.
|
|
if hasattr(op, "xpath"):
|
|
return len(rdt.get_policy_list(op))
|
|
|
|
return 0
|
|
|
|
###
|
|
# Collection of counter examples
|
|
|
|
class Hashable:
|
|
def __init__(self, obj):
|
|
self.obj = obj
|
|
|
|
def __hash__(self):
|
|
return id(self.obj)
|
|
|
|
def copy_context(context):
|
|
ret = copy(context)
|
|
if hasattr(context, 'counter_example'):
|
|
ret.counter_example = dict()
|
|
return ret
|
|
|
|
def add_counter_example(context, private_context, kvlist):
|
|
if hasattr(context, 'counter_example'):
|
|
context.counter_example.update(kvlist)
|
|
if private_context:
|
|
context.counter_example.update(private_context.counter_example)
|
|
|
|
@method('every')
|
|
@method('some')
|
|
def evaluate(self, context=None):
|
|
if context is None:
|
|
raise self.missing_context()
|
|
|
|
some = self.symbol == 'some'
|
|
varrefs = [Hashable(self[k]) for k in range(0, len(self) - 1, 2)]
|
|
varnames = [self[k][0].value for k in range(0, len(self) - 1, 2)]
|
|
selectors = [self[k].select for k in range(1, len(self) - 1, 2)]
|
|
|
|
for results in copy(context).iter_product(selectors, varnames):
|
|
private_context = copy_context(context)
|
|
private_context.variables.update(x for x in zip(varnames, results))
|
|
if self.boolean_value([x for x in self[-1].select(private_context)]):
|
|
if some:
|
|
add_counter_example(context, private_context, zip(varrefs, results))
|
|
return True
|
|
elif not some:
|
|
add_counter_example(context, private_context, zip(varrefs, results))
|
|
return False
|
|
|
|
return not some
|
|
|
|
elementpath.XPath2Parser = CustomParser
|