# Copyright (c) 2015-2025 Vector 35 Inc## Permission is hereby granted, free of charge, to any person obtaining a copy# of this software and associated documentation files (the "Software"), to# deal in the Software without restriction, including without limitation the# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or# sell copies of the Software, and to permit persons to whom the Software is# furnished to do so, subject to the following conditions:## The above copyright notice and this permission notice shall be included in# all copies or substantial portions of the Software.## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS# IN THE SOFTWARE.importabcimportctypesimportdataclassesfromjsonimportdumpsfromtypingimportList,Tuple,Optional,Dictimportsysimporttraceback# Binary Ninja Componentsimportbinaryninjaimportbinaryninja._binaryninjacoreascorefrom.settingsimportSettingsfrom.importbinaryviewfrom.importplatformfrom.importtypecontainerfrom.importtypesfrom.importdeprecationfrom.logimportlog_error_for_exceptionfrom.enumsimportTypeParserErrorSeverity,TypeParserOption[docs]@dataclasses.dataclass(frozen=True)classQualifiedNameTypeAndId:name:'types.QualifiedNameType'id:strtype:'types.Type'@classmethoddef_from_core_struct(cls,struct:core.BNQualifiedNameTypeAndId)->'QualifiedNameTypeAndId':name=types.QualifiedName._from_core_struct(struct.name)type=types.Type.create(handle=core.BNNewTypeReference(struct.type))returnQualifiedNameTypeAndId(name,struct.id,type)def_to_core_struct(self)->core.BNQualifiedNameTypeAndId:result=core.BNQualifiedNameTypeAndId()result.name=types.QualifiedName(self.name)._to_core_struct()result.type=core.BNNewTypeReference(self.type.handle)result.id=self.idreturnresult [docs]@dataclasses.dataclass(frozen=True)classTypeParserError:severity:TypeParserErrorSeveritymessage:strfile_name:strline:intcolumn:intdef__str__(self):text=""ifself.severity==TypeParserErrorSeverity.ErrorSeverity \
orself.severity==TypeParserErrorSeverity.FatalSeverity:text+="error: "ifself.severity==TypeParserErrorSeverity.WarningSeverity:text+="warning: "ifself.severity==TypeParserErrorSeverity.NoteSeverity:text+="note: "ifself.severity==TypeParserErrorSeverity.RemarkSeverity:text+="remark: "ifself.severity==TypeParserErrorSeverity.IgnoredSeverity:text+="ignored: "ifself.file_name=="":text+=f"<unknown>:{self.message}\n"else:text+=f"{self.file_name}:{self.line}:{self.column}{self.message}\n"returntext@classmethoddef_from_core_struct(cls,struct:core.BNTypeParserError)->'TypeParserError':returnTypeParserError(struct.severity,struct.message,struct.fileName,struct.line,struct.column)def_to_core_struct(self)->core.BNTypeParserError:result=core.BNTypeParserError()result.severity=self.severityresult.message=self.messageresult.fileName=self.file_nameresult.line=self.lineresult.column=self.columnreturnresult [docs]@dataclasses.dataclass(frozen=True)classParsedType:name:'types.QualifiedNameType'type:'types.Type'is_user:bool@classmethoddef_from_core_struct(cls,struct:core.BNParsedType)->'ParsedType':name=types.QualifiedName._from_core_struct(struct.name)type=types.Type.create(handle=core.BNNewTypeReference(struct.type))returnParsedType(name,type,struct.isUser)def_to_core_struct(self)->core.BNParsedType:result=core.BNParsedType()result.name=types.QualifiedName(self.name)._to_core_struct()result.type=core.BNNewTypeReference(self.type.handle)result.isUser=self.is_userreturnresult [docs]@dataclasses.dataclass(frozen=True)classBasicTypeParserResult:types:Dict['types.QualifiedName','types.Type']variables:Dict['types.QualifiedName','types.Type']functions:Dict['types.QualifiedName','types.Type']def__repr__(self):returnf"<types:{self.types}, variables:{self.variables}, functions:{self.functions}>" [docs]@dataclasses.dataclass(frozen=True)classTypeParserResult:types:List[ParsedType]variables:List[ParsedType]functions:List[ParsedType]def__repr__(self):returnf"<types:{self.types}, variables:{self.variables}, functions:{self.functions}>"@classmethoddef_from_core_struct(cls,struct:core.BNTypeParserResult)->'TypeParserResult':types=[]variables=[]functions=[]foriinrange(struct.typeCount):types.append(ParsedType._from_core_struct(struct.types[i]))foriinrange(struct.variableCount):variables.append(ParsedType._from_core_struct(struct.variables[i]))foriinrange(struct.functionCount):functions.append(ParsedType._from_core_struct(struct.functions[i]))returnTypeParserResult(types,variables,functions)def_to_core_struct(self)->core.BNTypeParserResult:result=core.BNTypeParserResult()result.typeCount=len(self.types)result.variableCount=len(self.variables)result.functionCount=len(self.functions)result.types=(core.BNParsedType*len(self.types))()result.variables=(core.BNParsedType*len(self.variables))()result.functions=(core.BNParsedType*len(self.functions))()for(i,type)inenumerate(self.types):result.types[i]=type._to_core_struct()for(i,variable)inenumerate(self.variables):result.variables[i]=variable._to_core_struct()for(i,function)inenumerate(self.functions):result.functions[i]=function._to_core_struct()returnresult [docs]defto_bytes(field):iftype(field)==bytes:returnfieldiftype(field)==str:returnfield.encode()returnstr(field).encode() class_TypeParserMetaclass(type):def__iter__(self):binaryninja._init_plugins()count=ctypes.c_ulonglong()types=core.BNGetTypeParserList(count)try:foriinrange(0,count.value):yieldCoreTypeParser(types[i])finally:core.BNFreeTypeParserList(types)def__getitem__(self,value):binaryninja._init_plugins()handle=core.BNGetTypeParserByName(str(value))ifhandleisNone:raiseKeyError(f"'{value}' is not a valid TypeParser")returnCoreTypeParser(handle)@propertydefdefault(self):"""Get the default parser as specified by the user's settings:return: Default parser"""returnCoreTypeParser(core.BNGetDefaultTypeParser())[docs]classTypeParser(metaclass=_TypeParserMetaclass):name=None_registered_parsers=[]_cached_string=None_cached_result=None_cached_error=None[docs]def__init__(self,handle=None):ifhandleisnotNone:self.handle=core.handle_of_type(handle,core.BNTypeParser)self.__dict__["name"]=core.BNGetTypeParserName(handle) [docs]defregister(self):"""Register a custom parser with the API"""assertself.__class__.nameisnotNoneself._cb=core.BNTypeParserCallbacks()self._cb.context=0self._cb.getOptionText=self._cb.getOptionText.__class__(self._get_option_text)self._cb.preprocessSource=self._cb.preprocessSource.__class__(self._preprocess_source)self._cb.parseTypesFromSource=self._cb.parseTypesFromSource.__class__(self._parse_types_from_source)self._cb.parseTypeString=self._cb.parseTypeString.__class__(self._parse_type_string)self._cb.freeString=self._cb.freeString.__class__(self._free_string)self._cb.freeResult=self._cb.freeResult.__class__(self._free_result)self._cb.freeErrorList=self._cb.freeErrorList.__class__(self._free_error_list)self.handle=core.BNRegisterTypeParser(self.__class__.name,self._cb)self.__class__._registered_parsers.append(self) def__str__(self):returnf'<TypeParser:{self.name}>'def__repr__(self):returnf'<TypeParser:{self.name}>'def_get_option_text(self,option,value,result)->bool:try:value_py=core.pyNativeStr(value)result_py=self.get_option_text(option,value_py)ifresult_pyisNone:returnFalseTypeParser._cached_string=core.cstr(result_py)result[0]=TypeParser._cached_stringreturnTrueexcept:log_error_for_exception("Unhandled Python exception in TypeParser._get_option_text")returnFalsedef_preprocess_source(self,ctxt,source,fileName,platform_,existingTypes,options,optionCount,includeDirs,includeDirCount,output,errors,errorCount)->bool:try:source_py=core.pyNativeStr(source)file_name_py=core.pyNativeStr(fileName)platform_py=platform.CorePlatform._from_cache(handle=core.BNNewPlatformReference(platform_))existing_types_py=NoneifexistingTypes:existing_types_py=typecontainer.TypeContainer(handle=core.BNDuplicateTypeContainer(existingTypes))options_py=[]foriinrange(optionCount):options_py.append(core.pyNativeStr(options[i]))include_dirs_py=[]foriinrange(includeDirCount):include_dirs_py.append(core.pyNativeStr(includeDirs[i]))(output_py,errors_py)=self.preprocess_source(source_py,file_name_py,platform_py,existing_types_py,options_py,include_dirs_py)ifoutput_pyisnotNoneandoutputisnotNone:TypeParser._cached_string=core.cstr(output_py)output[0]=TypeParser._cached_stringiferrorCountisnotNone:errorCount[0]=len(errors_py)iferrorsisnotNone:errors_out=(core.BNTypeParserError*len(errors_py))()foriinrange(len(errors_py)):errors_out[i]=errors_py[i]._to_core_struct()TypeParser._cached_error=errors_outerrors[0]=errors_outreturnoutput_pyisnotNoneexcept:errorCount[0]=0log_error_for_exception("Unhandled Python exception in TypeParser._preprocess_source")returnFalsedef_parse_types_from_source(self,ctxt,source,fileName,platform_,existingTypes,options,optionCount,includeDirs,includeDirCount,autoTypeSource,result,errors,errorCount)->bool:try:source_py=core.pyNativeStr(source)file_name_py=core.pyNativeStr(fileName)platform_py=platform.CorePlatform._from_cache(handle=core.BNNewPlatformReference(platform_))existing_types_py=NoneifexistingTypes:existing_types_py=typecontainer.TypeContainer(handle=core.BNDuplicateTypeContainer(existingTypes))options_py=[]foriinrange(optionCount):options_py.append(core.pyNativeStr(options[i]))include_dirs_py=[]foriinrange(includeDirCount):include_dirs_py.append(core.pyNativeStr(includeDirs[i]))auto_type_source=core.pyNativeStr(autoTypeSource)(result_py,errors_py)=self.parse_types_from_source(source_py,file_name_py,platform_py,existing_types_py,options_py,include_dirs_py,auto_type_source)ifresult_pyisnotNoneandresultisnotNone:result_struct=result_py._to_core_struct()TypeParser._cached_result=result_structresult[0]=result_structiferrorCountisnotNone:errorCount[0]=len(errors_py)iferrorsisnotNone:errors_out=(core.BNTypeParserError*len(errors_py))()foriinrange(len(errors_py)):errors_out[i]=errors_py[i]._to_core_struct()TypeParser._cached_error=errors_outerrors[0]=errors_outreturnresult_pyisnotNoneexcept:result[0].typeCount=0result[0].variableCount=0result[0].functionCount=0errorCount[0]=0log_error_for_exception("Unhandled Python exception in TypeParser._parse_types_from_source")returnFalsedef_parse_type_string(self,ctxt,source,platform_,existingTypes,result,errors,errorCount)->bool:try:source_py=core.pyNativeStr(source)platform_py=platform.CorePlatform._from_cache(handle=core.BNNewPlatformReference(platform_))existing_types_py=NoneifexistingTypes:existing_types_py=typecontainer.TypeContainer(handle=core.BNDuplicateTypeContainer(existingTypes))(result_py,errors_py)=self.parse_type_string(source_py,platform_py,existing_types_py)ifresult_pyisnotNoneandresultisnotNone:result[0].name=types.QualifiedName(result_py[0])._to_core_struct()result[0].type=core.BNNewTypeReference(result_py[1].handle)iferrorCountisnotNone:errorCount[0]=len(errors_py)iferrorsisnotNone:errors_out=(core.BNTypeParserError*len(errors_py))()foriinrange(len(errors_py)):errors_out[i]=errors_py[i]._to_core_struct()TypeParser._cached_error=errors_outerrors[0]=errors_outreturnresult_pyisnotNoneexcept:errorCount[0]=0log_error_for_exception("Unhandled Python exception in TypeParser._parse_type_string")returnFalsedef_free_string(self,ctxt,string)->bool:try:TypeParser._cached_string=NonereturnTrueexcept:log_error_for_exception("Unhandled Python exception in TypeParser._free_string")returnFalsedef_free_result(self,ctxt,result)->bool:try:ifTypeParser._cached_resultisnotNone:foriinrange(TypeParser._cached_result.typeCount):core.BNFreeType(TypeParser._cached_result.types[i].type)foriinrange(TypeParser._cached_result.variableCount):core.BNFreeType(TypeParser._cached_result.variables[i].type)foriinrange(TypeParser._cached_result.functionCount):core.BNFreeType(TypeParser._cached_result.functions[i].type)TypeParser._cached_result=NonereturnTrueexcept:log_error_for_exception("Unhandled Python exception in TypeParser._free_result")returnFalsedef_free_error_list(self,ctxt,errors,errorCount)->bool:try:TypeParser._cached_error=NonereturnTrueexcept:log_error_for_exception("Unhandled Python exception in TypeParser._free_error_list")returnFalse[docs]defget_option_text(self,option:TypeParserOption,value:str)->Optional[str]:"""Get the string representation of an option for passing to parse_type_*:param option: Option type:param value: Option value:return: A string representing the option if the parser supports it, otherwise None"""returnNone [docs]defpreprocess_source(self,source:str,file_name:str,platform:'platform.Platform',existing_types:Optional['types.TypeContainerType']=None,options:Optional[List[str]]=None,include_dirs:Optional[List[str]]=None)->Tuple[Optional[str],List[TypeParserError]]:"""Preprocess a block of source, returning the source that would be parsed:param source: Source code to process:param file_name: Name of the file containing the source (does not need to exist on disk):param platform: Platform to assume the source is relevant to:param existing_types: Optional collection of all existing types to use for parsing context:param options: Optional string arguments to pass as options, e.g. command line arguments:param include_dirs: Optional list of directories to include in the header search path:return: A tuple of (preproccessed source, errors), where the preproccessed source is None if there was a fatal error."""raiseNotImplementedError("Not implemented") [docs]defparse_types_from_source(self,source:str,file_name:str,platform:'platform.Platform',existing_types:Optional['types.TypeContainerType']=None,options:Optional[List[str]]=None,include_dirs:Optional[List[str]]=None,auto_type_source:str="")->Tuple[Optional[TypeParserResult],List[TypeParserError]]:"""Parse an entire block of source into types, variables, and functions:param source: Source code to parse:param file_name: Name of the file containing the source (optional: exists on disk):param platform: Platform to assume the types are relevant to:param existing_types: Optional container of all existing types to use for parsing context:param options: Optional string arguments to pass as options, e.g. command line arguments:param include_dirs: Optional list of directories to include in the header search path:param auto_type_source: Optional source of types if used for automatically generated types:return: A tuple of (result, errors) where the result is None if there was a fatal error"""raiseNotImplementedError("Not implemented") [docs]defparse_type_string(self,source:str,platform:'platform.Platform',existing_types:Optional['types.TypeContainerType']=None)->Tuple[Optional[Tuple['types.QualifiedNameType','types.Type']],List[TypeParserError]]:"""Parse a single type and name from a string containing their definition.:param source: Source code to parse:param platform: Platform to assume the types are relevant to:param existing_types: Optional container of all existing types to use for parsing context:return: A tuple of (result, errors) where result is a tuple of (type, name) or None of there was a fatal error."""raiseNotImplementedError("Not implemented") [docs]classCoreTypeParser(TypeParser):[docs]defget_option_text(self,option:TypeParserOption,value:str)->Optional[str]:result_cpp=ctypes.c_char_p()ifnotcore.BNGetTypeParserOptionText(self.handle,option,value,result_cpp):returnNoneresult=core.pyNativeStr(result_cpp.value)core.free_string(result_cpp)returnresult [docs]defpreprocess_source(self,source:str,file_name:str,platform:'platform.Platform',existing_types:Optional['types.TypeContainerType']=None,options:Optional[List[str]]=None,include_dirs:Optional[List[str]]=None)->Tuple[Optional[str],List[TypeParserError]]:ifoptionsisNone:options=[]ifinclude_dirsisNone:include_dirs=[]existing_types_cpp=Noneifexisting_typesisnotNone:ifisinstance(existing_types,(binaryview.BinaryView,)):existing_types_cpp=existing_types.type_containerelifisinstance(existing_types,(typecontainer.TypeContainer,)):existing_types_cpp=existing_typeselse:assertFalse,"Unexpected type container type"options_cpp=(ctypes.c_char_p*len(options))()for(i,s)inenumerate(options):options_cpp[i]=core.cstr(s)include_dirs_cpp=(ctypes.c_char_p*len(include_dirs))()for(i,s)inenumerate(include_dirs):include_dirs_cpp[i]=core.cstr(s)output_cpp=ctypes.c_char_p()errors_cpp=ctypes.POINTER(core.BNTypeParserError)()error_count=ctypes.c_size_t()success=core.BNTypeParserPreprocessSource(self.handle,source,file_name,platform.handle,existing_types_cpp.handleifexisting_types_cppisnotNoneelseNone,options_cpp,len(options),include_dirs_cpp,len(include_dirs),output_cpp,errors_cpp,error_count)ifsuccess:output=core.pyNativeStr(output_cpp.value)core.free_string(output_cpp)else:output=Noneerrors=[]foriinrange(error_count.value):errors.append(TypeParserError._from_core_struct(errors_cpp[i]))core.BNFreeTypeParserErrors(errors_cpp,error_count.value)returnoutput,errors [docs]defparse_types_from_source(self,source:str,file_name:str,platform:'platform.Platform',existing_types:Optional['types.TypeContainerType']=None,options:Optional[List[str]]=None,include_dirs:Optional[List[str]]=None,auto_type_source:str="")->Tuple[Optional[TypeParserResult],List[TypeParserError]]:ifoptionsisNone:options=[]ifinclude_dirsisNone:include_dirs=[]existing_types_cpp=Noneifexisting_typesisnotNone:ifisinstance(existing_types,(binaryview.BinaryView,)):existing_types_cpp=existing_types.type_containerelifisinstance(existing_types,(typecontainer.TypeContainer,)):existing_types_cpp=existing_typeselse:assertFalse,"Unexpected type container type"options_cpp=(ctypes.c_char_p*len(options))()for(i,s)inenumerate(options):options_cpp[i]=core.cstr(s)include_dirs_cpp=(ctypes.c_char_p*len(include_dirs))()for(i,s)inenumerate(include_dirs):include_dirs_cpp[i]=core.cstr(s)result_cpp=core.BNTypeParserResult()errors_cpp=ctypes.POINTER(core.BNTypeParserError)()error_count=ctypes.c_size_t()success=core.BNTypeParserParseTypesFromSource(self.handle,source,file_name,platform.handle,existing_types_cpp.handleifexisting_types_cppisnotNoneelseNone,options_cpp,len(options),include_dirs_cpp,len(include_dirs),auto_type_source,result_cpp,errors_cpp,error_count)ifsuccess:result=TypeParserResult._from_core_struct(result_cpp)else:result=Nonecore.BNFreeTypeParserResult(result_cpp)errors=[]foriinrange(error_count.value):errors.append(TypeParserError._from_core_struct(errors_cpp[i]))core.BNFreeTypeParserErrors(errors_cpp,error_count.value)returnresult,errors [docs]defparse_type_string(self,source:str,platform:'platform.Platform',existing_types:Optional['types.TypeContainerType']=None)->Tuple[Optional[Tuple['types.QualifiedNameType','types.Type']],List[TypeParserError]]:existing_types_cpp=Noneifexisting_typesisnotNone:ifisinstance(existing_types,(binaryview.BinaryView,)):existing_types_cpp=existing_types.type_containerelifisinstance(existing_types,(typecontainer.TypeContainer,)):existing_types_cpp=existing_typeselse:assertFalse,"Unexpected type container type"result_cpp=core.BNQualifiedNameAndType()errors_cpp=ctypes.POINTER(core.BNTypeParserError)()error_count=ctypes.c_size_t()success=core.BNTypeParserParseTypeString(self.handle,source,platform.handle,existing_types_cpp.handleifexisting_types_cppisnotNoneelseNone,result_cpp,errors_cpp,error_count)ifsuccess:result=(types.QualifiedName._from_core_struct(result_cpp.name),types.Type.create(handle=core.BNNewTypeReference(result_cpp.type)))core.BNFreeQualifiedNameAndType(result_cpp)else:result=Noneerrors=[]foriinrange(error_count.value):errors.append(TypeParserError._from_core_struct(errors_cpp[i]))core.BNFreeTypeParserErrors(errors_cpp,error_count.value)returnresult,errors