Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 82 additions & 46 deletions iosMath/lib/MTMathAtomFactory.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@

#import "MTMathAtomFactory.h"
#import "MTMathListBuilder.h"
#import <os/lock.h>

// Lock protecting the two genuinely-mutable symbol tables: `commands` (in
// +supportedLatexSymbols) and `textToCommands` (in +textToLatexSymbolNames).
// Read-only tables are guarded only by dispatch_once and need no run-time lock.
static os_unfair_lock gSymbolTableLock = OS_UNFAIR_LOCK_INIT;

NSString *const MTSymbolMultiplication = @"\u00D7";
NSString *const MTSymbolDivision = @"\u00F7";
Expand Down Expand Up @@ -163,9 +169,13 @@ + (nullable MTMathAtom *)atomForLatexSymbolName:(NSString *)symbolName
// Switch to the canonical name
symbolName = canonicalName;
}

NSDictionary* commands = [self supportedLatexSymbols];
MTMathAtom* atom = commands[symbolName];

// commands is a genuinely-mutable dict (addLatexSymbol: writes it), so guard.
NSMutableDictionary* commands = [self supportedLatexSymbols];
MTMathAtom* atom = nil;
os_unfair_lock_lock(&gSymbolTableLock);
atom = commands[symbolName];
os_unfair_lock_unlock(&gSymbolTableLock);
if (atom) {
// Return a copy of the atom since atoms are mutable.
return [atom copy];
Expand All @@ -178,32 +188,43 @@ + (nullable NSString*) latexSymbolNameForAtom:(MTMathAtom*) atom
if (atom.nucleus.length == 0) {
return nil;
}
NSDictionary<NSString*, NSDictionary<NSNumber*, NSString*>*>* dict = [MTMathAtomFactory textToLatexSymbolNames];
// textToLatexSymbolNames is a genuinely-mutable dict (addLatexSymbol: writes it), and the
// per-nucleus `inner` dict is mutated in place by addLatexSymbol:, so the lock must cover the
// FULL read — both the outer dict[...] lookup and the inner[...] lookups (incl. the Bin
// fallback). Resolve `name` to a local under the lock, then copy it out before unlocking.
NSMutableDictionary* dict = [MTMathAtomFactory textToLatexSymbolNames];
NSString* name = nil;
os_unfair_lock_lock(&gSymbolTableLock);
NSDictionary<NSNumber*, NSString*>* inner = dict[atom.nucleus];
if (!inner) {
return nil;
}
NSString* name = inner[@(atom.type)];
if (name) {
return name;
}
// -[MTMathList finalized] reclassifies leading/orphan/trailing Bin atoms to Un. The
// forward table only ever registers atoms as Bin, so a (nucleus, Un)
// lookup must fall back to the Bin cell to recover the canonical name.
if (atom.type == kMTMathAtomUnaryOperator) {
return inner[@(kMTMathAtomBinaryOperator)];
if (inner) {
name = inner[@(atom.type)];
// -[MTMathList finalized] reclassifies leading/orphan/trailing Bin atoms to Un. The
// forward table only ever registers atoms as Bin, so a (nucleus, Un)
// lookup must fall back to the Bin cell to recover the canonical name.
if (!name && atom.type == kMTMathAtomUnaryOperator) {
name = inner[@(kMTMathAtomBinaryOperator)];
}
}
return nil;
// `name` is an immutable NSString stored in the dict; copy guarantees we don't hold a
// reference into the mutable container after unlocking.
NSString* result = [name copy];
os_unfair_lock_unlock(&gSymbolTableLock);
return result;
}

+ (void)addLatexSymbol:(NSString *)name value:(MTMathAtom *)atom
{
NSParameterAssert(name);
NSParameterAssert(atom);
// Ensure both tables are initialized before taking the lock (dispatch_once
// inside each accessor guarantees a single init; no deadlock since the tokens
// are method-local and the lock is not held during the once-block).
NSMutableDictionary<NSString*, MTMathAtom*>* commands = [self supportedLatexSymbols];
commands[name] = atom;
NSMutableDictionary<NSString*, NSMutableDictionary<NSNumber*, NSString*>*>* dict = [self textToLatexSymbolNames];

os_unfair_lock_lock(&gSymbolTableLock);
commands[name] = [atom copy]; // copy on write — symmetric with the read side
Comment on lines 217 to +226

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In Objective-C, assertions like NSParameterAssert are disabled in release builds. If name or atom is nil at runtime in a release build, attempting to set a value in commands or copying atom will cause an NSInvalidArgumentException crash. Always guard against nil keys and values before modifying an NSMutableDictionary.

    if (!name || !atom) {
        return;
    }
    NSMutableDictionary<NSString*, MTMathAtom*>* commands = [self supportedLatexSymbols];
    NSMutableDictionary<NSString*, NSMutableDictionary<NSNumber*, NSString*>*>* dict = [self textToLatexSymbolNames];

    os_unfair_lock_lock(&gSymbolTableLock);
    commands[name] = [atom copy];
References
  1. Always guard against nil keys before accessing or modifying an NSMutableDictionary (or NSDictionary) in Objective-C to prevent NSInvalidArgumentException crashes.

if (atom.nucleus.length != 0) {
NSMutableDictionary<NSString*, NSMutableDictionary<NSNumber*, NSString*>*>* dict = [self textToLatexSymbolNames];
NSMutableDictionary<NSNumber*, NSString*>* inner = dict[atom.nucleus];
if (!inner) {
inner = [NSMutableDictionary dictionaryWithCapacity:1];
Expand All @@ -216,12 +237,16 @@ + (void)addLatexSymbol:(NSString *)name value:(MTMathAtom *)atom
inner[typeKey] = name;
}
}
os_unfair_lock_unlock(&gSymbolTableLock);
}

+ (NSArray<NSString *> *)supportedLatexSymbolNames
{
NSDictionary<NSString*, MTMathAtom*>* commands = [MTMathAtomFactory supportedLatexSymbols];
return commands.allKeys;
NSMutableDictionary<NSString*, MTMathAtom*>* commands = [MTMathAtomFactory supportedLatexSymbols];
os_unfair_lock_lock(&gSymbolTableLock);
NSArray* keys = commands.allKeys;
os_unfair_lock_unlock(&gSymbolTableLock);
return keys;
}

+ (nullable MTAccent*) accentWithName:(NSString*) accentName
Expand Down Expand Up @@ -272,7 +297,8 @@ + (MTFontStyle)fontStyleWithName:(NSString *)fontName {
+ (NSDictionary<NSString*, NSNumber*>*) textStyles
{
static NSDictionary<NSString*, NSNumber*>* textStyles = nil;
if (!textStyles) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
textStyles = @{
@"text": @(kMTTextStyleRoman),
@"textrm": @(kMTTextStyleRoman),
Expand All @@ -281,7 +307,7 @@ + (MTFontStyle)fontStyleWithName:(NSString *)fontName {
@"textsf": @(kMTTextStyleSansSerif),
@"texttt": @(kMTTextStyleTypewriter),
};
}
});
return textStyles;
}

Expand Down Expand Up @@ -367,14 +393,15 @@ + (nullable MTMathAtom *)tableWithEnvironment:(NSString *)env rows:(NSArray<NSAr
}
}
static NSDictionary<NSString*, NSArray*>* matrixEnvs = nil;
if (!matrixEnvs) {
static dispatch_once_t matrixEnvsOnce;
dispatch_once(&matrixEnvsOnce, ^{
matrixEnvs = @{ @"matrix" : @[],
@"pmatrix" : @[ @"(", @")"],
@"bmatrix" : @[ @"[", @"]"],
@"Bmatrix" : @[ @"{", @"}"],
@"vmatrix" : @[ @"vert", @"vert"],
@"Vmatrix" : @[ @"Vert", @"Vert"], };
}
});
if ([matrixEnvs objectForKey:env]) {
// it is set to matrix as the delimiters are converted to latex outside the table.
table.environment = @"matrix";
Expand Down Expand Up @@ -493,7 +520,8 @@ + (nullable MTMathAtom *)tableWithEnvironment:(NSString *)env rows:(NSArray<NSAr
+ (NSMutableDictionary<NSString*, MTMathAtom*>*) supportedLatexSymbols
{
static NSMutableDictionary<NSString*, MTMathAtom*>* commands = nil;
if (!commands) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
commands = [NSMutableDictionary dictionaryWithDictionary:@{
// Greek characters
@"alpha" : [MTMathAtom atomWithType:kMTMathAtomVariable value:@"\u03B1"],
Expand Down Expand Up @@ -883,15 +911,15 @@ + (nullable MTMathAtom *)tableWithEnvironment:(NSString *)env rows:(NSArray<NSAr
@"scriptstyle" : [[MTMathStyle alloc] initWithStyle:kMTLineStyleScript],
@"scriptscriptstyle" : [[MTMathStyle alloc] initWithStyle:kMTLineStyleScriptScript],
}];

}
});
return commands;
}

+ (NSDictionary*) aliases
{
static NSDictionary* aliases = nil;
if (!aliases) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
aliases = @{
@"lnot" : @"neg",
@"land" : @"wedge",
Expand Down Expand Up @@ -920,14 +948,15 @@ + (NSDictionary*) aliases
@"precnapprox" : @"nprecapprox",
@"succnapprox" : @"nsuccapprox",
};
}
});
return aliases;
}

+ (NSMutableDictionary<NSString*, NSMutableDictionary<NSNumber*, NSString*>*>*) textToLatexSymbolNames
{
static NSMutableDictionary<NSString*, NSMutableDictionary<NSNumber*, NSString*>*>* textToCommands = nil;
if (!textToCommands) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary* commands = [self supportedLatexSymbols];
textToCommands = [NSMutableDictionary dictionaryWithCapacity:commands.count];
for (NSString* command in commands) {
Expand Down Expand Up @@ -957,14 +986,15 @@ + (NSDictionary*) aliases
}
inner[typeKey] = command;
}
}
});
return textToCommands;
}

+ (NSDictionary<NSString*, NSString*>*) accents
{
static NSDictionary* accents = nil;
if (!accents) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
accents = @{
@"grave" : @"\u0300",
@"acute" : @"\u0301",
Expand All @@ -979,14 +1009,15 @@ + (NSDictionary*) aliases
@"widehat" : @"\u0302",
@"widetilde" : @"\u0303",
};
}
});
return accents;
}

+ (NSDictionary*) accentValueToName
{
static NSDictionary* accentToCommands = nil;
if (!accentToCommands) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary* accents = [self accents];
NSMutableDictionary* mutableDict = [NSMutableDictionary dictionaryWithCapacity:accents.count];
for (NSString* command in accents) {
Expand All @@ -1007,14 +1038,15 @@ + (NSDictionary*) accentValueToName
mutableDict[acc] = command;
}
accentToCommands = [mutableDict copy];
}
});
return accentToCommands;
}

+(NSDictionary<NSString*, NSString*> *) delimiters
{
static NSDictionary* delims = nil;
if (!delims) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
delims = @{
@"." : @"", // . means no delimiter
@"(" : @"(",
Expand Down Expand Up @@ -1049,14 +1081,15 @@ + (NSDictionary*) accentValueToName
@"lfloor" : @"\u230A",
@"rfloor" : @"\u230B",
};
}
});
return delims;
}

+ (NSDictionary*) delimValueToName
{
static NSDictionary* delimToCommands = nil;
if (!delimToCommands) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary* delims = [self delimiters];
NSMutableDictionary* mutableDict = [NSMutableDictionary dictionaryWithCapacity:delims.count];
for (NSString* command in delims) {
Expand All @@ -1077,15 +1110,16 @@ + (NSDictionary*) delimValueToName
mutableDict[delim] = command;
}
delimToCommands = [mutableDict copy];
}
});
return delimToCommands;
}


+(NSDictionary<NSString*, NSNumber*> *) fontStyles
{
static NSDictionary<NSString*, NSNumber*>* fontStyles = nil;
if (!fontStyles) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// \text* commands are handled by the parser via the textStyles
// dictionary, so they do NOT appear here.
fontStyles = @{
Expand All @@ -1106,7 +1140,7 @@ + (NSDictionary*) delimValueToName
@"mathbfit": @(kMTFontStyleBoldItalic),
@"bm": @(kMTFontStyleBoldItalic),
};
}
});
return fontStyles;
}

Expand All @@ -1115,7 +1149,8 @@ + (NSDictionary*) delimValueToName
+ (NSDictionary<NSString*, MTMathStackCommandSpec*>*) stackCommands
{
static NSDictionary* stackCommands = nil;
if (!stackCommands) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Each command maps to a single stretchy cap glyph (a Unicode codepoint). The
// typesetter walks the cap's OpenType h_variants first; if no variant is wide
// enough, it falls back to the font's HorizontalGlyphAssembly (parts + connector
Expand All @@ -1142,7 +1177,7 @@ + (NSDictionary*) delimValueToName
@"stackrel": [[MTMathStackCommandSpec alloc] initWithOver:nil under:nil displayClass:kMTMathAtomRelation argRoles:@[@(kMTStackArgOver), @(kMTStackArgBase)] inheritsClass:NO],
@"stackbin": [[MTMathStackCommandSpec alloc] initWithOver:nil under:nil displayClass:kMTMathAtomBinaryOperator argRoles:@[@(kMTStackArgOver), @(kMTStackArgBase)] inheritsClass:NO],
};
}
});
return stackCommands;
}

Expand Down Expand Up @@ -1192,7 +1227,8 @@ + (MTMathAtomType) inheritedDisplayClassForBase:(MTMathList*)base
+ (NSDictionary<NSString*, NSString*>*) stackCommandReverseTable
{
static NSDictionary* reverseTable = nil;
if (!reverseTable) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary<NSString*, MTMathStackCommandSpec*>* forward = [self stackCommands];
NSMutableDictionary* mutable = [NSMutableDictionary dictionaryWithCapacity:forward.count];
for (NSString* cmd in forward) {
Expand All @@ -1201,7 +1237,7 @@ + (MTMathAtomType) inheritedDisplayClassForBase:(MTMathList*)base
mutable[key] = cmd;
}
reverseTable = [mutable copy];
}
});
return reverseTable;
}

Expand Down
20 changes: 12 additions & 8 deletions iosMath/lib/MTMathListBuilder.m
Original file line number Diff line number Diff line change
Expand Up @@ -607,10 +607,11 @@ - (BOOL) expectCharacter:(unichar) ch
- (NSString*) readCommand
{
static NSSet<NSNumber*>* singleCharCommands = nil;
if (!singleCharCommands) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSArray* singleChars = @[ @'{', @'}', @'$', @'#', @'%', @'_', @'|', @' ', @',', @'>', @';', @'!', @'\\' ];
singleCharCommands = [[NSSet alloc] initWithArray:singleChars];
}
});
if ([self hasCharacters]) {
// Check if we have a single character command.
unichar ch = [self getNextCharacter];
Expand Down Expand Up @@ -848,13 +849,14 @@ - (MTMathAtom*) atomForCommand:(NSString*) command
- (MTMathList*) stopCommand:(NSString*) command list:(MTMathList*) list stopChar:(unichar) stopChar
{
static NSDictionary<NSString*, NSArray*>* fractionCommands = nil;
if (!fractionCommands) {
static dispatch_once_t fractionCommandsOnce;
dispatch_once(&fractionCommandsOnce, ^{
fractionCommands = @{ @"over" : @[],
@"atop" : @[],
@"choose" : @[ @"(", @")"],
@"brack" : @[ @"[", @"]"],
@"brace" : @[ @"{", @"}"]};
}
});
if ([command isEqualToString:@"right"]) {
if (!_currentInnerAtom) {
NSString* errorMessage = @"Missing \\left";
Expand Down Expand Up @@ -1009,7 +1011,8 @@ - (MTMathAtom*) buildTable:(NSString*) env firstList:(MTMathList*) firstList row
+ (NSDictionary*) spaceToCommands
{
static NSDictionary* spaceToCommands = nil;
if (!spaceToCommands) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
spaceToCommands = @{
@3 : @",",
@4 : @">",
Expand All @@ -1018,7 +1021,7 @@ + (NSDictionary*) spaceToCommands
@18 : @"quad",
@36 : @"qquad",
};
}
});
return spaceToCommands;
}

Expand Down Expand Up @@ -1093,14 +1096,15 @@ + (NSDictionary*) spaceToCommands
+ (NSDictionary*) styleToCommands
{
static NSDictionary* styleToCommands = nil;
if (!styleToCommands) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
styleToCommands = @{
@(kMTLineStyleDisplay) : @"displaystyle",
@(kMTLineStyleText) : @"textstyle",
@(kMTLineStyleScript) : @"scriptstyle",
@(kMTLineStyleScriptScript) : @"scriptscriptstyle",
};
}
});
return styleToCommands;
}

Expand Down
Loading
Loading