Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FontForge crashes when a font has anchor classes, and you perform bulk operations on the font (such as changing the EM size) #5130

Closed
Tynach opened this issue Oct 19, 2022 · 0 comments · Fixed by #5405
Labels

Comments

@Tynach
Copy link
Contributor

Tynach commented Oct 19, 2022

This caused me to lose a significant amount of work on what's basically my first font project, because at some point one of the bulk operations happened while saving the file, which corrupted it. I believe it was trying to auto-hint glyphs that were composed of references to other glyphs, though I'm not 100% sure of that.

To reproduce it with the current master branch, simply open up any popular multilingual font (Roboto is the one I mostly tested on, but this happens with Noto Sans and several others as well; basically, any font that supports Unicode's 'combining diacritics', with anchor marks set up so they work on a variety of characters), open up 'Element' → 'Font Info...', go to the 'General' page, and then change the EM size (I typically tried doubling or halving the EM size; results were the same). Make sure that 'Scale Outlines' is enabled/checked, and click 'OK' at the bottom of the window to experience the crash.

If you have a glyph that has both its own anchor classes, as well as references to other glyphs with those same anchor classes defined (or maybe just anchor classes in general, but I didn't test that), the crash happens much more often. Simply opening the 'Anchor Control' window for an anchor class, and then within it choosing to view any other glyph (doesn't matter if it's a mark or base glyph), will trigger the same crash. The backtrace in that case is usually longer, but the lines of code causing the crash are the same.

On the topic of backtraces, here's a couple of examples:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7bba9f9 in AnchorPointsDuplicate (sc=0x55555638e4c0, base=0x5555562d8420) at /home/tynach/Software/Code/fontforge/fontforge/fvfonts.c:318
318             for ( ac=sc->parent->anchor; ac!=NULL; ac=ac->next )
(gdb) backtrace
#0  0x00007ffff7bba9f9 in AnchorPointsDuplicate (sc=0x55555638e4c0, base=0x5555562d8420) at /home/tynach/Software/Code/fontforge/fontforge/fvfonts.c:318
#1  SplineCharCopy (sc=sc@entry=0x555555f18a00, into=into@entry=0x0, mc=mc@entry=0x0) at /home/tynach/Software/Code/fontforge/fontforge/fvfonts.c:542
#2  0x00007ffff7cc3e99 in SFDDumpUndo (sfd=sfd@entry=0x555555963eb0, sc=sc@entry=0x555555f18a00, u=u@entry=0x55555600b360, keyPrefix=keyPrefix@entry=0x7ffff7e0effe "Undo", idx=idx@entry=0) at /home/tynach/Software/Code/fontforge/fontforge/sfd.c:980
#3  0x00007ffff7cc4f98 in SFDDumpChar (sfd=sfd@entry=0x555555963eb0, sc=0x555555f18a00, map=map@entry=0x555555e5ced0, newgids=newgids@entry=0x0, todir=todir@entry=0, saveUndoes=1) at /home/tynach/Software/Code/fontforge/fontforge/sfd.c:1637
#4  0x00007ffff7cd9f82 in SFAutoSave (sf=sf@entry=0x555555e5dcf0, map=0x555555e5ced0) at /home/tynach/Software/Code/fontforge/fontforge/sfd.c:9508
#5  0x00007ffff7b5be1f in _DoAutoSaves (fvs=<optimized out>) at /home/tynach/Software/Code/fontforge/fontforge/autosave.c:173
#6  DoAutoSaves () at /home/tynach/Software/Code/fontforge/fontforge/autosave.c:179
#7  0x00005555557272c5 in splash_e_h (gw=0x555555b586d0, gw@entry=<error reading variable: value has been optimized out>, event=0x7fffffffcec0, event@entry=<error reading variable: value has been optimized out>)
    at /home/tynach/Software/Code/fontforge/fontforgeexe/startui.c:443
#8  0x0000555555758aae in _GGDKDraw_CallEHChecked (gw=0x555555b586d0, event=<optimized out>, eh=<optimized out>) at /home/tynach/Software/Code/fontforge/gdraw/ggdkdraw.c:347
#9  0x0000555555758f20 in _GGDKDraw_ProcessTimerEvent (user_data=0x555555da2bd0) at /home/tynach/Software/Code/fontforge/gdraw/ggdkdraw.c:863
#10 0x00007ffff7006be8 in  () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#11 0x00007ffff700604e in g_main_context_dispatch () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#12 0x00007ffff7006400 in  () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#13 0x00007ffff70064a3 in g_main_context_iteration () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#14 0x00005555557578bd in GGDKDrawEventLoop (gdisp=0x555555a46aa0) at /home/tynach/Software/Code/fontforge/gdraw/ggdkdraw.c:2131
#15 0x000055555572845e in fontforge_main (argc=<optimized out>, argv=<optimized out>) at /home/tynach/Software/Code/fontforge/fontforgeexe/startui.c:1032
#16 0x00007ffff6c97083 in __libc_start_main (main=0x5555555b08c0 <main>, argc=1, argv=0x7fffffffd688, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffd678) at ../csu/libc-start.c:308
#17 0x00005555555b08fe in _start ()
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7bba9f9 in AnchorPointsDuplicate (sc=0x555555e2e750, base=0x555555f20f50) at /home/tynach/Software/Code/fontforge/fontforge/fvfonts.c:318
318             for ( ac=sc->parent->anchor; ac!=NULL; ac=ac->next )
(gdb) backtrace
#0  0x00007ffff7bba9f9 in AnchorPointsDuplicate (sc=0x555555e2e750, base=0x555555f20f50) at /home/tynach/Software/Code/fontforge/fontforge/fvfonts.c:318
#1  SplineCharCopy (sc=sc@entry=0x555555f20c80, into=into@entry=0x0, mc=mc@entry=0x0) at /home/tynach/Software/Code/fontforge/fontforge/fvfonts.c:542
#2  0x00007ffff7cc3f41 in SFDDumpUndo (sfd=sfd@entry=0x5555560aeeb0, sc=sc@entry=0x555555f20c80, u=u@entry=0x5555560b30d0, keyPrefix=keyPrefix@entry=0x7ffff7e0effe "Undo", idx=idx@entry=0) at /home/tynach/Software/Code/fontforge/fontforge/sfd.c:995
#3  0x00007ffff7cc4f98 in SFDDumpChar (sfd=sfd@entry=0x5555560aeeb0, sc=0x555555f20c80, map=map@entry=0x555555e5d110, newgids=newgids@entry=0x0, todir=todir@entry=0, saveUndoes=1) at /home/tynach/Software/Code/fontforge/fontforge/sfd.c:1637
#4  0x00007ffff7cd9f82 in SFAutoSave (sf=sf@entry=0x555555e5bb90, map=0x555555e5d110) at /home/tynach/Software/Code/fontforge/fontforge/sfd.c:9508
#5  0x00007ffff7b5be1f in _DoAutoSaves (fvs=<optimized out>) at /home/tynach/Software/Code/fontforge/fontforge/autosave.c:173
#6  DoAutoSaves () at /home/tynach/Software/Code/fontforge/fontforge/autosave.c:179
#7  0x00005555557272c5 in splash_e_h (gw=0x555555b54f60, gw@entry=<error reading variable: value has been optimized out>, event=0x7fffffffadc0, event@entry=<error reading variable: value has been optimized out>)
    at /home/tynach/Software/Code/fontforge/fontforgeexe/startui.c:443
#8  0x0000555555758aae in _GGDKDraw_CallEHChecked (gw=0x555555b54f60, event=<optimized out>, eh=<optimized out>) at /home/tynach/Software/Code/fontforge/gdraw/ggdkdraw.c:347
#9  0x0000555555758f20 in _GGDKDraw_ProcessTimerEvent (user_data=0x555555da2ab0) at /home/tynach/Software/Code/fontforge/gdraw/ggdkdraw.c:863
#10 0x00007ffff7006be8 in  () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#11 0x00007ffff700604e in g_main_context_dispatch () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#12 0x00007ffff7006400 in  () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#13 0x00007ffff70064a3 in g_main_context_iteration () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#14 0x00005555555b66a7 in AnchorControl (sc=<optimized out>, ap=<optimized out>, layer=<optimized out>) at /home/tynach/Software/Code/fontforge/fontforgeexe/anchorsaway.c:1448
#15 0x00005555555b683a in AnchorControlClass (_sf=<optimized out>, ac=<optimized out>, layer=<optimized out>) at /home/tynach/Software/Code/fontforge/fontforgeexe/anchorsaway.c:1501
#16 0x00005555556aeae2 in AnchorClassD_ShowAnchors (e=<optimized out>, g=<optimized out>) at /home/tynach/Software/Code/fontforge/fontforgeexe/lookupui.c:2400
#17 AnchorClassD_ShowAnchors (g=<optimized out>, e=<optimized out>) at /home/tynach/Software/Code/fontforge/fontforgeexe/lookupui.c:2378
#18 0x00005555557462f7 in GButtonInvoked (b=b@entry=0x555556093310, ev=ev@entry=0x7fffffffc2f0) at /home/tynach/Software/Code/fontforge/gdraw/gbuttons.c:252
#19 0x000055555574678a in gbutton_mouse (g=0x555556093310, event=0x7fffffffc2f0) at /home/tynach/Software/Code/fontforge/gdraw/gbuttons.c:480
#20 0x000055555574bfe8 in _GWidget_Container_eh (gw=gw@entry=0x55555607fe60, event=event@entry=0x7fffffffc2f0) at /home/tynach/Software/Code/fontforge/gdraw/gcontainer.c:294
#21 0x000055555574c60a in _GWidget_TopLevel_eh (event=0x7fffffffc2f0, gw=0x55555607fe60) at /home/tynach/Software/Code/fontforge/gdraw/gcontainer.c:614
#22 _GWidget_TopLevel_eh (gw=0x55555607fe60, gw@entry=<error reading variable: value has been optimized out>, event=0x7fffffffc2f0, event@entry=<error reading variable: value has been optimized out>) at /home/tynach/Software/Code/fontforge/gdraw/gcontainer.c:562
#23 0x0000555555758aae in _GGDKDraw_CallEHChecked (gw=0x55555607fe60, event=<optimized out>, eh=<optimized out>) at /home/tynach/Software/Code/fontforge/gdraw/ggdkdraw.c:347
#24 0x00005555557593e2 in _GGDKDraw_DispatchEvent (event=<optimized out>, data=<optimized out>) at /home/tynach/Software/Code/fontforge/gdraw/ggdkdraw.c:1234
#25 0x00007ffff72fff69 in  () at /usr/lib/x86_64-linux-gnu/libgdk-3.so.0
#26 0x00007ffff73330f6 in  () at /usr/lib/x86_64-linux-gnu/libgdk-3.so.0
#27 0x00007ffff700617d in g_main_context_dispatch () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#28 0x00007ffff7006400 in  () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#29 0x00007ffff70064a3 in g_main_context_iteration () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#30 0x00005555556abc5f in AnchorClassD (sf=sf@entry=0x555555e5bb90, sub=sub@entry=0x555555e5c270, def_layer=def_layer@entry=1) at /home/tynach/Software/Code/fontforge/fontforgeexe/lookupui.c:2678
#31 0x00005555556b7faf in _LookupSubtableContents (sf=0x555555e5bb90, sub=0x555555e5c270, sd=sd@entry=0x0, def_layer=1) at /home/tynach/Software/Code/fontforge/fontforgeexe/lookupui.c:6248
#32 0x000055555565ecc7 in LookupSubtableContents (gfi=gfi@entry=0x555555efff80, isgpos=isgpos@entry=1) at /home/tynach/Software/Code/fontforge/fontforgeexe/fontinfo.c:5970
#33 0x0000555555678753 in LookupMouse (event=0x7fffffffceb0, isgpos=1, gfi=0x555555efff80) at /home/tynach/Software/Code/fontforge/fontforgeexe/fontinfo.c:7365
#34 lookups_e_h (gw=<optimized out>, event=0x7fffffffceb0, isgpos=<optimized out>) at /home/tynach/Software/Code/fontforge/fontforgeexe/fontinfo.c:7409
#35 0x000055555574c178 in _GWidget_Container_eh (gw=0x55555606eb50, gw@entry=<error reading variable: value has been optimized out>, event=0x7fffffffceb0, event@entry=<error reading variable: value has been optimized out>)
    at /home/tynach/Software/Code/fontforge/gdraw/gcontainer.c:395
#36 0x0000555555758aae in _GGDKDraw_CallEHChecked (gw=0x55555606eb50, event=<optimized out>, eh=<optimized out>) at /home/tynach/Software/Code/fontforge/gdraw/ggdkdraw.c:347
#37 0x00005555557593e2 in _GGDKDraw_DispatchEvent (event=<optimized out>, data=<optimized out>) at /home/tynach/Software/Code/fontforge/gdraw/ggdkdraw.c:1234
#38 0x00007ffff72fff69 in  () at /usr/lib/x86_64-linux-gnu/libgdk-3.so.0
#39 0x00007ffff73330f6 in  () at /usr/lib/x86_64-linux-gnu/libgdk-3.so.0
#40 0x00007ffff700617d in g_main_context_dispatch () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#41 0x00007ffff7006400 in  () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#42 0x00007ffff70064a3 in g_main_context_iteration () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#43 0x00005555557578bd in GGDKDrawEventLoop (gdisp=0x555555a48140) at /home/tynach/Software/Code/fontforge/gdraw/ggdkdraw.c:2131
#44 0x000055555572845e in fontforge_main (argc=<optimized out>, argv=<optimized out>) at /home/tynach/Software/Code/fontforge/fontforgeexe/startui.c:1032
#45 0x00007ffff6c97083 in __libc_start_main (main=0x5555555b08c0 <main>, argc=1, argv=0x7fffffffd698, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffd688) at ../csu/libc-start.c:308
#46 0x00005555555b08fe in _start ()

The culprit seems to be that within AnchorPointsDuplicate(), sc->parent is sometimes set to NULL, causing sc->parent to be at address 0x300, which causes a segfault when an attempt is made to set ac too its value. I've studied the code around it, and think that what's happening is that only base is tested for a NULL value, because it's assumed that if it's not NULL, then sc->parent will also be valid.

I've tried changing the outer for loop to read:

for ( ; base!=NULL && sc->parent!=NULL; base = base->next ) {

And this seems to fix this particular crash.. But I don't know if it could potentially lead to some data not being copied over when copying from one font to another. I've done some testing, and an error dialog box pops up if I try to just paste glyphs from one font into another font, but if I remake the anchor classes within the new font and then try pasting again it appears to work fine.

Trying to use 'Edit' → 'Copy Lookup Data' in one font, and then using 'Paste' on another font, crashes still... But it crashed before, still crashes, and does not seem to be a related crash.

I'd open a pull request, except I'm not confident in my fix. I don't know this codebase nearly well enough to know exactly what sc->parent is usually used for, or how it's supposed to be set.

Within the calling function SplineCharCopy(), there's the line nsc->parent = into; - and when I have a font that doesn't have glyphs containing both anchor marks and references to glyphs with those same anchor marks, I've used breakpoints to confirm that in that case both nsc->anchor and into (and thus nsc->parent) are set to NULL when that function calls AnchorPointsDuplicate(). This leads me to suspect that it's quite likely that a real fix would involve making sure that the sc passed to SplineCharCopy has sc->anchor set to NULL in the situations where this is happening... But I don't know enough about this codebase to have any idea where to look for that.


Edit: I forgot to mention that I'm using Linux, specifically KDE Neon, and compiled the source code from the 'master' branch. I don't think that changes anything, though.

@jtanx jtanx added the I-crash label Dec 30, 2022
Tynach added a commit to Tynach/fontforge that referenced this issue Apr 11, 2024
When bulk operations are performed on a font, FontForge runs
`AnchorPoint *AnchorPointsDuplicate(AnchorPoint *base, SplineChar *sc)`.
Early in the function there is a `for` loop which runs while `base`
isn't NULL, but it later accesses `sc->parent->anchor` without checking
if `sc->parent` itself is NULL first.

This seems to be because the function assumes it will only be called
when the duplication of anchor points is actually desired, and thus
there should be a valid place to copy the anchor points to. Instead,
it's being called from within
`SplineChar *SplineCharCopy(SplineChar *sc, SplineFont *into, struct
sfmergecontext *mc)`, which is itself being called with `into` set to 0
within 2 different cases handled by
`void SFDDumpUndo(FILE *sfd, SplineChar *sc, Undoes *u, const char
*keyPrefix, int idx)`, and also once within
`Undoes *SFDGetUndo(FILE *sfd, SplineChar *sc, const char *startTag, int
current_layer)`.

`SFDDumpUndo()` seems to only call `SplineCharCopy` as a means toward
some of its side effects. It appears to make a new empty SplineChar copy
the SplineChar it's working on into the new SplineChar, extract and dump
hints from it, and then delete the SplineChar it created. Likewise,
`SFDGetUndo()` seems to also only call `SplineCharCopy()` to do some
temporary managing of hint information.

Since these appear to be the only scenarios where `SplineCharCopy` is
called with `into` set to NULL, and the only thing that is being done in
those situations is do some stuff relating to font hints, I've
determined that it's safe to simply not duplicate the AnchorPoints when
sc->parent is set to NULL.

This fixes fontforge#5130, and indeed is just the same fix I proposed back then.
At the time I wasn't confident this was safe, but I've extensively used
FontForge with this change made and had no further AnchorPoint crashes.
I also further investigated the root causes, which I describe above.

A more 'proper' fix would probably include systematic changes to how
hint undoes are saved and loaded from SFD files, but that's beyond my
skill and, quite honestly, not something I even care about that much.
The code appears to work despite it's oddities, and simply checking if
`sc->parent` is NULL before doing anything with the AnchorPoints allows
all the hint-related stuff that `SplineCharCopy()` does continue to work
for the sake of both `SFDDumpUndo()` and `SFDGetUndo()`.
Tynach added a commit to Tynach/fontforge that referenced this issue Apr 18, 2024
Any time `SFDDumpUndo()` or `SFDGetUndo()` are run, they end up calling
`SplineCharCopy()` with `NULL` for the `SplineFont *into` parameter.
When this happens, `SplineCharCopy()` attempts to copy the anchors into
the new SplineChar's `parent` font, but since that is set to `NULL` it
crashes when `AnchorPointsDuplicate()` attempts to read from
`sc->parent->anchor`.

Because `SFDDumpUndo()` and `SFDGetUndo()` appear to only do this so
that they can have a copy of the character's hints at a given undo state
(and then copy thoes hints into or out of the .sfd file), never affect
anchor classes, and delete the parentless SplineChar immediately after,
I've determined that it's safe to simply not duplicate the AnchorPoints
when `into` is set to `NULL`.

To do this, instead of setting `nsc->anchor` to the output returned by
`AnchorPointsDuplicate()`, I use a ternary statement to detect whether
or not `into` is NULL. If it is, then `nsc->anchor` is set to NULL, and
otherwise the value returned by `AnchorPointsDuplicate()` is used. I
decided not to do the reverse (which could have made the code shorter)
simply because line 505 already includes a similar ternary statement
that checks if `into` is `NULL`, and I decided to use the same format.

This fixes fontforge#5130, and is a slightly more elegant variation of the same
fix that I had proposed back then (more elegant because I skip the
entire `AnchorPointsDuplicate()` function call instead of merely
skipping over the loop that makes up the majority of the function). I
don't see a reason for this to be functionally any different from the
originally proposed solution, and I've extensively used FontForge with
that change made with no further `AnchorPoint` crashes.

A more 'proper' fix would probably include systematic changes to how
hint undoes are saved and loaded from SFD files, but that's beyond my
skill and, quite honestly, not something I even care about that much.
That code appears to work despite it's oddities, and simply checking if
`into==NULL` before doing anything with the `AnchorPoints` allows all
the hint-related stuff that `SplineCharCopy()` does continue to work for
the sake of both `SFDDumpUndo()` and `SFDGetUndo()`.
skef pushed a commit that referenced this issue Apr 20, 2024
Any time `SFDDumpUndo()` or `SFDGetUndo()` are run, they end up calling
`SplineCharCopy()` with `NULL` for the `SplineFont *into` parameter.
When this happens, `SplineCharCopy()` attempts to copy the anchors into
the new SplineChar's `parent` font, but since that is set to `NULL` it
crashes when `AnchorPointsDuplicate()` attempts to read from
`sc->parent->anchor`.

Because `SFDDumpUndo()` and `SFDGetUndo()` appear to only do this so
that they can have a copy of the character's hints at a given undo state
(and then copy thoes hints into or out of the .sfd file), never affect
anchor classes, and delete the parentless SplineChar immediately after,
I've determined that it's safe to simply not duplicate the AnchorPoints
when `into` is set to `NULL`.

To do this, instead of setting `nsc->anchor` to the output returned by
`AnchorPointsDuplicate()`, I use a ternary statement to detect whether
or not `into` is NULL. If it is, then `nsc->anchor` is set to NULL, and
otherwise the value returned by `AnchorPointsDuplicate()` is used. I
decided not to do the reverse (which could have made the code shorter)
simply because line 505 already includes a similar ternary statement
that checks if `into` is `NULL`, and I decided to use the same format.

This fixes #5130, and is a slightly more elegant variation of the same
fix that I had proposed back then (more elegant because I skip the
entire `AnchorPointsDuplicate()` function call instead of merely
skipping over the loop that makes up the majority of the function). I
don't see a reason for this to be functionally any different from the
originally proposed solution, and I've extensively used FontForge with
that change made with no further `AnchorPoint` crashes.

A more 'proper' fix would probably include systematic changes to how
hint undoes are saved and loaded from SFD files, but that's beyond my
skill and, quite honestly, not something I even care about that much.
That code appears to work despite it's oddities, and simply checking if
`into==NULL` before doing anything with the `AnchorPoints` allows all
the hint-related stuff that `SplineCharCopy()` does continue to work for
the sake of both `SFDDumpUndo()` and `SFDGetUndo()`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
2 participants