Compiler segfault with t3make, apparently platform-specific, and also very obscure

This is so fiddly I’m not sure I can even summarize it concisely so I’ll just wade in:

Here’s a minimalist “game” source file, sample.t:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

versionInfo: GameID;
gameMain: GameMainDef newGame() {};

Here’s a separate source file containing a couple grammatical productions, yzzyxProd.t:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

class YzzyxProd: CommandProd;

grammar yzzyxPhrase(foozle)
        : yzzyxPredicate->cmd_
        : YzzyxProd
;

grammar yzzyxPredicate(yzzyx)
        : 'yzzyx'
        : HelloAction
        execAction() {}
;

Here’s the makefile.t3m:

-D LANGUAGE=en_us
-D MESSAGESTYLE=neu
-Fy obj -Fo obj
-o game.t3
-lib system
-lib adv3/adv3
-source sample
-source yzzyxProd

If you try to compile this using the current rev of FrobTADS, on OpenBSD:

> t3make -a -f makefile.t3m
TADS Compiler 3.1.3  Copyright 1999, 2012 Michael J. Roberts
        Files to build: 90
        symbol_export /usr/local/share/frobtads/tads3/lib/_main.t -> obj/_main.t3s
        symbol_export /usr/local/share/frobtads/tads3/lib/file.t -> obj/file.t3s
        symbol_export /usr/local/share/frobtads/tads3/lib/tok.t -> obj/tok.t3s
[...]
        symbol_export sample.t -> obj/sample.t3s
        symbol_export yzzyxProd.t -> obj/yzzyxProd.t3s
Segmentation fault (core dumped)

…it segfaults.

The same code compiles fine under linux (tested with the current rev of Ubuntu). And the compiler doesn’t have problems in general on OpenBSD.

Reversing the order of the grammar declarations fixes the problem, which suggests the problem is with the production referencing a predicate that hasn’t been declared yet. So this version of yzzyxProd.t compiles without error:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

class YzzyxProd: CommandProd;

grammar yzzyxPredicate(yzzyx)
        : 'yzzyx'
        : HelloAction
        execAction() {}
;

grammar yzzyxPhrase(foozle)
        : yzzyxPredicate->cmd_
        : YzzyxProd
;

But it isn’t the fact that the production references a predicate before it’s declared. Because if you move the same code to sample.t, it works fine. That is, with sample.t:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

versionInfo: GameID;
gameMain: GameMainDef newGame() {};

class YzzyxProd: CommandProd;

grammar yzzyxPhrase(foozle)
        : yzzyxPredicate->cmd_
        : YzzyxProd
;

grammar yzzyxPredicate(yzzyx)
        : 'yzzyx'
        : HelloAction
        execAction() {}
;

…and the edited yzzyxProd.t:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

/*
class YzzyxProd: CommandProd;

grammar yzzyxPhrase(foozle)
        : yzzyxPredicate->cmd_
        : YzzyxProd
;

grammar yzzyxPredicate(yzzyx)
        : 'yzzyx'
        : HelloAction
        execAction() {}
;
*/

Thus:

> t3make -a -f makefile.t3m
TADS Compiler 3.1.3  Copyright 1999, 2012 Michael J. Roberts
        Files to build: 90
        symbol_export /usr/local/share/frobtads/tads3/lib/_main.t -> obj/_main.t3s
        symbol_export /usr/local/share/frobtads/tads3/lib/file.t -> obj/file.t3s
        symbol_export /usr/local/share/frobtads/tads3/lib/tok.t -> obj/tok.t3s
[...]
        compile sample.t -> obj/sample.t3o
        compile yzzyxProd.t -> obj/yzzyxProd.t3o
        link -> game.t3p
        preinit -> game.t3

…and in fact this works if only the yzzyxPhrase production declaration is moved to sample.t (and so the compiler will reach it before it has seen the predicate declaration).

I can’t upload a tarball of the code here, so here’s a github repo of the whole “project” that illustrates the problem.

The platform I observed the problem was OpenBSD 7.6 on amd64, and it does not happen on Ubuntu 24.04 on amd64.

2 Likes

I had some time today to look at this and it appears to be a full-on heisenbug. I re-compiled FrobTADS from source with -DCMAKE_BUILD_TYPE=Debug hoping to be able to get additional information out of gdb. Compiled that way t3make does not segfault when compiling the T3 code in question.

Thinking, okay, maybe it was my previous build (which I had also done from source, because there’s no OpenBSD package for FrobTADS)…and got the original behavior. Went through a full cycle of recompiling with debug symbols and then back to without and observed the same behavior.

The crash happens in CTcParser::create_sym_node(), so I might do some further debug-by-printf() late. I would see what valgrind has to say about it, but support on OpenBSD isn’t there yet.

2 Likes

Not sure why this is only happening on OpenBSD, but this seems to resolve the issue:

--- tcprs.cpp.bak       Thu Jul 24 21:33:10 2025
+++ tcprs.cpp   Thu Jul 24 21:32:45 2025
@@ -1050,11 +1050,15 @@
      *   use. 
      */
     CTcPrsSymtab *symtab;
-    CTcSymbol *entry = local_symtab_->find(sym, sym_len, &symtab);
 
-    /* if we found it in local scope, return a resolved symbol node */
-    if (entry != 0 && symtab != global_symtab_)
-        return new CTPNSymResolved(entry);
+    if(local_symtab_ != 0)
+    {
+        CTcSymbol *entry = local_symtab_->find(sym, sym_len, &symtab);
+
+        /* if we found it in local scope, return a resolved symbol node */
+        if (entry != 0 && symtab != global_symtab_)
+            return new CTPNSymResolved(entry);
+    }
 
     /* if there's a debugger local scope, look it up there */
     if (debug_symtab_ != 0)

That’s just adding a check to not call local_symtab_->find() if local_symtab_ isn’t defined (the class definition initializes the property to 0).

I haven’t done any further digging to figure out why this would happen specifically on OpenBSD and not on e.g. linux when compiled from the same source.

1 Like

Reading more than once your diff, I suspect that the bug lies somewhere inside that find(sym, sym_len, &symtab) call.

looking at that find source can shed some light ?

HTH and
Best regards from Italy,
dott. Piergiorgio.

No, the segfault happens (in the unpatched code) because local_symtab_ (sometimes) isn’t set before CTcParser::create_sym_node() is called. The offending call is from CTcParser::parse_grammar() in tcprsprg.cpp.

I’m not sure why it’s specifically happening only on OpenBSD and only when compiled without debugging. I’ll try backtracking through the code later, but I’m not familiar enough with the internals to make a guess about a cause.