0

I'm working on a Javascript/web module bundler program that is written in C and a bit of JavaScript. I keep having a problem with the program where the program will randomly crash at a random malloc call in a random function. It only occurs when I use it on a more complicated project with far more files and will work fine if used on a smaller project. I've had errors with malloc and free that were being caused by memory leaks/errors in other parts of the program but as far as I can tell there is nothing wrong this time that is noticeable.

This is the error message that occurs:

RuntimeError: memory access out of bounds
    at dlmalloc (wasm://wasm/007a3c2a:wasm-function[339]:0x7db59)
    at ReadDataFromFile (wasm://wasm/007a3c2a:wasm-function[114]:0x4cfd8)
    at BundleFile (wasm://wasm/007a3c2a:wasm-function[128]:0x55e36)
    at BundleFiles (wasm://wasm/007a3c2a:wasm-function[130]:0x6b9fb)
    at /mnt/c/Users/redstarbird/Documents/Projects/ArrowStack/Build/CFunctions.js:985:22
    at Object.ccall (/mnt/c/Users/redstarbird/Documents/Projects/ArrowStack/Build/CFunctions.js:5328:22)
    at /mnt/c/Users/redstarbird/Documents/Projects/ArrowStack/src/ArrowPack.js:104:24

The function that it crashes in looks like this:

char EMSCRIPTEN_KEEPALIVE *ReadDataFromFile(char *path)
{ // returns contents of file
    printf("path: %s\n", path);
    FILE *filePTR = fopen(path, "r");

    if (filePTR == NULL)
    {
        printf("Error opening file %s\n", path);
        return NULL;
    }
    fseek(filePTR, 0, SEEK_END);      // seek to end of file
    long int length = ftell(filePTR); // get length of file
    fseek(filePTR, 0, SEEK_SET);      // go back to start of file

    char *buffer = malloc(length + 1); // This is where it crashes
    if (buffer == NULL)
    {
        printf("Error creating buffer\n");
        exit(1);
    }

    int currentChar = 0;
    do
    {
        if (feof(filePTR))
        {
            break;
        }
        buffer[currentChar] = fgetc(filePTR);
        currentChar++;
    } while (1);
    fclose(filePTR);
    buffer[currentChar - 1] = '\0';
    return buffer;
}

The project is compiled using this command:

emcc --no-entry -s INITIAL_MEMORY=128mb -Wl,--export=__heap_base -Wl,--export=__data_end -Wl,--export=malloc -Wl,--export=free -sENVIRONMENT=node --profiling -sRUNTIME_DEBUG=1 -fsanitize=undefined -sLLD_REPORT_UNDEFINED -g3 -sSTACK_OVERFLOW_CHECK=2 -sASSERTIONS=2 src/Main.c src/C/cJSON/cJSON.c src/DependencyGraph/DependencyGraph.c ./src/C/StringRelatedFunctions.c ./src/Regex/RegexFunctions.c ./src/DependencyGraph/FindDependencies.c ./src/SettingsSingleton/settingsSingleton.c ./src/C/ProblemHandler.c ./src/C/TextColors.c ./src/C/FileHandler.c ./src/C/IntFunctions.c ./src/Minifiers/HTMLMinifier.c ./src/C/FileTypesHandler.c ./src/C/Stack.c ./src/C/BundleFiles.c ./src/C/ProgressBar.c ./src/C/StringShiftHandler.c ./src/Minifiers/JSMinifier.c -s EXPORT_ES6=0 -s MODULARIZE -s USE_ES6_IMPORT_META=0 -s EXPORTED_RUNTIME_METHODS=["ccall"] -s NODERAWFS=1 -sBINARYEN=1 -sEXIT_RUNTIME=1 -sALLOW_MEMORY_GROWTH -o Build/CFunctions.js

The javascript code to initialise the webassembly looks like this:

#!/usr/bin/env node
"use strict";
// js wrapper for arrowpack for NPM
const fs = require("fs");
const path = require("path");
const chalk = require("chalk");
const settingsSingleton = require("./SettingsSingleton/settingsSingleton");
const DirFunctions = require("./js/DirFunctions");
const wasm_exec = require("../Build/wasm_exec.js");
const CFunctionFactory = require("../Build/CFunctions.js");
const go = new Go();
// const Sleep = require("../src/js/Sleep");
const { mkdirIfNotExists } = require("./js/DirFunctions.js");

var StartTime = performance.now();

const argv = require("yargs/yargs")(process.argv.slice(2))
    .option("c", {
        alias: "config-path",
        describe: "Path to config file if not in working directory",
        type: "string"
    })
    .help().argv;

var CONFIG_FILE_NAME = "ArrowPack-config.json"



if (argv.c) { CONFIG_FILE_NAME = path.join(argv.c, CONFIG_FILE_NAME) } else { console.log("no custom file thingy"); }



var rawconfigData = null;
if (fs.existsSync(CONFIG_FILE_NAME)) { rawconfigData = fs.readFileSync(CONFIG_FILE_NAME, "utf8"); }
var temp;
const Settings = new settingsSingleton(rawconfigData);
if (Settings.getValue("largeProject") === false) {
    temp = DirFunctions.RecursiveWalkDir(Settings.getValue("entry")); // eventually add pluginAPI event here
} else {
    let RecursiveWalkDirWASM = fs.readFileSync("../Build/RecursiveWalkDir.wasm"); const { WebAssembly } = require("wasi");
    let compiledWalkDirWASM = WebAssembly.compile(wasm);
    let InstanceWalkDirWASM = WebAssembly.instantiate(compiledWalkDirWASM);
    const { InstanceWalkDirWASMExports } = instance;
    temp = InstanceWalkDirWASMExports.walk_dir(Settings.getValue("entry"));
}

var WalkedFiles = temp.Files;
var WalkedDirs = temp.Directories;
console.log(WalkedDirs);
if (WalkedDirs) {
    WalkedDirs.forEach(Dir => {
        console.log(chalk.red(Dir));
        var tempDir = Settings.getValue("exit") + Dir.substring(Settings.getValue("entry").length);

        console.log(chalk.yellowBright(tempDir));
        DirFunctions.mkdirIfNotExists(tempDir);
        //DirFunctions.mkdirIfNotExists(Dir);
    });
}
DirFunctions.mkdirIfNotExists("ARROWPACK_TEMP_PREPROCESS_DIR");
var AbsoluteFilesCharLength = 0;
var WrappedWalkedFiles = "";
if (WalkedFiles && WalkedFiles.length > 0) { // Paths are wrapped into one string because passing array of strings from JS to C is complicated
    WalkedFiles.forEach(FilePath => { WrappedWalkedFiles += FilePath + "::"; console.log(chalk.bold.blue(FilePath)); AbsoluteFilesCharLength += FilePath.length; });

    console.log(chalk.red(WalkedFiles.length));

    var StructsPointer;

    CFunctionFactory().then((CFunctions) => {
        CFunctions._CheckWasm();
        CFunctions._InitFileTypes();
        for (let k in Settings.settings) {
            if (CFunctions.ccall(
                "SendSettingsString",
                "number",
                ["string"],
                [k]
            ) != 1) {
                throw "Error sending Wasm settings string: " + k;
            }
            console.log(chalk.bold.blue(k));
            // Gives time to apply settings
            if (CFunctions.ccall(
                "SendSettingsString",
                "number",
                ["string"],
                [Settings.settings[k].toString()]
            ) != 1) {
                throw "Error sending Wasm settings string: " + Settings.settings[k];
            }
            console.log(chalk.bold.blue(Settings.settings[k]));
            // Also gives time to apply settings
        }
        var Success;
        // StructsPointer = CFunctions._CreateTree(allocateUTF8(WrappedWalkedFiles), WalkedFiles.length, AbsoluteFilesCharLength); // Need to get this working eventually for faster speed but couldn't work out allocateUTF8
        StructsPointer = CFunctions.ccall(
            "CreateGraph",
            "number",
            ["string", "number"],
            [WrappedWalkedFiles, WalkedFiles.length]
        );

        Success = CFunctions.ccall(
            "BundleFiles",
            "number",
            ["number"],
            [StructsPointer]
        );

        if (Success === 1 || Success === 0) {
            fs.rm("ARROWPACK_TEMP_PREPROCESS_DIR", { recursive: true }, (err) => {
                if (err) { console.error(err); } else {
                    console.log("Sucessfully removed temporary preprocess directory");
                }
            });
            DirFunctions.DeleteDirectory(); //CFunctions.ccall("PrintTimeTaken", "void", ["number", "number"], [StartTime, performance.now()]); // Not working for some reason
            console.log(chalk.magentaBright("Bundling files completed in " + (performance.now() - StartTime) / 1000 + " seconds"));

        }
    });

    // console.log("\n\nBuild completed in" + (EndTime - StartTime) / 1000 + " seconds!\n\n\n"); // Need to get Wasm code to run this because Wasm code seems to be non-blocking
    /*
        WebAssembly.instantiateStreaming(DependencyTreeWasmBuffer, DependencyTreeMemory).then((instance) => {
            StructsPointer = instance.ccall(
                "CreateTree",
                "number",
                ["string", "number", "string"],
                [WrappedWalkedFiles, WalkedFiles.length, settings.getValue("entry"), settings.getValue("exit")]
            )
        });
    
        var GoWASMFileHandler;
        const goWASM = fs.readFileSync("../Build/FileHandler.wasm");
    
        WebAssembly.instantiate(goWASM, go.importObject).then(function (obj) {
            GoWASMFileHandler = obj.instance;
            go.run(GoWASMFileHandler);
            GoWASMFileHandler.exports.HandleFiles(StructsPointer, settings.getValue("entry"));
        });*/
}

The full code for the project is at https://github.com/redstarbird/arrowpack. Any help would be appreciated because I'm very stuck as to how to fix this.

redstarbird
  • 1
  • 1
  • 1
  • What is the value of `length` when it crashes? Note: Your loop to read the whole file will read one character too much so the last character in `buffer` will be erroneous. I guess that explains why you do `malloc(length + 1);`. Instead do `for(int ch; (ch = fgetc(filePTR)) != EOF; ++currentChar) { buffer[currentChar] = ch; }` and then you don't have to allocate that extra byte. – Ted Lyngmo Feb 09 '23 at 18:10
  • You get the length of the file with `ftell(filePTR)`, so why are you using `fgetc` in a loop? The entire loop can be replaced with `fread(buffer, 1, length, filePtr);` – David Ranieri Feb 09 '23 at 18:12
  • 1
    Not familiar with webassembly, but usually a crash in a malloc call suggest that the heap was already corrupt when you got there. – 500 - Internal Server Error Feb 09 '23 at 18:15
  • 1
    Your loop that reads from the file is essentially the same as `while (!feof(filePTR))`, see https://stackoverflow.com/questions/5431941/while-feof-file-is-always-wrong – Barmar Feb 09 '23 at 18:15
  • @TedLyngmo The value of length before it crashes as malloc is 1372 which seems about right for the file and it shouldn't cause a crash at malloc I don't think – redstarbird Feb 09 '23 at 18:18
  • @500-InternalServerError Do I fix that by going through any previous code to find any errors leading to corrupting the heap? – redstarbird Feb 09 '23 at 18:30
  • Yes, that's exactly what you need to do. -- Start with the most recent free or realloc before this malloc and work your way backwards as needed. – 500 - Internal Server Error Feb 09 '23 at 18:35
  • You may find it helpful to use AddressSanitizer (`-fsanitize=address` on gcc, clang and emcc) to help track down heap corruption. See https://emscripten.org/docs/debugging/Sanitizers.html#address-sanitizer – Nick Lewycky Feb 12 '23 at 00:40

0 Answers0