SOAdvancedDissector/cppprototypeparser.py

221 lines
7.9 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright Grégory Soutadé
# This file is part of SOAdvancedDissector
# SOAdvancedDissector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SOAdvancedDissector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SOAdvancedDissector. If not, see <http://www.gnu.org/licenses/>.
#
class CPPPrototypeParser:
"""Class that extract all parts of a cpp method prototype"""
def __init__(self, fullname=''):
if fullname:
self.parse(fullname)
else:
self._reset()
def _reset(self):
self.fullclassname = '' # Class name with its namespaces
self.names = [] # All decoded names : namespaces, class, function
self.namespaces = [] # Namespaces part
self.parameters = [] # Parameters part
self.fullparameters = '' # Full parameters string
self.funcname = '' # Function name
self.classname = '' # Class name
self.fullname = '' # namespaces + class + function + parameters
self.has_parameters = False
self.is_function = False # Is single function or class method
# Should parse something like :
# uft::ClassDescriptor<layout::MasterConditionalReference>::queryFunc(uft::StructDescriptor const*, void*, uft::Value const&, void*)
def parse(self, fullname):
"""Parse CPP method prototype and fill class members
with right values
Parameters
----------
fullname : str
Prototype to parse
"""
self._reset()
self.fullname = fullname
if '(' in fullname:
self.has_parameters = True
splitname = fullname.split('(')
if 'operator()' in fullname:
namepart = splitname[0] + '()'
self.fullparameters = '(' + ''.join(splitname[2:])
else:
namepart = splitname[0]
self.fullparameters = '(' + ''.join(splitname[1:])
self._parseParameters()
else:
namepart = fullname
self.names = self._parseName(namepart)
# Function or class method
if self.has_parameters:
self.funcname = self.names[-1]
# Class method with at least class name + method name
if len(self.names) >= 2:
self.classname = self.names[-2]
self.fullclassname = '::'.join(self.names[:-1])
# Contains namespaces
if len(self.names) > 2:
self.namespaces = self.names[:-2]
# Simple function
else:
self.is_function = True
self.funcname = self.names[0]
else:
# Class name with or without namespaces
self.classname = self.names[-1]
if len(self.names) > 1:
self.namespaces = self.names[:-1]
if self.funcname.endswith(' const'):
self.funcname = self.funcname[:-len(' const')]
if self.classname.endswith(' const'):
self.classname = self.classname[:-len(' const')]
def _parseName(self, name):
"""Parse CPP method name and split in different parts
using '::' as delimiter.
It supports templates names and function prototypes
Parameters
----------
name : str
Name to parse
Returns
-------
list
Splitted name parts
"""
nb_parenthesis = 0
nb_chevrons = 0
nb_parenthesis = 0
cur_buf = ''
names = []
nb_db_points = 0
in_template = False
in_parenthesis = False
operator = False
for c in name.strip():
if operator:
cur_buf += c
continue
if c == '(':
cur_buf += c
nb_parenthesis += 1
in_parenthesis = True
elif c == ')':
cur_buf += c
nb_parenthesis -= 1
if nb_parenthesis == 0:
in_parenthesis = False
elif c == ':':
if in_template or in_parenthesis:
cur_buf += c
continue
nb_db_points += 1
if nb_db_points == 2:
names.append(cur_buf)
cur_buf = ''
nb_db_points = 0
elif c == '<':
cur_buf += c
in_template = True
nb_chevrons += 1
elif c == '>':
cur_buf += c
nb_chevrons -= 1
if nb_chevrons == 0:
in_template = False
else:
cur_buf += c
# Next characters are part of operator name
if cur_buf == 'operator':
operator = True
if cur_buf:
names.append(cur_buf)
return names
def _parseParameters(self):
"""Parse each parameters and do split on them
using '::' as delimiter.
Update self.parameters member
"""
nb_chevrons = 0
in_template = False
cur_buf = ''
# Parse fullparameters except parenthesis
for c in self.fullparameters[1:-1]:
if c == ',':
if in_template: continue
self.parameters.append(self._parseName(cur_buf))
cur_buf = ''
elif c == '<':
cur_buf += c
if in_template: continue
in_template = True
nb_chevrons += 1
elif c == '>':
cur_buf += c
nb_chevrons -= 1
if nb_chevrons == 0:
in_template = False
else:
cur_buf += c
if cur_buf:
self.parameters.append(self._parseName(cur_buf))
# Some tests
if __name__ == "__main__":
strings = ['uft::ClassDescriptor<layout::MasterConditionalReference>::queryFunc(uft::StructDescriptor const*, void*, uft::Value const&, void*)',
'adept::LicenseImpl::getVoucherID()',
'adept::DRMProcessorImpl::addSignIn()',
'layout::InlineLayoutEngine::AnnotationGlyphRunCounter::operator()(uft::sref<layout::RunListItem>&)',
'dp::PointerVector<void>::~PointerVector()',
'adept::IoCallbackWrapper<adept::DRMProcessorImpl>::IoCallbackWrapper(adept::DRMProcessorImpl*, void (adept::DRMProcessorImpl::*)(dp::Unknown*, bool), void (adept::DRMProcessorImpl::*)(double), void (adept::DRMProcessorImpl::*)(dp::String const&))',
'tetraphilia::ThreadImpl<T3AppTraits, tetraphilia::PFiber<T3AppTraits>, tetraphilia::NoClientYieldHook<T3AppTraits> >',
'debugOutputOpen']
for s in strings:
p = CPPPrototypeParser(s)
print('Original {}'.format(s))
print('Full parameters {}'.format(p.fullparameters))
print('Names {}'.format(p.names))
print('Namespaces {}'.format(p.namespaces))
print('Parameters {}'.format(p.parameters))
print('Class name {}'.format(p.classname))
print('Func name {}'.format(p.funcname))
print('Full class name {}'.format(p.fullclassname))
print('Full parameters {}'.format(p.fullparameters))
print('Is function ? {}'.format(p.is_function))
print('Has parameters ? {}'.format(p.has_parameters))
print("")