Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: dasm-assembler/dasm
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: bugfix
Choose a base ref
...
head repository: dasm-assembler/dasm
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Checking mergeability… Don’t worry, you can still create the pull request.
  • 13 commits
  • 48 files changed
  • 2 contributors

Commits on Jun 8, 2024

  1. Configuration menu
    Copy the full SHA
    57fd734 View commit details
    Browse the repository at this point in the history

Commits on Jun 25, 2026

  1. Fix multiple memory safety, correctness, and robustness bugs

    This commit addresses a systematic audit of the DASM source across
    src/globals.c, src/exp.c, src/main.c, src/ops.c, and src/mnef8.c.
    Changes fall into four categories: buffer overflows/overreads, memory
    leaks, logic errors, and silent error suppression. F8-specific bugs in
    mnef8.c are annotated with comments but left unmodified pending a
    dedicated fix.
    
    --- globals.c ---
    
    Avbuf too small (crash on long input lines)
      Avbuf was declared as char Avbuf[512]. parse() packs the label,
      mnemonic, and operand fields from the source line sequentially into
      this buffer. With MAXLINE=1024, the three fields combined can reach
      3*MAXLINE bytes, overflowing Avbuf[512] by up to ~2.5 kB on a
      maximally long input line.
      Fix: char Avbuf[MAXLINE * 3].
    
    Cvt[] and Opsize[] too short (out-of-bounds access for address modes 22-26)
      NUMOC was increased to 27 (address modes AM_IMP..AM_OTHER_ENDIAN,
      indices 0-26) when AM_OTHER_ENDIAN was added, but Cvt[] and Opsize[]
      were only 22 entries long. v_mnemonic() indexes both arrays directly
      by addrmode without bounds checks. Any instruction using address modes
      22-26 would read uninitialised memory.
      Fix: extend both arrays to 27 entries with appropriate zero-fill for
      the new modes.
    
    --- exp.c ---
    
    pushsymbol() infinite recursion (missing return after asmerr)
      The recursion depth guard called asmerr(ERROR_RECURSION_TOO_DEEP, true)
      and then fell through into the rest of the function body rather than
      returning. With bAbort=true, asmerr normally calls exit(), but if the
      error is non-fatal in some contexts the fall-through continued the
      recursion. Added symbolRecursionCount-- and return str + 1 immediately
      after the asmerr call to guarantee the function exits cleanly.
    
    op_mod() silent division-by-zero
      When the divisor v2 was zero, the modulo operator silently pushed v1
      unchanged onto the argument stack (equivalent to returning the dividend
      as the result). No error was raised and assembly continued with a
      meaningless value.
      Fix: call asmerr(ERROR_DIVISION_BY_0, true, NULL) and push 0 as the
      result, matching the behaviour of op_div().
    
    stackarg() silently discards expression table overflow
      When the expression evaluator's argument stack filled to MAXARGS, it
      printed a bare puts() message and wrapped the stack pointer back to
      Argibase. This silently corrupted the in-flight expression without
      failing the assembly.
      Fix: call asmerr(ERROR_EXPRESSION_TABLE_OVERFLOW, true, NULL) so the
      pass aborts cleanly.
    
    --- main.c ---
    
    cleanup() stack overflow via deep recursion and tempbuf too small
      cleanup() was tail-recursive: each comment-processing iteration called
      itself, with no limit on depth and a stack frame containing a local
      tempbuf[MAXLINE]. A source file with many nested or sequential comments
      could exhaust the stack.
      Additionally, tempbuf was sized at MAXLINE bytes. The snprintf into
      tempbuf concatenates up to two MAXLINE-length strings (the pre- and
      post-comment fragments), so tempbuf needed MAXLINE*2 bytes.
      Fix: convert to an iterative while(1)/continue loop. Size both
      tempbuf locals at MAXLINE*2.
    
    outlistfile() buffer overflow on long source lines
      buf1 and buf2 were static char[MAXLINE+32]. outlistfile() writes a
      prefix (~42 bytes of line number, PC, and hex bytes), then appends
      label + mnemonic + operands + a possible comment via sprintf. On a
      near-maximum-length source line the total content can exceed
      MAXLINE+32 by ~20 bytes, overflowing into adjacent static storage.
      Fix: increase both buffers to char[MAXLINE*2+64].
    
    sftos() rotating buffer had only 2 slots, needed 4
      sftos() maintains a rotating static buffer so callers can use its
      return value in a single printf with multiple calls. The buffer had
      2 slots, but ShowSegments() calls sftos() 4 times in one printf
      invocation (initorg, initrorg, org, rorg). Slots 0 and 1 were
      overwritten before printf consumed them, producing garbled output.
      Fix: expand to 4 slots: static char buf[4][MAX_SYM_LEN+14] with
      slot = (slot + 1) % 4.
    
    findmne() stack buffer overflow for long macro names
      findmne() copies the lowercased mnemonic into char buf[64] with no
      bounds check. User-defined macro names are limited only by MAX_SYM_LEN
      (1024). A macro name longer than 63 characters overflows buf[64] into
      the surrounding stack frame.
      Fix: char buf[MAX_SYM_LEN + 2].
    
    parse() memory leak: symarg not freed in label comma-arg block
      When parsing a label with comma-delimited arguments (e.g. label,expr),
      eval() was called to produce symarg, its value was used, but
      FreeSymbolList(symarg) was never called. This leaked one or more
      SYMBOL nodes per such label per assembly pass.
      Fix: call FreeSymbolList(symarg) after the if(symarg) block.
    
    asmerr() buffer overflows in erroradd1 and erroradd2 (3+2 sites)
      erroradd1[500] and erroradd2[500] are static 500-byte buffers used to
      accumulate the formatted error message for the pass buffer.
      - erroradd1 was written with sprintf(erroradd1, "... %s ...", name)
        where name is a source file path. A filename longer than ~490 bytes
        overflows erroradd1.
      - erroradd2 was written with sprintf(erroradd2, str, sText) where
        sText is caller-supplied context such as a symbol name or filename,
        which can reach MAX_SYM_LEN (1024) bytes.
      Fix: replace all five sprintf calls with snprintf(buf, sizeof(buf), ...)
      to clamp output to the buffer size.
    
    CompareAddress() and CompareOrder() undefined behaviour on overflow
      Both comparison functions returned sym1->value - sym2->value (a long
      subtraction). For values near LONG_MIN/LONG_MAX the subtraction
      overflows, producing undefined behaviour and incorrect qsort ordering.
      Fix: return (a > b) - (a < b), the standard safe three-way comparison.
    
    passbuffer_update() off-by-one in realloc size calculation
      newsizerequired was computed as strlen(existing) + strlen(new) with no
      +1 for the null terminator. On an exact-fit realloc the null byte was
      written one past the end of the allocation.
      Fix: add +1.
    
    Macro imbalance diagnostic wrote to listfile instead of stderr
      The check for unbalanced mac/endm pairs at end of assembly used a
      locally-declared error_file pointing to FI_listfile or stdout, then
      printed the diagnostic there. This buried the message in the listing
      output rather than on the error stream.
      Fix: write directly to stderr.
    
    argVal declared as int for -m option (should be long)
      The -m command-line option stores its result in argVal via atol().
      argVal was declared as int, truncating the value on platforms where
      sizeof(long) > sizeof(int), and causing the > 64 threshold check to
      behave incorrectly for large values.
      Fix: declare argVal as long.
    
    --- ops.c ---
    
    v_echo() sprintf overflow
      v_echo() used sprintf(buf, "%s", s->string) and sprintf(buf, "$%lx",
      s->value) into buf[256]. A string symbol value longer than 255 bytes
      overflows buf.
      Fix: snprintf(buf, sizeof(buf), ...) at both sites.
    
    generate() uses wrong error code for file-size-exceeded condition
      When the output segment exceeded maxFileSize, the code called
      asmerr(ERROR_RECURSION_TOO_DEEP, ...) producing the message "Recursion
      too deep in ..." for a condition that has nothing to do with recursion.
      Fix: use ERROR_AVOID_SEGFAULT ("Internal error in %s") which is
      generic enough to be accurate, and use snprintf for errMsg to avoid a
      trailing newline being embedded mid-message.
    
    v_set() buffer overflow in comma-arg label concatenation
      v_set() builds a dynamic symbol name in dynamicname[257] by
      concatenating evaluated argument values. The strcpy(dynamicname+j,
      tempval) had no bounds check on j, so sufficient comma arguments could
      overflow dynamicname.
      Fix: guard the strcpy with if (j + strlen(tempval) < 256).
    
    v_set() memory leak: symarg not freed in comma-arg loop
      Same pattern as the parse() leak above: eval(tempbuf, 0) was called
      inside the comma-arg while loop and symarg was used but never passed
      to FreeSymbolList().
      Fix: call FreeSymbolList(symarg) at the bottom of the if(symarg) block
      (combined with the bounds-check fix above).
    
    v_incbin() wrong Glen when Redo is set and skip_bytes > 0
      The Redo optimisation path estimated the binary file's contribution to
      the segment size as ftell(binfile) (total file length). When a skip
      offset was specified (incbin file,N), the correct size is total_length
      - skip_bytes. Passing the full file length to generate() advanced the
      segment origin by too many bytes, producing spurious phase errors on
      subsequent passes.
      Fix: Glen = ftell(binfile) - skip_bytes, clamped to >= 0.
    
    pfopen() fixed 512-byte path buffer overflows for long incdir+filename
      pfopen() allocated a fixed 512-byte buffer for constructing candidate
      search paths (incdir + "/" + filename). addpart() writes into this
      buffer with unchecked strcpy. A long INCDIR path combined with a long
      filename easily exceeds 511 bytes.
      Fix: compute the maximum required length by iterating incdirlist before
      allocating, and zmalloc() exactly that much.
    
    --- mnef8.c (comments only, no code changes) ---
    
    Four bugs in the F8 backend are documented with BUG comments at the
    relevant sites:
    
      v_ins_outs() and v_lis(): parse_value() return value is ignored. If
      the operand expression is unresolved on the first pass, parse_value()
      increments Redo but the function falls through and emits the opcode
      with operand=0 rather than deferring. Fix requires checking the return
      value and returning early.
    
      v_lr(): reg_dst and reg_src are declared as unsigned char, but
      parse_special_register() returns int with sentinel REG_NONE=29. The
      truncation to unsigned char is accidentally safe for REG_NONE (0x1d
      fits), but would silently break if the sentinel ever exceeded 255.
      Fix requires declaring both as int.
    
      generate_branch(): branch displacement is computed as target - PC - 1,
      but F8 branch instructions are 2 bytes wide so the displacement should
      be measured from PC + 2, not PC + 1. All F8 branches land one byte
      short of their target.
      Fix: change to target_adr - getPC() - 2.
    andrew-davie committed Jun 25, 2026
    Configuration menu
    Copy the full SHA
    8fc7846 View commit details
    Browse the repository at this point in the history
  2. new bugfix version #

    andrew-davie committed Jun 25, 2026
    Configuration menu
    Copy the full SHA
    8ccbc74 View commit details
    Browse the repository at this point in the history
  3. new error handler

    new test suite/handling -- all pass
    andrew-davie committed Jun 25, 2026
    Configuration menu
    Copy the full SHA
    8b46964 View commit details
    Browse the repository at this point in the history
  4. Update main.yml

    changed to v4 for upload-artifact
    andrew-davie authored Jun 25, 2026
    Configuration menu
    Copy the full SHA
    6f78606 View commit details
    Browse the repository at this point in the history
  5. Fix safety/correctness bugs, add include guards, per-platform build dirs

    Build system
    - Makefile: compile objects into .objs/<OS>/ to prevent Mac Mach-O and
      Linux ELF .o files clobbering each other when the source tree is shared
    - .gitignore: add src/.objs/
    - errors.c: add missing #include "util.h" so getprogname/strlcat
      prototypes are visible on Linux (was causing implicit-declaration warnings)
    - errors.h: declare panic() so all TUs can call it without implicit
      declaration; move forward-decl out of main.c
    - asm.h: add missing include guard (#ifndef _DASM_ASM_H) — its absence
      caused duplicate enum errors whenever two headers both included asm.h
    - symbols.h: rewrite to match reality; previous version was an aspirational
      redesign with snake_case names, wrong sortmode_t values, and functions
      that don't exist in symbols.c
    - version.h: fix DASM_PRINT_LEGAL macro — DASM_PRINT_COPYRIGHT(void)
      expanded to (void)puts("...")(void), a compile error; remove stray (void)
    
    Memory safety
    - main.c cleanup(): widen two char buf[MAXLINE] to char buf[MAXLINE*2];
      comment reformatting can produce up to MAXLINE+2 bytes
    - main.c outlistfile(): replace sprintf with snprintf, passing remaining
      buffer length to prevent overflow
    - main.c bStopAtEnd: fix memset byte count (was nMaxPasses+1, missing
      *sizeof(bool)); add NULL check and positive-value validation for -p
    - main.c parse(): add AVBUF_WRITE macro with bounds check; replace all
      bare Avbuf[j++] writes with the macro; add size check on label-arg path
    - ops.c v_dc: flush Gen[] before each character's bytes to prevent overflow
    - symbols.c findsymbol/CreateSymbol: widen local symbol buf from +14 to
      +22 bytes and switch sprintf→snprintf (20-digit index + separator + NUL)
    - util.c small_alloc/small_free_all: promote static locals buf/left to
      file-scope (small_alloc_buf/small_alloc_left) so small_free_all() can
      reset them, preventing use-after-free on the next assembly pass
    
    Correctness
    - main.c asmerr(): move snprintf before fprintf to avoid passing a
      non-literal format string directly to fprintf (format-string injection)
    - main.c passbuffer_update(): change newsizerequired/sizes from int to
      size_t; fix OOM recovery path to return without corrupting state
    - ops.c v_align(): reject alignment value of zero (was silent divide-by-zero)
    - ops.c v_incbin(): fix free condition (buf!=fname not buf!=str); add
      FreeSymbolList(sym) to plug per-pass memory leak; reject negative skip
    - ops.c genfill(): guard word/long size multiplications against signed
      integer overflow UB before shifting
    - ops.c v_setsym(): replace strcpy with snprintf
    - exp.c op_shiftleft/op_shiftright(): cast to unsigned before left-shift
      to avoid UB; clamp out-of-range shift counts to zero
    - ftohex.c getwlh(): store getc() result in int and check for EOF before
      casting; open input file in binary mode ("rb")
    - ftobin.c getwlh(): same EOF fix as ftohex.c
    
    Style
    - Apply K&R formatting (.clang-format) across all source files
    - Add blank line after opening brace of each function body
    andrew-davie committed Jun 25, 2026
    Configuration menu
    Copy the full SHA
    afe99c0 View commit details
    Browse the repository at this point in the history
  6. Configuration menu
    Copy the full SHA
    d2c027a View commit details
    Browse the repository at this point in the history
  7. Configuration menu
    Copy the full SHA
    221a236 View commit details
    Browse the repository at this point in the history
  8. Configuration menu
    Copy the full SHA
    90922e4 View commit details
    Browse the repository at this point in the history
  9. doc and other cleanups

    andrew-davie committed Jun 25, 2026
    Configuration menu
    Copy the full SHA
    a727cc9 View commit details
    Browse the repository at this point in the history
  10. fix: permalloc uses int instead of size_t

    permalloc() declaration in asm.h and definition in main.c used int bytes; the internal left counter was also int. Changed both to size_t to match checked_malloc/zero_malloc.
    fix: retire ckmalloc/zmalloc in favour of checked_malloc/zero_malloc
    
    ckmalloc and zmalloc in main.c duplicated checked_malloc/zero_malloc from util.c with weaker error reporting and int parameters. Removed the old functions; all call sites in ops.c, exp.c, main.c, and mnef8.c updated. Added #include "util.h" to asm.h so the replacements are available everywhere.
    fix: sizeof(ISEGNAME) used where strlen+1 is needed
    
    permalloc(sizeof(ISEGNAME)) produced a buffer of 4 or 8 bytes (pointer size) instead of 21 (string length). Replaced with permalloc(strlen(ISEGNAME) + 1). This was a heap overflow on every run.
    fix: sprintf → snprintf in mnemonic error message buffers
    
    sBuffer[128] in v_mnemonic() and v_dc() in ops.c, and sBuffer[MAX_SYM_LEN*4] in symbols.c, used sprintf with operands up to MAXLINE (1024) bytes. Replaced all instances with snprintf(buf, sizeof(buf), ...).
    fix: stackarg() and doop() write to array before overflow check
    
    Both functions wrote to Argstack[Argi]/Opdis[Opi] and then checked whether the index had exceeded the array bound. Reordered to check first and return early, preventing potential out-of-bounds writes.
    fix: v_hex() calls gethexdig() on null byte for odd-length input
    
    When the HEX directive had an odd number of digits, gethexdig(str[i+1]) was called with '\0', producing a spurious "Bad Hex Digit" error. Now checks str[i+1] != '\0' first and emits a proper "odd number of hex digits" error.
    fix: clearrefs() loop variable was short, should be int
    
    SHASHSIZE can exceed SHORT_MAX; loop index changed from short to int.
    fix: bStopAtEnd[] read at end of pass loop lacks bounds guard
    
    Added a defensive bounds check on pass before reading bStopAtEnd[pass], parallel to the existing write guard in asmerr().
    fix: errors.c print_error_message assert on empty string replaced with runtime guard
    
    assert(strlen(message) > 0) fires in release builds and kills the process. Replaced with a runtime if (strlen(message) == 0) return that survives without NDEBUG.
    fix: test_errors.c incompatible function pointer cast
    
    (void(*)(void))flush_deferred_errors is undefined behaviour — flush_deferred_errors takes a FILE *. Added _flush_deferred_errors_wrapper() helper and used that instead.
    fix: ftobin.c opens files in text mode
    
    fopen(av[2], "r") and fopen(av[3], "w") opened binary files in text mode, corrupting data on Windows (CR/LF translation, 0x1A EOF). Changed to "rb" and "wb".
    fix: unchecked ftell() calls in generate()
    
    Three ftell(FI_temp) calls stored results into long but were never checked for -1. Added error checks with asmerr(ERROR_FILE_ERROR, ...) on failure. Also added ferror() checks at the end of generate() and closegenerate() to catch any silent putc()/fwrite() failures.
    fix: mnef8.c BUG comments replaced with accurate notes
    
    Removed overstated "BUG:" labels from F8 backend. The parse_value() return-value pattern is intentional in DASM's multi-pass model; the branch displacement -1 has been in production since 2004 without reported issues. Comments now reflect what is actually known.
    andrew-davie committed Jun 25, 2026
    Configuration menu
    Copy the full SHA
    cee3220 View commit details
    Browse the repository at this point in the history

Commits on Jul 7, 2026

  1. Merge branch 'master' into resolve_issue_with_carriage_return_breakin…

    …g_character_literals
    ajxs committed Jul 7, 2026
    Configuration menu
    Copy the full SHA
    e3720e8 View commit details
    Browse the repository at this point in the history
  2. Merge pull request #148 from ajxs/resolve_issue_with_carriage_return_…

    …breaking_character_literals
    
    Resolve issue with carriage return breaking whitespace character literals
    andrew-davie authored Jul 7, 2026
    Configuration menu
    Copy the full SHA
    c361b82 View commit details
    Browse the repository at this point in the history
Loading