From c7801a939bfa499b6cbc0dbe5e32e2e730639799 Mon Sep 17 00:00:00 2001
From: Andrey Tarantsov <andrey@tarantsov.com>
Date: Thu, 22 Nov 2012 16:33:23 +0700
Subject: [PATCH] Fix: instrumentation breaks "new(require('foo'))(args)"

Fix a missing parenthesis when generating code for a new() statement
applied to an expression, like the example:

    new(require('foo'))(args)

which used to generate

    new require('foo')(args)

resulting in the returned function being called without 'new'.

I have no idea why on earth TOK_LP calls instrument_function_call; it
seems to treat all parethesized expressions as function calls, but I
didn't feel like investigating that.
---
 instrument-js.cpp                             | 13 +++++++---
 .../javascript-new-indirect.js                | 25 +++++++++++++++++++
 tests/javascript/javascript-new-indirect.js   |  5 ++++
 3 files changed, 40 insertions(+), 3 deletions(-)
 create mode 100644 tests/javascript.expected/javascript-new-indirect.js
 create mode 100644 tests/javascript/javascript-new-indirect.js

diff --git a/instrument-js.cpp b/instrument-js.cpp
index 7a1868a..2814b45 100644
--- a/instrument-js.cpp
+++ b/instrument-js.cpp
@@ -438,7 +438,7 @@ static void instrument_function(JSParseNode * node, Stream * f, int indent, enum
   Stream_write_char(f, '}');
 }
 
-static void instrument_function_call(JSParseNode * node, Stream * f) {
+static void instrument_function_call(JSParseNode * node, Stream * f, bool parenthesize_for_new) {
   JSParseNode * function_node = node->pn_head;
   if (function_node->pn_type == TOK_FUNCTION) {
     JSObject * object = function_node->pn_funpob->object;
@@ -455,7 +455,14 @@ static void instrument_function_call(JSParseNode * node, Stream * f) {
       return;
     }
   }
+  bool parenthesize_callee = parenthesize_for_new && (function_node->pn_type == TOK_LP); // e.g. new (require('foo'))()
+  if (parenthesize_callee) {
+    Stream_write_char(f, '(');
+  }
   output_expression(function_node, f, false);
+  if (parenthesize_callee) {
+    Stream_write_char(f, ')');
+  }
   Stream_write_char(f, '(');
   for (struct JSParseNode * p = function_node->pn_next; p != NULL; p = p->pn_next) {
     if (p != node->pn_head->pn_next) {
@@ -636,7 +643,7 @@ static void output_expression(JSParseNode * node, Stream * f, bool parenthesize_
     break;
   case TOK_NEW:
     Stream_write_string(f, "new ");
-    instrument_function_call(node, f);
+    instrument_function_call(node, f, true);
     break;
   case TOK_DELETE:
     Stream_write_string(f, "delete ");
@@ -693,7 +700,7 @@ static void output_expression(JSParseNode * node, Stream * f, bool parenthesize_
     Stream_write_char(f, ']');
     break;
   case TOK_LP:
-    instrument_function_call(node, f);
+    instrument_function_call(node, f, false);
     break;
   case TOK_RB:
     Stream_write_char(f, '[');
diff --git a/tests/javascript.expected/javascript-new-indirect.js b/tests/javascript.expected/javascript-new-indirect.js
new file mode 100644
index 0000000..38c8a4e
--- /dev/null
+++ b/tests/javascript.expected/javascript-new-indirect.js
@@ -0,0 +1,25 @@
+/* automatically generated by JSCoverage - do not edit */
+if (typeof _$jscoverage === 'undefined') _$jscoverage = {};
+if (! _$jscoverage['javascript-new-indirect.js']) {
+  _$jscoverage['javascript-new-indirect.js'] = [];
+  _$jscoverage['javascript-new-indirect.js'][1] = 0;
+  _$jscoverage['javascript-new-indirect.js'][2] = 0;
+  _$jscoverage['javascript-new-indirect.js'][3] = 0;
+  _$jscoverage['javascript-new-indirect.js'][4] = 0;
+  _$jscoverage['javascript-new-indirect.js'][5] = 0;
+}
+_$jscoverage['javascript-new-indirect.js'][1]++;
+function X() {
+}
+_$jscoverage['javascript-new-indirect.js'][2]++;
+function y() {
+  _$jscoverage['javascript-new-indirect.js'][2]++;
+  return X;
+}
+_$jscoverage['javascript-new-indirect.js'][3]++;
+x = new (y())();
+_$jscoverage['javascript-new-indirect.js'][4]++;
+x = new (y())(1);
+_$jscoverage['javascript-new-indirect.js'][5]++;
+x = new (y())(1, 2);
+_$jscoverage['javascript-new-indirect.js'].source = ["function X() {}","function y() { return X; }","x = new (y())();","x = new (y())(1);","x = new (y())(1, 2);"];
diff --git a/tests/javascript/javascript-new-indirect.js b/tests/javascript/javascript-new-indirect.js
new file mode 100644
index 0000000..3fd43c1
--- /dev/null
+++ b/tests/javascript/javascript-new-indirect.js
@@ -0,0 +1,5 @@
+function X() {}
+function y() { return X; }
+x = new (y())();
+x = new (y())(1);
+x = new (y())(1, 2);