#!/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 . # 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::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::queryFunc(uft::StructDescriptor const*, void*, uft::Value const&, void*)', 'adept::LicenseImpl::getVoucherID()', 'adept::DRMProcessorImpl::addSignIn()', 'layout::InlineLayoutEngine::AnnotationGlyphRunCounter::operator()(uft::sref&)', 'dp::PointerVector::~PointerVector()', 'adept::IoCallbackWrapper::IoCallbackWrapper(adept::DRMProcessorImpl*, void (adept::DRMProcessorImpl::*)(dp::Unknown*, bool), void (adept::DRMProcessorImpl::*)(double), void (adept::DRMProcessorImpl::*)(dp::String const&))', 'tetraphilia::ThreadImpl, tetraphilia::NoClientYieldHook >', '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("")