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

“TypeError: Cannot read properties of null (reading 'length')” with pipe + dup&dup2 #22030

Closed
hly2019 opened this issue May 31, 2024 · 2 comments · Fixed by #23217
Closed

Comments

@hly2019
Copy link

hly2019 commented May 31, 2024

Please include the following in your bug report:

Version of emscripten/emsdk:
3.1.54

Failing command line in full:

# emscripten
emcc pipe.cpp -o main.js
node main.js

# g++
g++ pipe.cpp  -o main
./main

Hi, I was using pipe with dup, dup2 for read/write redirection. And I found when doing something like the following programs, it will raise an exception TypeError: Cannot read properties of null (reading 'length').

In brief, here I'm trying to redirect the stderr to the write end of the pipe, then things originally written to file descriptor 2(stderr) could be read from the pipe. And it uses original_fd to restore the status of stderr.

(Additionally, here I even tried to replace the stderr with some other regular file, like temp.txt, but the program still throws.)

It seems to be an issue of pipe working with dup&dup2. It'd be great if you can check this. Thank you!

Code

#include <iostream>
#include <unistd.h>
#include <string.h>

int main() {
    // FILE* file_ = fopen("temp.txt", "w+");
    FILE* file_ = stderr;
    int fd = ::fileno(file_);
    int original_fd;
    int read_end_fd;

    int pipefd[2];


    original_fd = ::dup(fd); // duplicate stderr to original_fd, and original_fd is used to restore stderr later

    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }

    read_end_fd = pipefd[0];
    pipefd[0] = -1;
    int write_end_fd = pipefd[1];

    ::dup2(write_end_fd, fd); // now things write to fd(stderr) is redirected to write_end_fd
    ::close(write_end_fd); // close the write end of the pipe after duplicating

    ::write(fd, "x", 1); // write to the stderr, expected to be read from pipe

    ::dup2(original_fd, fd); //  restore fd (stderr) to its original state
    ::close(original_fd);

    char buffer[10];
    printf("flag before read\n");
    int ret = ::read(read_end_fd, buffer, 1); // Read from the pipe
    
    printf("ret is: %d\n", ret);
    printf("read content: %s\n", buffer);
    printf("error: %s\n", strerror(errno));

    ::close(read_end_fd); // Close the read end of the pipe

    return 0;
}

Results

Here is the execution result. The wasm execution raised an exception of Cannot read properties of null (reading 'length'). And for the native execution, it runs successfully.

# emscripten
flag before read
/data/main.js:128
      throw ex;
      ^

TypeError: Cannot read properties of null (reading 'length')
    at Object.read (/data/main.js:3972:44)
    at Object.read (/data/main.js:3159:43)
    at doReadv (/data/main.js:4171:23)
    at _fd_read (/data/main.js:4186:17)
    at wasm://wasm/0001046a:wasm-function[44]:0xe45
    at wasm://wasm/0001046a:wasm-function[10]:0x4b9
    at wasm://wasm/0001046a:wasm-function[11]:0x574
    at /data/main.js:691:12
    at callMain (/data/main.js:4689:15)
    at doRun (/data/main.js:4739:23)

# g++
flag before read
ret is: 1
read content: x
error: Success

Another pattern

I also found another similar pattern which seems to be the same issue, as shown here:

#include <iostream>
#include <unistd.h>
#include <string.h>

int main() {
    int pipefds[2];
    if (pipe(pipefds) == -1) {
        perror("pipe");
        return 1;
    }
    int pipefd_write = pipefds[1];
    write(pipefd_write, "x", 1);

    int original_fd = dup(pipefd_write); // to restore the pipefd_write later.

    int tempfds[2]; // only tempfds[1], the write end, is used here.
    if (pipe(tempfds) == -1) {
        perror("pipe");
        return 1;
    }

    dup2(tempfds[1], pipefd_write); // replace pipefd_write with tempfds[1], redirecting pipefd_write to tempfds[1]
    close(tempfds[1]); // close
    

    dup2(original_fd, pipefd_write); // recover pipefd_write

    close(original_fd);
    close(tempfds[0]); // we don't use tempfds[0] in this example
    
    close(pipefd_write);

    char buf[10];
    read(pipefds[0], buf, 1);
    printf("buf: %s\n", buf);

    return 0;
}

Compilation command are the same. And the results are also similar to the first code snippet

# emcc
...
      throw ex;
      ^

TypeError: Cannot read properties of null (reading 'length')
...

# g++
buf: x
@hly2019
Copy link
Author

hly2019 commented Jun 4, 2024

Hi @sbc100, could you please take a look at this? It's quite weird. Thanks!

@sbc100
Copy link
Collaborator

sbc100 commented Jun 4, 2024

Hi @sbc100, could you please take a look at this? It's quite weird. Thanks!

It would not surprise me if combining dup and pipe exposed existing bugs in emscripten. This type of stiff is not something that most of our users do on the web so it tends to not get too much attention. I'm a little busy right now but I'll try to take a look ASAP. n the mean time you might be able to track down the bug by looking though the library_fs.js file. Also, if you can make the repro even smaller that would be even more useful.

BTW you can see the existing tests we have for pipe in test/unistd/pipe.c and run it using ./test/runner core0.test_unistd_pipe.

hoodmane added a commit to hoodmane/emscripten that referenced this issue Dec 18, 2024
hoodmane added a commit to hoodmane/emscripten that referenced this issue Dec 18, 2024
hoodmane added a commit to hoodmane/emscripten that referenced this issue Dec 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants