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

Error with recursive function calls in the instrumented JavaScript code #62

Open
gantony opened this issue Apr 19, 2020 · 1 comment
Open

Comments

@gantony
Copy link

gantony commented Apr 19, 2020

First, congrats on creating BLeak, this is such a wonderful and helpful software :)

I ran into an issue when using recursive function calls in the instrumented JavaScript code.

The issue is with an old version of the jQuery (haven't checked a newer version).

The original code causing the issue is:

    add: function() {
        if ( list ) {

            // If we have memory from a past run, we should fire after adding
            if ( memory && !firing ) {
                firingIndex = list.length - 1;
                queue.push( memory );
            }

            ( function add( args ) {
                jQuery.each( args, function( _, arg ) {
                    if ( jQuery.isFunction( arg ) ) {
                        if ( !options.unique || !self.has( arg ) ) {
                            list.push( arg );
                        }
                    } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {

                        // Inspect recursively
                        // !!! This is where the error happens !!! 
                        add( arg );
                    }
                } );
            } )( arguments );

            if ( memory && !firing ) {
                fire();
            }
        }
        return this;
    },

The instrumented code is as follows:

s31.self = {
      add: $$$FUNCTION_EXPRESSION$$$(function () {
        if (s31.list) {
          if (s31.memory && !s31.firing) {
            s31.firingIndex = s31.list.length - 1;
            s31.queue.push(s31.memory);
          }
          $$$FUNCTION_EXPRESSION$$$(function add(args) {
            var s32 = $$$CREATE_SCOPE_OBJECT$$$(s31, ["add"], {}, [], []);
            s1.jQuery.each(args, $$$FUNCTION_EXPRESSION$$$(function (_, arg) {
              if (s1.jQuery.isFunction(arg)) {
                if (!s31.options.unique || !s31.self.has(arg)) {
                  s31.list.push(arg);
                }
              } else if (arg && arg.length && !$$$SEQ$$$(s1.jQuery.type(arg), "string")) {
                // !!! This is where the error happens !!!
                (s32.add || s32.add)(arg);
              }
            }, s32));
          }, s31)(arguments);
          if (s31.memory && !s31.firing) {
            (s31.fire || s31.fire)();
          }
        }
        return this;
      }, s31),

I was able to work around the issue by rewriting the function so that the instrumented code becomes:

s31.self = {
      add: $$$FUNCTION_EXPRESSION$$$(function () {
        var s32 = $$$CREATE_SCOPE_OBJECT$$$(s31, ["add2", "callAdd2"], {}, [], []);
        if (s31.list) {
          if (s31.memory && !s31.firing) {
            s31.firingIndex = s31.list.length - 1;
            s31.queue.push(s31.memory);
          }
          s32.add2 = $$$FUNCTION_EXPRESSION$$$(function (args) {
            s1.jQuery.each(args, $$$FUNCTION_EXPRESSION$$$(function (_, arg) {
              if (s1.jQuery.isFunction(arg)) {
                if (!s31.options.unique || !s31.self.has(arg)) {
                  s31.list.push(arg);
                }
              } else if (arg && arg.length && !$$$SEQ$$$(s1.jQuery.type(arg), "string")) {
                (s32.callAdd2 || s32.callAdd2)(arg);
              }
            }, s32));
          }, s32);
          s32.callAdd2 = $$$FUNCTION_EXPRESSION$$$(function (args) {
            (s32.add2 || s32.add2)(args);
          }, s32);
          (s32.add2 || s32.add2)(arguments);
          if (s31.memory && !s31.firing) {
            (s31.fire || s31.fire)();
          }
        }
        return this;
      }, s31),

This is not blocking me anymore, but other people may run into similar issues.

Please let me know if I can provide anything else to help solve this issue.

@gantony
Copy link
Author

gantony commented Apr 19, 2020

I can actually reduce this to a somewhat more minimal example if that helps:

recursive-function.html

<html>

<body>
    <button id="addToList">addToList()</button>

    <script src="./recursive-function.js"></script>
</body>
</html>

recursive-function.js

var list = [];

function isWindow( obj ) {
    return obj != null && obj === obj.window;
}
function isArrayLike( obj ) {

	// Support: iOS 8.2 (not reproducible in simulator)
	// `in` check used to prevent JIT error (gh-2145)
	// hasOwn isn't used here due to false negatives
	// regarding Nodelist length in IE
	var length = !!obj && "length" in obj && obj.length,
		type = typeof obj;

	if ( type === "function" || isWindow( obj ) ) {
		return false;
	}

	return type === "array" || length === 0 ||
		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}

var jQueryEach = function(obj, callback) {
		var length, i = 0;

		if ( isArrayLike( obj ) ) {
			length = obj.length;
			for ( ; i < length; i++ ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		} else {
			for ( i in obj ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		}

		return obj;
}
var add = function() {
    ( function innerAdd( args ) {
        jQueryEach( args, function( _, arg ) {
            if ( typeof arg === "function" ) {
                list.push( arg );
            } else if ( arg && arg.length && typeof arg !== "string" ) {
                // Inspect recursively
                innerAdd( arg );
            }
        });
    } )( arguments );
};

function addToList() {
    
    // Please don't ask why I'm doing this, it's coming from another library initialising...
    var crazyArgs = {
        0: undefined,
        1: {
            0: undefined,
            length: 1
        },
        length: 2
    }
    add(crazyArgs);
    console.log("list: ", list);
}

document.getElementById('addToList').addEventListener('click', addToList);

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

No branches or pull requests

1 participant