Skip to content

Commit ae59f91

Browse files
committed
Add API for type libraries to declare named types without defining them
The C++, Python and Rust APIs can now declare a named type in a type library without defining it, and list the types a library declares. Previously Python's `NamedTypeReferenceType.target` and Rust's `NamedTypeReference::target` looked up a reference only by its type id. A reference to a declared type has no type under its id, so they now fall back to the reference's name, matching core. Python gains `BinaryView.get_type_by_ref` to do this. Python's cycle detection when following references recorded the type object rather than its id, and never recorded the starting id. A cycle recursed until Python raised `RecursionError`. It now raises `TypeError`.
1 parent 1324067 commit ae59f91

10 files changed

Lines changed: 317 additions & 17 deletions

File tree

‎binaryninjaapi.h‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22165,6 +22165,12 @@ namespace BinaryNinja {
2216522165
*/
2216622166
std::vector<QualifiedNameAndType> GetNamedTypes();
2216722167

22168+
/*! The type class a type library declares for \c name, or std::nullopt if it does not declare \c name. */
22169+
std::optional<BNNamedTypeReferenceClass> GetNamedTypeDeclaration(const QualifiedName& name);
22170+
22171+
/*! The named types a type library declares without defining, and the type class of each. */
22172+
std::vector<std::pair<QualifiedName, BNNamedTypeReferenceClass>> GetNamedTypeDeclarations();
22173+
2216822174
/*! Sets the name of a type library instance that has not been finalized
2216922175

2217022176
\param name
@@ -22253,6 +22259,25 @@ namespace BinaryNinja {
2225322259
*/
2225422260
void AddNamedType(const QualifiedName& name, Ref<Type> type);
2225522261

22262+
/*! Declares a named type without defining it, as C does with <tt>struct name;</tt>. Use it for a type
22263+
the library refers to only through pointers, such as an opaque struct whose layout comes from
22264+
the binary or another source. Importing the type into a view does not define it there. References
22265+
to it resolve to the view's type of that name and follow later renames. Defining a declared name
22266+
removes the declaration, and declaring a defined name does nothing.
22267+
22268+
\param name
22269+
\param typeClass The kind of type the name is: StructNamedTypeClass, ClassNamedTypeClass,
22270+
UnionNamedTypeClass or EnumNamedTypeClass. AddNamedTypeDeclaration logs an error and ignores other
22271+
classes.
22272+
*/
22273+
void AddNamedTypeDeclaration(const QualifiedName& name, BNNamedTypeReferenceClass typeClass);
22274+
22275+
/*! Removes a declaration made with AddNamedTypeDeclaration
22276+
22277+
\param name
22278+
*/
22279+
void RemoveNamedTypeDeclaration(const QualifiedName& name);
22280+
2225622281
/*! Manually flag NamedTypeReferences to the given QualifiedName as originating from another source
2225722282
TypeLibrary with the given dependency name.
2225822283

‎binaryninjacore.h‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2842,6 +2842,12 @@ extern "C"
28422842
BNType* type;
28432843
} BNQualifiedNameAndType;
28442844

2845+
typedef struct BNTypeLibraryNamedTypeDeclaration
2846+
{
2847+
BNQualifiedName name;
2848+
BNNamedTypeReferenceClass typeClass;
2849+
} BNTypeLibraryNamedTypeDeclaration;
2850+
28452851
typedef struct BNQualifiedNameTypeAndId
28462852
{
28472853
BNQualifiedName name;
@@ -7844,6 +7850,16 @@ extern "C"
78447850
BINARYNINJACOREAPI BNQualifiedNameAndType* BNGetTypeLibraryNamedObjects(BNTypeLibrary* lib, size_t* count);
78457851
BINARYNINJACOREAPI BNQualifiedNameAndType* BNGetTypeLibraryNamedTypes(BNTypeLibrary* lib, size_t* count);
78467852

7853+
BINARYNINJACOREAPI void BNAddTypeLibraryNamedTypeDeclaration(
7854+
BNTypeLibrary* lib, BNQualifiedName* name, BNNamedTypeReferenceClass typeClass);
7855+
BINARYNINJACOREAPI void BNRemoveTypeLibraryNamedTypeDeclaration(BNTypeLibrary* lib, BNQualifiedName* name);
7856+
BINARYNINJACOREAPI bool BNGetTypeLibraryNamedTypeDeclaration(
7857+
BNTypeLibrary* lib, BNQualifiedName* name, BNNamedTypeReferenceClass* typeClass);
7858+
BINARYNINJACOREAPI BNTypeLibraryNamedTypeDeclaration* BNGetTypeLibraryNamedTypeDeclarations(
7859+
BNTypeLibrary* lib, size_t* count);
7860+
BINARYNINJACOREAPI void BNFreeTypeLibraryNamedTypeDeclarationList(
7861+
BNTypeLibraryNamedTypeDeclaration* declarations, size_t count);
7862+
78477863
BINARYNINJACOREAPI bool BNWriteTypeLibraryToFile(BNTypeLibrary* lib, const char* path);
78487864

78497865
BINARYNINJACOREAPI void BNAddBinaryViewTypeLibrary(BNBinaryView* view, BNTypeLibrary* lib);

‎plugins/bntl_utils/src/process.rs‎

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,11 @@ impl ProcessedData {
157157
///
158158
/// This is useful if you intend to save the type libraries to disk in a finalized form.
159159
pub fn prune(self) -> Self {
160-
let is_empty =
161-
|tl: &TypeLibrary| tl.named_types().is_empty() && tl.named_objects().is_empty();
160+
let is_empty = |tl: &TypeLibrary| {
161+
tl.named_types().is_empty()
162+
&& tl.named_type_declarations().is_empty()
163+
&& tl.named_objects().is_empty()
164+
};
162165
let pruned_type_libraries = self
163166
.type_libraries
164167
.into_iter()
@@ -205,6 +208,11 @@ impl ProcessedData {
205208
for named_type in &tl.named_types() {
206209
merged_type_library.add_named_type(named_type.name.clone(), &named_type.ty);
207210
}
211+
// A definition from any library wins over a declaration, whatever the merge order.
212+
for declaration in &tl.named_type_declarations() {
213+
merged_type_library
214+
.add_named_type_declaration(declaration.name, declaration.type_class);
215+
}
208216
for named_object in &tl.named_objects() {
209217
merged_type_library
210218
.add_named_object(named_object.name.clone(), &named_object.ty);
@@ -298,7 +306,10 @@ impl ProcessedData {
298306
// we can relocate them as well, assuming no other type library also uses it.
299307
let is_associated = default_type_library
300308
.get_named_type(ntr_name.clone())
301-
.is_some();
309+
.is_some()
310+
|| default_type_library
311+
.get_named_type_declaration(ntr_name.clone())
312+
.is_some();
302313
if is_associated {
303314
recorded_references
304315
.entry(ntr_name)
@@ -327,10 +338,14 @@ impl ProcessedData {
327338
if type_libraries.len() == 1 {
328339
// Only one type library uses this type, so we can safely relocate it.
329340
let type_library = type_libraries.iter().next().unwrap();
330-
let named_ty = default_type_library
331-
.get_named_type(qualified_name.clone())
332-
.unwrap();
333-
type_library.add_named_type(qualified_name, &named_ty);
341+
if let Some(named_ty) = default_type_library.get_named_type(qualified_name.clone())
342+
{
343+
type_library.add_named_type(qualified_name, &named_ty);
344+
} else if let Some(type_class) =
345+
default_type_library.get_named_type_declaration(qualified_name.clone())
346+
{
347+
type_library.add_named_type_declaration(qualified_name, type_class);
348+
}
334349
}
335350
}
336351
}
@@ -738,6 +753,14 @@ impl TypeLibProcessor {
738753
for t in tl.named_types().iter() {
739754
type_name_to_library.insert(t.name.clone(), lib_name.clone());
740755
}
756+
// Map declared names to the declaring library so the library being built records a type
757+
// source instead of copying the view's definition. A library that defines the name takes
758+
// precedence.
759+
for declaration in &tl.named_type_declarations() {
760+
type_name_to_library
761+
.entry(declaration.name)
762+
.or_insert_with(|| lib_name.clone());
763+
}
741764
}
742765

743766
let add_referenced_types = |type_library: &TypeLibrary, ty: &Type| {
@@ -1309,4 +1332,42 @@ mod tests {
13091332
assert_eq!(merged_tl.platform_names().len(), 2);
13101333
assert_eq!(merged_tl.named_types().len(), 2);
13111334
}
1335+
1336+
#[test]
1337+
fn test_data_merging_keeps_declarations() {
1338+
use binaryninja::types::{NamedTypeDeclaration, NamedTypeReferenceClass};
1339+
1340+
let _session =
1341+
binaryninja::headless::Session::new().expect("Failed to create headless session");
1342+
let x86_platform = Platform::by_name("x86").expect("Failed to get x86 platform");
1343+
let tl1 = TypeLibrary::new(x86_platform.arch(), "foo");
1344+
tl1.set_dependency_name("foo");
1345+
tl1.add_platform(&x86_platform);
1346+
tl1.add_named_type_declaration(
1347+
"declared".into(),
1348+
NamedTypeReferenceClass::StructNamedTypeClass,
1349+
);
1350+
tl1.add_named_type_declaration(
1351+
"defined".into(),
1352+
NamedTypeReferenceClass::StructNamedTypeClass,
1353+
);
1354+
let tl1_data = ProcessedData::new(vec![tl1]);
1355+
1356+
let tl2 = TypeLibrary::new(x86_platform.arch(), "bar");
1357+
tl2.set_dependency_name("foo");
1358+
tl2.add_platform(&x86_platform);
1359+
tl2.add_named_type("defined".into(), &Type::named_int(4, false, "defined"));
1360+
let tl2_data = ProcessedData::new(vec![tl2]);
1361+
1362+
let merged_data = ProcessedData::merge(&[tl1_data, tl2_data]);
1363+
let merged_tl = &merged_data.type_libraries.iter().next().unwrap();
1364+
assert_eq!(
1365+
merged_tl.named_type_declarations().to_vec(),
1366+
vec![NamedTypeDeclaration {
1367+
name: "declared".into(),
1368+
type_class: NamedTypeReferenceClass::StructNamedTypeClass,
1369+
}]
1370+
);
1371+
assert!(merged_tl.get_named_type("defined".into()).is_some());
1372+
}
13121373
}

‎plugins/bntl_utils/src/validate.rs‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ impl TypeLibValidater {
160160
for ty in &type_lib.named_types() {
161161
self.valid_external_references.insert(ty.name);
162162
}
163+
for declaration in &type_lib.named_type_declarations() {
164+
self.valid_external_references.insert(declaration.name);
165+
}
163166
for obj in &type_lib.named_objects() {
164167
self.valid_external_references.insert(obj.name);
165168
}
@@ -247,11 +250,14 @@ impl TypeLibValidater {
247250

248251
for ty in &types {
249252
let qualified_name = QualifiedName::from(ty);
250-
let is_named_ty = dep_type_lib
253+
let is_resolved = dep_type_lib
251254
.get_named_type(qualified_name.clone())
252-
.is_none();
253-
let is_named_obj = dep_type_lib.get_named_object(qualified_name).is_none();
254-
if !is_named_ty && !is_named_obj {
255+
.is_some()
256+
|| dep_type_lib
257+
.get_named_type_declaration(qualified_name.clone())
258+
.is_some()
259+
|| dep_type_lib.get_named_object(qualified_name).is_some();
260+
if !is_resolved {
255261
issues.push(ValidateIssue::UnresolvedSourceReference {
256262
name: ty.to_string(),
257263
source: src.to_string(),

‎python/binaryview.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8986,6 +8986,20 @@ def get_type_by_id(self, id: str) -> Optional['_types.Type']:
89868986
return None
89878987
return _types.Type.create(obj, platform=self.platform)
89888988

8989+
def get_type_by_ref(self, ref: '_types.NamedTypeReferenceType') -> Optional['_types.Type']:
8990+
"""
8991+
``get_type_by_ref`` returns the type that ``ref`` refers to. It looks up the reference's id first,
8992+
then the reference's name.
8993+
8994+
:param NamedTypeReferenceType ref: Reference to resolve
8995+
:return: A :py:class:`Type` or None if no type is defined for the reference
8996+
:rtype: Type or None
8997+
"""
8998+
obj = core.BNGetAnalysisTypeByRef(self.handle, ref.ntr_handle)
8999+
if not obj:
9000+
return None
9001+
return _types.Type.create(obj, platform=self.platform)
9002+
89899003
def get_type_name_by_id(self, id: str) -> Optional['_types.QualifiedName']:
89909004
"""
89919005
``get_type_name_by_id`` returns the defined type name whose unique identifier corresponds with the provided ``id``

‎python/typelibrary.py‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from . import platform
3131
from . import architecture
3232
from . import typecontainer
33+
from .enums import NamedTypeReferenceClass
3334

3435

3536
class TypeLibrary:
@@ -428,6 +429,61 @@ def remove_named_type(self, name: Union[types.QualifiedName, str]) -> None:
428429
name = types.QualifiedName(name)
429430
core.BNRemoveTypeLibraryNamedType(self.handle, name._to_core_struct())
430431

432+
def add_named_type_declaration(
433+
self, name: Union[types.QualifiedName, str], type_class: NamedTypeReferenceClass
434+
) -> None:
435+
"""
436+
`add_named_type_declaration` declares a named type without defining it, as C does with ``struct name;``.
437+
Use it for a type the library refers to only through pointers, such as an opaque struct whose layout comes
438+
from the binary or another source. Importing the type into a view does not define it there. References to it
439+
resolve to the view's type of that name and follow later renames. Defining a declared name removes the
440+
declaration, and declaring a defined name does nothing.
441+
442+
:param QualifiedName name:
443+
:param NamedTypeReferenceClass type_class: the kind of type the name is: ``StructNamedTypeClass``,
444+
``ClassNamedTypeClass``, ``UnionNamedTypeClass`` or ``EnumNamedTypeClass``. `add_named_type_declaration`
445+
logs an error and ignores other classes.
446+
"""
447+
if not isinstance(name, types.QualifiedName):
448+
name = types.QualifiedName(name)
449+
core.BNAddTypeLibraryNamedTypeDeclaration(self.handle, name._to_core_struct(), type_class)
450+
451+
def remove_named_type_declaration(self, name: Union[types.QualifiedName, str]) -> None:
452+
"""
453+
`remove_named_type_declaration` removes a declaration made with :py:meth:`add_named_type_declaration`.
454+
"""
455+
if not isinstance(name, types.QualifiedName):
456+
name = types.QualifiedName(name)
457+
core.BNRemoveTypeLibraryNamedTypeDeclaration(self.handle, name._to_core_struct())
458+
459+
def get_named_type_declaration(self, name: Union[types.QualifiedName, str]) -> Optional[NamedTypeReferenceClass]:
460+
"""
461+
`get_named_type_declaration` returns the type class the type library declares for ``name``, or None if it
462+
does not declare ``name``.
463+
"""
464+
if not isinstance(name, types.QualifiedName):
465+
name = types.QualifiedName(name)
466+
type_class = core.NamedTypeReferenceClassEnum()
467+
if not core.BNGetTypeLibraryNamedTypeDeclaration(self.handle, name._to_core_struct(), type_class):
468+
return None
469+
return NamedTypeReferenceClass(type_class.value)
470+
471+
@property
472+
def named_type_declarations(self) -> Dict[types.QualifiedName, NamedTypeReferenceClass]:
473+
"""
474+
A dict of the named types the type library declares without defining, and the type class of each (read-only)
475+
"""
476+
count = ctypes.c_ulonglong(0)
477+
declarations = core.BNGetTypeLibraryNamedTypeDeclarations(self.handle, count)
478+
assert declarations is not None, "core.BNGetTypeLibraryNamedTypeDeclarations returned None"
479+
try:
480+
return {
481+
types.QualifiedName._from_core_struct(declarations[i].name): NamedTypeReferenceClass(declarations[i].typeClass)
482+
for i in range(count.value)
483+
}
484+
finally:
485+
core.BNFreeTypeLibraryNamedTypeDeclarationList(declarations, count.value)
486+
431487
def add_type_source(self, name: Union[types.QualifiedName, str], source: str) -> None:
432488
"""
433489
Manually flag NamedTypeReferences to the given QualifiedName as originating from another source

‎python/types.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3801,15 +3801,15 @@ def generate_auto_demangled_type_ref(type_class: NamedTypeReferenceClass, name:
38013801
return NamedTypeReferenceType.create(type_class, type_id, name)
38023802

38033803
def _target_helper(self, bv: 'binaryview.BinaryView', type_ids=None) -> Optional[Type]:
3804-
t = bv.get_type_by_id(self.type_id)
3804+
t = bv.get_type_by_ref(self)
38053805
if t is None:
38063806
return None
38073807
if type_ids is None:
3808-
type_ids = set()
3808+
type_ids = {self.type_id}
38093809
if isinstance(t, NamedTypeReferenceType):
38103810
if t.type_id in type_ids:
38113811
raise TypeError("Can't get target for recursively defined type")
3812-
type_ids.add(t)
3812+
type_ids.add(t.type_id)
38133813
return t._target_helper(bv, type_ids)
38143814
else:
38153815
return t

‎rust/src/types.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ use std::{
5757
pub use archive::{TypeArchive, TypeArchiveId, TypeArchiveSnapshotId};
5858
pub use container::TypeContainer;
5959
pub use enumeration::{Enumeration, EnumerationBuilder, EnumerationMember};
60-
pub use library::TypeLibrary;
60+
pub use library::{NamedTypeDeclaration, TypeLibrary};
6161
pub use parser::{
6262
CoreTypeParser, ParsedType, TypeParser, TypeParserError, TypeParserErrorSeverity,
6363
TypeParserResult,
@@ -1627,7 +1627,7 @@ impl NamedTypeReference {
16271627
}
16281628

16291629
fn target_helper(&self, bv: &BinaryView, visited: &mut HashSet<String>) -> Option<Ref<Type>> {
1630-
let ty = bv.type_by_id(&self.id())?;
1630+
let ty = bv.type_by_ref(self)?;
16311631
match ty.type_class() {
16321632
TypeClass::NamedTypeReferenceClass => {
16331633
// Recurse into the NTR type until we get the target type.

0 commit comments

Comments
 (0)