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

[bug] functions outputJSON and pathExists execute callbacks even if there are errors/missing args (not matching fs behaviour) #865

Closed
emarteca opened this issue Feb 3, 2021 · 2 comments

Comments

@emarteca
Copy link

emarteca commented Feb 3, 2021

Hi all! I'm working on an automated test generator and have been testing it by applying regression testing to a set of popular npm libraries, including yours! In this commit I found a difference in the behaviour of outputJSON when called with incorrect arguments.

The issue

Functions that have been converted with universalify.fromPromise execute the callback argument even when the other arguments are missing or invalid. It looks like this results in a bug, because the comparable fs functions do error checking of these arguments.

To replicate the issue:

let fse = require('fs-extra');
function callback() { 
  console.log("Callback executing!");
  console.log(arguments);
}
fse.outputJSON("newfile.json", "[ ]", callback); // correct call
fse.outputJSON( callback); // incorrect call

Before the change in this commit, the output of running the above code is:

// correct call output: 
> Callback executing!
> [Arguments] { '0': null, '1': undefined }
// incorrect call output:
> Uncaught:
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received function callback
    at validateString (internal/validators.js:124:11)
    at Object.dirname (path.js:1128:5)
...

Then, after the update, the output of running the above code is:

// correct call output:
> Callback executing!
> [Arguments] { '0': null, '1': undefined }
// incorrect call output:
> Callback executing!
> [Arguments] {
  '0': TypeError: Cannot read property 'replace' of undefined
      at stringify ...

So, the callback is now executed even when the arguments are incorrect, and have the function error passed in as an argument to the callback.

What is causing this behaviour

This change from using the universalify library’s fromPromise function instead of fromCallback is causing the difference.

Looking at the implementation of fromPromise: this function takes a function fn as an argument, and returns a new function that

In our case, we see that the incorrect call to outputJSON does result in an error TypeError: Cannot read property 'replace' of undefined at stringify... (corresponds to this call being made with undefined). And so, the promise resulting from the call to outputJSON is rejected. As such, the reject branch of the .then is triggered, which results in our callback being executed.

We see that in our callback, the TypeError is printed as it is the argument to the callback.

pathExists: another function with this behaviour

The pathExists function is also universalified with fromPromise. As such, calling pathExists with only a callback has the same behaviour as outputJSON (i.e. the callback is executed).

fse.pathExists("newfile.json", callback); // newfile.json exists
> Callback executing!
> [Arguments] { '0': null, '1': true}

// incorrect call still executes the callback
fse.pathExists( callback); 
> Callback executing!
>  [Arguments] { '0': null, '1': false}

Comparison to behaviour of corresponding 'fs' functions

outputJSON is a special case of writeFile, so I assume it should have similar error checking to fs.writeFile.
pathExists is checking for the existence of a file and is just a wrapper for fs.access, so it should have the same error checking as fs.access.
Both of these functions have a type error if you try and call them with just a callback.

let fs = require('fs');
fs.writeFile(callback); // this function expects 3 arguments, and the last must be a callback
> Uncaught:                                                                                                                                                                            
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function. Received undefined                                                                                                    
    at maybeCallback (fs.js:160:9)
    at Object.writeFile (fs.js:1431:14)

fs.access(callback);
Uncaught:                                                                                                                                                                            
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function. Received undefined                                                                                                    
    at maybeCallback (fs.js:160:9)
    at Object.writeFile (fs.js:1431:14)

Fix

It is not enough to just add argument type checking code to the implementations of outputJSON and pathExists: because of the design of fromPromise, the callback will be executed regardless of the contents of these functions.
I have a proposed fix that simply involves replicating the implementation of fromPromise in the body of these functions, after some type checking.
I'll make a PR soon and link it here.

@emarteca emarteca changed the title [bug] functions execute callbacks even if there are errors/missing arg (not matching fs behaviour) [bug] functions outputJSON and pathExists execute callbacks even if there are errors/missing args (not matching fs behaviour) Feb 3, 2021
@emarteca
Copy link
Author

emarteca commented Feb 3, 2021

Relevant PR: #866

@RyanZim
Copy link
Collaborator

RyanZim commented Apr 1, 2021

Closing as per #826.

@RyanZim RyanZim closed this as completed Apr 1, 2021
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