// // MachOModel.m // MacDependency // // Created by Konrad Windszus on 13.07.09. // Copyright 2009 Konrad Windszus. All rights reserved. // #import "MachOModel.h" #import "MyDocument.h" #import "ConversionStdString.h" #import "VersionFormatter.h" #import "ArchitectureModel.h" #include "MachO/machoexception.h" #include "MachO/machoheader.h" #include "MachO/symboltablecommand.h" // declare private methods @interface MachOModel () - (void) initChildren; - (void) checkConstraints:(DylibCommand*) dylibId; - (void) setStateWithWarning:(BOOL)isWarning; - (id) initWithFile:(MachO*)file command:(DylibCommand*)command document:(MyDocument*)document parent:(MachOModel*)parent architecture:(MachOArchitecture*)architecture; @end @implementation MachOModel - (id) initWithFile:(MachO*)aFile document:(MyDocument*)aDocument architecture:(MachOArchitecture*)anArchitecture loadChildren:(BOOL)loadChildren { state = StateNormal; if (!(self = [self initWithFile:aFile command:nil document:aDocument parent:nil architecture:anArchitecture])) return nil; if (loadChildren) { [self initChildren]; } return self; } // called by initChildren (calls initWithFile internally) - (id) initWithFilename:(std::string&)filename command:(DylibCommand*)aCommand document:(MyDocument*)aDocument parent:(MachOModel*)aParent { BOOL isWeakReference = (command && !command->isNecessary()); MachO* aFile = nullptr; MachOArchitecture* anArchitecture = nullptr; state = StateNormal; try { aFile = [aDocument cache]->getFile(filename, aParent->file); // throws exception in case file is not found anArchitecture = aFile->getCompatibleArchitecture(aParent->architecture); if (!anArchitecture) { [self setStateWithWarning:isWeakReference]; NSString* log = [NSString stringWithFormat:NSLocalizedString(@"ERR_ARCHITECTURE_MISMATCH", nil), filename.c_str(), [parent name]]; [aDocument appendLogLine:log withModel:self state:state]; } } catch (MachOException& exc) { [self setStateWithWarning:isWeakReference]; NSString* log = [NSString stringWithStdString:exc.getCause()]; [aDocument appendLogLine:log withModel:self state:state]; // distinguish between weak and strong. In both cases append to tree with a status color } [aDocument incrementNumDependencies]; if (!(self = [self initWithFile:aFile command:aCommand document:aDocument parent:aParent architecture:anArchitecture])) return nil; return self; } // private - (id) initWithFile:(MachO*)aFile command:(DylibCommand*)aCommand document:(MyDocument*)aDocument parent:(MachOModel*)aParent architecture:(MachOArchitecture*)anArchitecture { if (self = [super init]) { document = aDocument; parent = aParent; command = aCommand; file = aFile; children = nil; architecture = anArchitecture; if (architecture) { DylibCommand* dylibId = architecture->getDynamicLibIdCommand(); if (dylibId && command) { [self checkConstraints:dylibId]; } } } return self; } - (void) setStateWithWarning:(BOOL)isWarning { State newState = StateError; if (isWarning) { newState = StateWarning; } if (self->state < newState) { self->state = newState; } } - (id) parent { return parent; } - (void) checkConstraints:(DylibCommand*) dylibId { // check version information (only compatible version information is relevant) unsigned int minVersion = dylibId->getCompatibleVersion(); unsigned int requestedMinVersion = command->getCompatibleVersion(); unsigned int requestedMaxVersion = command->getCurrentVersion(); VersionFormatter* versionFormatter = [[VersionFormatter alloc] init]; BOOL isWeakReference = (command && !command->isNecessary()); NSString* log; // check minimum version if (minVersion != 0 && requestedMinVersion != 0 && minVersion < requestedMinVersion) { [self setStateWithWarning:isWeakReference]; log = [NSString stringWithFormat:NSLocalizedString(@"ERR_MINIMUM_VERSION", nil), parent->command->getName().c_str(), [parent name], [versionFormatter stringForObjectValue:[NSNumber numberWithUnsignedInt:requestedMinVersion]], [versionFormatter stringForObjectValue:[NSNumber numberWithUnsignedInt:minVersion]]]; [document appendLogLine:log withModel:self state:state]; } // extended checks which are currently not done by dyld // check maximum version if (minVersion != 0 && requestedMaxVersion != 0 && minVersion > requestedMaxVersion) { [self setStateWithWarning:YES]; log = [NSString stringWithFormat:NSLocalizedString(@"ERR_MAXIMUM_VERSION", nil), command->getName().c_str(), [parent name], [versionFormatter stringForObjectValue:[NSNumber numberWithUnsignedInt:requestedMaxVersion]], [versionFormatter stringForObjectValue:[NSNumber numberWithUnsignedInt:minVersion]]]; [document appendLogLine:log withModel:self state:state]; } // check names if (dylibId->getName() != command->getName()) { [self setStateWithWarning:YES]; log = [NSString stringWithFormat:NSLocalizedString(@"ERR_NAME_MISMATCH", nil), command->getName().c_str(), [parent name], dylibId->getName().c_str()]; [document appendLogLine:log withModel:self state:state]; } // TODO: check for relative paths, since they can lead to problems } - (NSArray*)children { if (children == nil) { [self initChildren]; } return children; } - (void) initChildren { // TODO: tweak capacity children = [NSMutableArray arrayWithCapacity:20]; if (architecture) { std::string workingDirectory = [[document workingDirectory] stdString]; for (MachOArchitecture::LoadCommandsConstIterator it = architecture->getLoadCommandsBegin(); it != architecture->getLoadCommandsEnd(); ++it) { LoadCommand* childLoadCommand = (*it); // check if it is dylibcommand DylibCommand* dylibCommand = dynamic_cast (childLoadCommand); if (dylibCommand != NULL && !dylibCommand->isId()) { std::string filename = architecture->getResolvedName(dylibCommand->getName(), workingDirectory); [children addObject: [[MachOModel alloc] initWithFilename: filename command: dylibCommand document: document parent: self]]; } } } } - (BOOL)isLeaf { [self children]; if ([children count] == 0) return YES; return NO; } - (NSColor*) textColor { NSColor* color; switch(state) { case StateWarning: color = [NSColor blueColor]; break; case StateError: color = [NSColor redColor]; break; default: color= [NSColor blackColor]; } return color; } - (NSString*) filename { std::string filename; if (file) filename = file->getFileName(); return [NSString stringWithStdString:filename]; } - (NSString*) version { if (file) { return [NSString stringWithStdString:file->getVersion()]; } return @""; } - (NSNumber*) size { if (file != nullptr) return @(file->getSize()); return @(0); } - (NSString*) name { std::string name; if (command) { name = command->getName(); } else if (file) { name = file->getName(); } return [NSString stringWithStdString:name]; } - (NSNumber*) currentVersion { if (command) { return [NSNumber numberWithUnsignedInt:command->getCurrentVersion()]; } return [NSNumber numberWithUnsignedInt:0]; } - (NSNumber*) compatibleVersion { if (command) { return [NSNumber numberWithUnsignedInt:command->getCompatibleVersion()]; } return [NSNumber numberWithUnsignedInt:0]; } - (NSDate*) lastModificationTime { if (file) return [NSDate dateWithTimeIntervalSince1970:file->getLastModificationTime()]; return NULL; } - (NSString*) dependencyType { NSString* type; if (!command) { return @""; } switch(command->getType()) { case DylibCommand::DependencyWeak: type = NSLocalizedString(@"DEPENDENCY_TYPE_WEAK", nil); break; case DylibCommand::DependencyDelayed: type = NSLocalizedString(@"DEPENDENCY_TYPE_DELAYED", nil); break; case DylibCommand::DependencyNormal: type = NSLocalizedString(@"DEPENDENCY_TYPE_NORMAL", nil); break; default: type = NSLocalizedString(@"UNDEFINED", @"Unknown"); } return type; } - (NSArray*) architectures { NSMutableArray* architectures = [NSMutableArray arrayWithCapacity:4]; if (file) { for (MachO::MachOArchitecturesIterator iter = file->getArchitecturesBegin(); iter != file->getArchitecturesEnd(); iter++) { // create model for architecture ArchitectureModel* currentArchitecture = [[ArchitectureModel alloc]initWithArchitecture:(*iter) file:file document:document isRoot:NO]; // correct order (current architecture should have first index) if ((*iter) == architecture) { [architectures insertObject:currentArchitecture atIndex:0]; // insert at beginning } else { [architectures addObject:currentArchitecture]; // insert at end } } } return architectures; } @end