2026-07-10 10:38:57 +02:00

558 lines
18 KiB
Python

import logging
from ScEpTIC import tools
from ScEpTIC.AST.elements.global_var import GlobalVar
from ScEpTIC.AST.elements.types import Type, BaseType
from ScEpTIC.AST.elements.value import Value
from ScEpTIC.exceptions import ParsingException, LLVMSyntaxErrorException
from ScEpTIC.llvmir_parser.instructions_parser import binary_operations
from ScEpTIC.llvmir_parser.instructions_parser import memory_operations
from ScEpTIC.llvmir_parser.sections_parser import custom_types
from ScEpTIC.llvmir_parser.sections_parser import metadata
from ScEpTIC.llvmir_parser.token_generator import TOKEN_SPACE
def parse_global_vars_section(text, log_section_content = False):
"""
Parses and returns the global variables section, using parse_global_var.
@<GlobalVarName> = [Linkage] [PreemptionSpecifier] [Visibility]
[DLLStorageClass] [ThreadLocal]
[(unnamed_addr|local_unnamed_addr)] [AddrSpace]
[ExternallyInitialized]
<global | constant> <Type> [<initial_valConstant>]
[, section "name"] [, comdat [($name)]]
[, align <Alignment>] (, !name !N)*
"""
logging.debug('Calling parse_global_var_section({}, {})'.format(text, log_section_content))
for line in text:
parse_global_var(line)
global_vars = GlobalVar.elements
if logging.getLogger().isEnabledFor(logging.INFO):
log_str = 'Parsed global variables section. {} global variables found'.format(len(global_vars))
if log_section_content:
log_str += ':\n{}'.format(tools.fancy_dict_to_str(global_vars))
else:
log_str += '.\n{}'.format(tools.fancy_dict_keys_to_str(global_vars))
logging.info(log_str)
return global_vars
def parse_global_var(text):
"""
Parses a line containing the definition of a global variable and returns it.
"""
logging.debug('Calling parse_global_var({})'.format(text))
if not is_global_var_def(text):
raise ParsingException('Invalid global variable definition! Global variables must start with @')
name = "@"+text[1]
# attributes are separated by comma
# text[3:] removes ['@', 'name', '=']
# if an array is initialized, it will be like [i32 val, i32 val2, ...]
# so elements inside [...] must not be splitted using commas.
# use [] and () due to getelementptr
elements = text[3:]
str_def = tools.get_indexes_of_matching_tokens(elements, 'c"', '"')
# fix for string initialization
if str_def is not None:
elements = elements[0:str_def[0]] + [elements[str_def[0]:str_def[1]]] + elements[str_def[1]:]
attributes = tools.split_context_into_sublist(elements, ",")
# parse main attributes part
retval = parse_main_attributes(attributes[0])
retval['name'] = name
attributes = attributes[1:]
# parse section part
section = parse_section_attribute(attributes[0])
if section is not None:
attributes = attributes[1:]
retval['section'] = section
# parse comdat part
comdat = parse_comdat_attribute(attributes[0])
if comdat is not None:
attributes = attributes[1:]
retval['comdat'] = comdat
# parse align part
align = parse_align_attribute(attributes[0])
if align is not None:
attributes = attributes[1:]
retval['align'] = align
# parse all metadatas
retval['metadata'] = metadata.get_element_metadata(tools.flat_list(attributes), 'global variable {}'.format(retval['name']))
if logging.getLogger().isEnabledFor(logging.DEBUG):
log_vals = " {}".format(retval)
logging.debug('Parsed global variable {}:\n{}'.format(name, tools.instert_new_line_every(log_vals, new_line_padding = ' ')))
retval = GlobalVar(retval['name'], retval['type'], retval['initial_val'], retval['is_constant'], retval['align'], retval['section'], retval['comdat'], retval['metadata'])
return retval
def parse_main_attributes(text):
"""
Parses the main and mandatory attributes of the variable (type, initial value).
Returns a dictionary containing the relevant information of the variable.
"""
logging.debug('Calling parse_main_attributes({})'.format(text))
if 'global' in text:
start = text.index('global')
elif 'constant' in text:
start = text.index('constant')
else:
raise LLVMSyntaxErrorException('Mandatory Global / Constant attribute not found!')
type_and_init = text[start+1:]
retval = parse_type_and_initial_val(type_and_init)
retval['is_constant'] = text[start] == 'constant'
# Those attributes are not important for my simulation
# [Linkage] [PreemptionSpecifier] [Visibility] [DLLStorageClass] [ThreadLocal]
# [(unnamed_addr|local_unnamed_addr)] [AddrSpace] [ExternallyInitialized]
optional_attributes = text[:start]
retval['optional_attributes'] = optional_attributes
return retval
def parse_type_and_initial_val(text):
"""
Returns the type and the initial_val (if any) of the variable
"""
logging.debug('Calling parse_type_and_initial_val({})'.format(text))
parentheses_groups = tools.split_parantheses_groups_with_context(text)
must_be_array = False
# struct with initial value assigned
if text[0] == '%' and text[2] == '{' and text[-1] == '}':
initial_val = parse_struct_initial_val(text)
initial_val = Value('struct_val', initial_val, None)
text = text[:text.index('{')]
# array
elif len(parentheses_groups) > 1:
text = parentheses_groups[0]
initial_val = parse_array_initial_val(parentheses_groups[1])
if initial_val == 'zeroinitializer':
initial_val = Value('immediate', initial_val, None)
else:
initial_val = Value('array_val', initial_val, None)
must_be_array = True
elif 'getelementptr' in text:
index = text.index('getelementptr')
pointer_val = memory_operations.parse_getelementptr_operation(text[index:], False)
initial_val = Value('address', pointer_val, None)
text = text[:index]
else:
# not an array, or array not initialized
value = text[-1]
if is_valid_initial_val(value):
text = text[:-1]
else:
value = None
initial_val = Value('immediate', value, None)
retval = {'type': parse_type(text), 'initial_val': initial_val}
initial_val.type = retval['type']
if must_be_array and not retval['type'].is_array:
raise ParsingException('Array initialization vector given. Type must be an array. {} given'.format(text))
return retval
def is_valid_initial_val(text):
"""
Verify if text is a valid initial_val.
Admitted initial_vals:
- 0.00e+000
- 0.0
- null
- zeroinitial_val
"""
logging.debug('Calling is_valid_initial_val({})'.format(text))
if 'e+' in text:
return True
if '0x' in text:
return True
if 'zeroinitializer' == text:
return True
if 'null' == text:
return True
try:
float(text)
return True
except ValueError:
pass
return False
def parse_struct_initial_val(text):
"""
Parses the struct initial value and returns it.
"""
logging.debug('Calling parse_struct_initial_val({})'.format(text))
initial_vals = []
# divide considering arrays
data = tools.split_context_into_sublist(text[3:-1], ',')
for element in data:
# Array
if '[' in element and ']' in element:
group = tools.split_parantheses_groups_with_context(element)
vals = parse_array_initial_val(group[1])
if vals == 'zeroinitializer':
# flatten zeroinitializer for structs
# [n x type]
# [1:-1] remove [ and ]
group = group[0][1:-1]
separator = group.index('x')
num_elements = int(group[separator-1])
elements_type = parse_type(group[separator+1:])
vals = []
for i in range(num_elements):
vals.append(Value('immediate', 0, elements_type))
val = vals
else:
val = binary_operations.parse_operand(element, True)
initial_vals.append(val)
return initial_vals
def parse_array_initial_val(text):
"""
Parses the array initial value and returns it. It also works for vectors.
It can be a list of values or a list of sub-lists.
It is a list (n-dimension, as the array initialization vector).
"""
logging.debug('Calling parse_array_initial_val({})'.format(text))
initial_vals = []
force_zero_par = False
if text.count('[') != text.count(']'):
raise LLVMSyntaxErrorException('Unbalanced number of [] parentheses for initial value of array: {}'.format(text))
if isinstance(text, list) and len(text) == 1 and isinstance(text[0], list):
text = tools.list_replace_element(text[0], TOKEN_SPACE, ' ')
force_zero_par = True
if text.count('[') == 0 or force_zero_par:
if text[0] == 'zeroinitializer' or (text[0] == '*' and text[1] == 'null'):
initial_vals = 'zeroinitializer'
elif text[0] == 'c' and text[1] == '"' and text[-1] == '"':
# get initialization string content
text = tools.list_to_string(text[2:-1])
i = 0
while i < len(text):
element = text[i]
if element == '\\':
element = tools.list_to_string(text[i+1:i+3])
element = int(element, 16)
i += 2
else:
element = ord(element)
element = Value('immediate', element, 'char')
initial_vals.append(element)
i += 1
else:
raise ParsingException('Array initialization string c"string_value" or zeroinitializer expected. {} given'.format(text))
elif text.count('[') == 1:
# just a list of values. Get the elements and parse them
# e.g [i32 0, i32 1, ...]
start, end = tools.get_list_boundaries(text, '[', ']')
text = tools.split_context_into_sublist(text[start:end], ',', ['{'], ['}'])
for initial_val in text:
if '{' in initial_val and '}' in initial_val and ',' in initial_val:
start, end = tools.get_list_boundaries(initial_val, '{', '}')
sub_text = tools.split_into_sublist(initial_val[start:end], ',')
for initial_val in sub_text:
initial_val = binary_operations.parse_operand(initial_val, True)
initial_vals.append(initial_val)
else:
initial_val = binary_operations.parse_operand(initial_val, True)
initial_vals.append(initial_val)
else:
# array 2+ dimensional.
# is a list of list (of list)*
# remove the external [] and divide into sublists
start, end = tools.get_list_boundaries(text, '[', ']', True)
text = tools.split_context_into_sublist(text[start:end], ',')
for val_grp in text:
# for each sublist we have [n x type] [ list of type-value ]
# those lines get the list of type-value and parse it.
val_grp = tools.split_parantheses_groups(val_grp)
initial_val = parse_array_initial_val(val_grp[1])
initial_vals.append(initial_val)
return initial_vals
def parse_type(text, is_function_return_type=False, is_function_attr=False):
"""
Parses and returns the type of given operand/variable.
"""
logging.debug('Calling parse_type({}, {}, {})'.format(text, is_function_return_type, is_function_attr))
parsed_type = Type.empty()
# bitcast or other conversion to point to a defined data structure
# the data structure can be ignored, since is specified in other places
if (text[0] == '{' or text[0] == '[' or text[0] == '<') and text[-1] == '*':
parsed_type.is_pointer = True
parsed_type.pointer_level = text.count('*')
# dummy value. It will be correctly replaced by the AST with the dimension of the address
# NB: pointer's dimension to a 32bit integer is the same of the dimension of a pointer to a 64bit double
# since they contains not the data, but an address to where data is placed.
text = ['i32']
# Struct / Union explicit definition
elif text[0] == '{':
parsed_type.is_base_type = False
parsed_type.is_ct_defined = True
parsed_type.custom_type_def = custom_types.parse_type_composition(['type'] + text)
# n-dimensional Array
elif text[0] == '[':
array_elements, parsed_type = parse_array_def(text)
parsed_type.is_array = True
parsed_type._set_array_composition(array_elements)
# vector type
elif text[0] == '<':
# < 2 x i32 >
parsed_type.is_vector = True
parsed_type.vector_dimension = int(text[1])
# actual type is on position 3 (4th token)
text = text[3]
else:
# If pointer, count the level
if '*' in text:
parsed_type.is_pointer = True
parsed_type.pointer_level = text.count('*')
text = tools.list_sanitize(text, '*')
# Struct / Union
if text[0] == '%':
parsed_type.is_base_type = False
parsed_type.custom_type_name = text[0]+text[1]
# if it is neither an array, nor a struct/union, it is a base type
if not parsed_type.is_array and parsed_type.is_base_type:
if isinstance(text, list):
text = text[0]
parsed_type.base_type = parse_base_type(text, is_function_return_type, is_function_attr)
return parsed_type
def parse_base_type(text, is_function_return_type = False, is_function_attr = False):
"""
Returns the base type of a variable (type and bits)
"""
logging.debug('Calling parse_base_type({}, {}, {})'.format(text, is_function_return_type, is_function_attr))
fp_bits = {'half': 16, 'float': 32, 'double': 64, 'fp128': 128, 'x86_fp80': 80, 'ppc_fp128': 128}
# float and derivates
if text in fp_bits:
return BaseType('float', fp_bits[text])
# integer / bits derivates
if text[0] == 'i':
return BaseType('int', text[1:])
# LLVM 15 pointer type
if text[0] == 'ptr':
raise Exception("ptr not supported")
#return BaseType('', text[1:])
# Supports both function return type and void pointers (function pointers)
# PREVIOUS: if is_function_return_type and text == 'void':
if text == 'void':
return BaseType('void', '0')
if is_function_attr:
if text == 'metadata':
return BaseType('metadata', '0')
elif text == '...':
return BaseType('var_args', '8')
raise NotImplementedError('Numeric type {} not implemented!'.format(text))
def parse_array_def(text):
"""
Returns the type of the array and its dimensionality
"""
logging.debug('Calling parse_array_def({})'.format(text))
# an array definition must start with [ and end with ]
# if is a string will be [...] c"..."
if text[0] != '[' or text[-1] != ']':
raise LLVMSyntaxErrorException('Array expected! {} given'.format(text))
# remove [ and ]
text = text[1:-1]
# ['number', 'x', 'type']
elements_number = [text[0]]
elements_type = text[2:]
# if the array is n+1 dimensions, parse it.
if elements_type[0] == '[':
subvec_elements_number, subvec_elements_type = parse_array_def(elements_type)
# concatenate two lists, to have a flat one
elements_number = elements_number + subvec_elements_number
# overwrite the array type with the one found recursively
elements_type = subvec_elements_type
else:
# the array dimensionality has been processed. Now get the actual type
elements_type = parse_type(elements_type)
return elements_number, elements_type
def parse_section_attribute(text):
"""
Check and returns the value of the section attribute.
If not present, returns false
"""
logging.debug('Calling parse_section_attribute({})'.format(text))
if 'section' not in text:
logging.debug('Section attribute not present.')
return None
# ['section', '"', 'section_name', '"']
# get only section_name
start, end = tools.get_list_apex_boundaries(text)
# convert to string
section = tools.list_to_string(text[start:end], {TOKEN_SPACE: ' '})
return section
def parse_comdat_attribute(text):
"""
Check and returns the value of the comdat attribute.
If not present, returns false
"""
logging.debug('Calling parse_comdat_attribute({})'.format(text))
if 'comdat' not in text:
logging.debug('Comdat attribute not present.')
return None
index = text.index('comdat')
comdat = text[index+1:]
# if is comdat(...) get the part inside ()
if(comdat[0] == '('):
comdat = comdat[1:-1]
comdat = tools.list_to_string(comdat, {TOKEN_SPACE: ' '})
return comdat
def parse_align_attribute(text):
"""
Check and returns the value of the align attribute.
If not present, returns false
"""
logging.debug('Calling parse_align_attribute({})'.format(text))
if 'align' not in text:
logging.debug('Align attribute not present.')
return None
index = text.index('align')
align = text[index+1]
return align
def is_global_var_def(text):
"""
Returns if the current line is a global variable definition.
"""
logging.debug('Calling is_global_var_def({})'.format(text))
return isinstance(text, list) and len(text) > 3 and text[0] == '@' and text[2] == '='