Add a pretty printer to improve the error message non-function calls
authorverwaest <verwaest@chromium.org>
Wed, 21 Jan 2015 13:40:32 +0000 (05:40 -0800)
committerCommit bot <commit-bot@chromium.org>
Wed, 21 Jan 2015 13:40:41 +0000 (13:40 +0000)
BUG=259443
LOG=y

Review URL: https://codereview.chromium.org/861623002

Cr-Commit-Position: refs/heads/master@{#26189}

13 files changed:
src/compiler.h
src/isolate.cc
src/messages.h
src/prettyprinter.cc
src/prettyprinter.h
src/runtime.js
src/runtime/runtime-internal.cc
src/runtime/runtime.h
test/cctest/test-api.cc
test/mozilla/mozilla.status
test/webkit/exception-for-nonobject-expected.txt
test/webkit/fast/js/toString-overrides-expected.txt
test/webkit/run-json-stringify-expected.txt

index 4fc1635..82b296a 100644 (file)
@@ -93,6 +93,7 @@ class CompilationInfo {
   };
 
   CompilationInfo(Handle<JSFunction> closure, Zone* zone);
+  CompilationInfo(Handle<Script> script, Zone* zone);
   CompilationInfo(Isolate* isolate, Zone* zone);
   virtual ~CompilationInfo();
 
@@ -398,8 +399,6 @@ class CompilationInfo {
   }
 
  protected:
-  CompilationInfo(Handle<Script> script,
-                  Zone* zone);
   CompilationInfo(Handle<SharedFunctionInfo> shared_info,
                   Zone* zone);
   CompilationInfo(HydrogenCodeStub* stub,
index 3a87d74..e0bb09b 100644 (file)
@@ -1063,7 +1063,7 @@ void Isolate::ComputeLocation(MessageLocation* target) {
       int pos = frame->LookupCode()->SourcePosition(frame->pc());
       // Compute the location from the function and the reloc info.
       Handle<Script> casted_script(Script::cast(script));
-      *target = MessageLocation(casted_script, pos, pos + 1);
+      *target = MessageLocation(casted_script, pos, pos + 1, handle(fun));
     }
   }
 }
index aec3469..1dbc0fa 100644 (file)
@@ -43,22 +43,24 @@ class SourceInfo;
 
 class MessageLocation {
  public:
-  MessageLocation(Handle<Script> script,
-                  int start_pos,
-                  int end_pos)
+  MessageLocation(Handle<Script> script, int start_pos, int end_pos,
+                  Handle<JSFunction> function = Handle<JSFunction>())
       : script_(script),
         start_pos_(start_pos),
-        end_pos_(end_pos) { }
+        end_pos_(end_pos),
+        function_(function) {}
   MessageLocation() : start_pos_(-1), end_pos_(-1) { }
 
   Handle<Script> script() const { return script_; }
   int start_pos() const { return start_pos_; }
   int end_pos() const { return end_pos_; }
+  Handle<JSFunction> function() const { return function_; }
 
  private:
   Handle<Script> script_;
   int start_pos_;
   int end_pos_;
+  Handle<JSFunction> function_;
 };
 
 
index 1ff2edd..305adbf 100644 (file)
 namespace v8 {
 namespace internal {
 
+CallPrinter::CallPrinter(Zone* zone) {
+  output_ = NULL;
+  size_ = 0;
+  pos_ = 0;
+  position_ = 0;
+  found_ = false;
+  done_ = false;
+  InitializeAstVisitor(zone);
+}
+
+
+CallPrinter::~CallPrinter() { DeleteArray(output_); }
+
+
+const char* CallPrinter::Print(FunctionLiteral* program, int position) {
+  Init();
+  position_ = position;
+  Find(program);
+  return output_;
+}
+
+
+void CallPrinter::Find(AstNode* node, bool print) {
+  if (done_) return;
+  if (found_) {
+    if (print) {
+      int start = pos_;
+      Visit(node);
+      if (start != pos_) return;
+    }
+    Print("(intermediate value)");
+  } else {
+    Visit(node);
+  }
+}
+
+
+void CallPrinter::Init() {
+  if (size_ == 0) {
+    DCHECK(output_ == NULL);
+    const int initial_size = 256;
+    output_ = NewArray<char>(initial_size);
+    size_ = initial_size;
+  }
+  output_[0] = '\0';
+  pos_ = 0;
+}
+
+
+void CallPrinter::Print(const char* format, ...) {
+  if (!found_ || done_) return;
+  for (;;) {
+    va_list arguments;
+    va_start(arguments, format);
+    int n = VSNPrintF(Vector<char>(output_, size_) + pos_, format, arguments);
+    va_end(arguments);
+
+    if (n >= 0) {
+      // there was enough space - we are done
+      pos_ += n;
+      return;
+    } else {
+      // there was not enough space - allocate more and try again
+      const int slack = 32;
+      int new_size = size_ + (size_ >> 1) + slack;
+      char* new_output = NewArray<char>(new_size);
+      MemCopy(new_output, output_, pos_);
+      DeleteArray(output_);
+      output_ = new_output;
+      size_ = new_size;
+    }
+  }
+}
+
+
+void CallPrinter::VisitBlock(Block* node) {
+  FindStatements(node->statements());
+}
+
+
+void CallPrinter::VisitVariableDeclaration(VariableDeclaration* node) {}
+
+
+void CallPrinter::VisitFunctionDeclaration(FunctionDeclaration* node) {}
+
+
+void CallPrinter::VisitModuleDeclaration(ModuleDeclaration* node) {
+  Find(node->module());
+}
+
+
+void CallPrinter::VisitImportDeclaration(ImportDeclaration* node) {
+  Find(node->module());
+}
+
+
+void CallPrinter::VisitExportDeclaration(ExportDeclaration* node) {}
+
+
+void CallPrinter::VisitModuleLiteral(ModuleLiteral* node) {
+  VisitBlock(node->body());
+}
+
+
+void CallPrinter::VisitModuleVariable(ModuleVariable* node) {
+  Find(node->proxy());
+}
+
+
+void CallPrinter::VisitModulePath(ModulePath* node) { Find(node->module()); }
+
+
+void CallPrinter::VisitModuleUrl(ModuleUrl* node) {}
+
+
+void CallPrinter::VisitModuleStatement(ModuleStatement* node) {
+  Find(node->body());
+}
+
+
+void CallPrinter::VisitExpressionStatement(ExpressionStatement* node) {
+  Find(node->expression());
+}
+
+
+void CallPrinter::VisitEmptyStatement(EmptyStatement* node) {}
+
+
+void CallPrinter::VisitIfStatement(IfStatement* node) {
+  Find(node->condition());
+  Find(node->then_statement());
+  if (node->HasElseStatement()) {
+    Find(node->else_statement());
+  }
+}
+
+
+void CallPrinter::VisitContinueStatement(ContinueStatement* node) {}
+
+
+void CallPrinter::VisitBreakStatement(BreakStatement* node) {}
+
+
+void CallPrinter::VisitReturnStatement(ReturnStatement* node) {
+  Find(node->expression());
+}
+
+
+void CallPrinter::VisitWithStatement(WithStatement* node) {
+  Find(node->expression());
+  Find(node->statement());
+}
+
+
+void CallPrinter::VisitSwitchStatement(SwitchStatement* node) {
+  Find(node->tag());
+  ZoneList<CaseClause*>* cases = node->cases();
+  for (int i = 0; i < cases->length(); i++) Find(cases->at(i));
+}
+
+
+void CallPrinter::VisitCaseClause(CaseClause* clause) {
+  if (!clause->is_default()) {
+    Find(clause->label());
+  }
+  FindStatements(clause->statements());
+}
+
+
+void CallPrinter::VisitDoWhileStatement(DoWhileStatement* node) {
+  Find(node->body());
+  Find(node->cond());
+}
+
+
+void CallPrinter::VisitWhileStatement(WhileStatement* node) {
+  Find(node->cond());
+  Find(node->body());
+}
+
+
+void CallPrinter::VisitForStatement(ForStatement* node) {
+  if (node->init() != NULL) {
+    Find(node->init());
+  }
+  if (node->cond() != NULL) Find(node->cond());
+  if (node->next() != NULL) Find(node->next());
+  Find(node->body());
+}
+
+
+void CallPrinter::VisitForInStatement(ForInStatement* node) {
+  Find(node->each());
+  Find(node->enumerable());
+  Find(node->body());
+}
+
+
+void CallPrinter::VisitForOfStatement(ForOfStatement* node) {
+  Find(node->each());
+  Find(node->iterable());
+  Find(node->body());
+}
+
+
+void CallPrinter::VisitTryCatchStatement(TryCatchStatement* node) {
+  Find(node->try_block());
+  Find(node->catch_block());
+}
+
+
+void CallPrinter::VisitTryFinallyStatement(TryFinallyStatement* node) {
+  Find(node->try_block());
+  Find(node->finally_block());
+}
+
+
+void CallPrinter::VisitDebuggerStatement(DebuggerStatement* node) {}
+
+
+void CallPrinter::VisitFunctionLiteral(FunctionLiteral* node) {
+  FindStatements(node->body());
+}
+
+
+void CallPrinter::VisitClassLiteral(ClassLiteral* node) {
+  if (node->extends()) Find(node->extends());
+  for (int i = 0; i < node->properties()->length(); i++) {
+    Find(node->properties()->at(i)->value());
+  }
+}
+
+
+void CallPrinter::VisitNativeFunctionLiteral(NativeFunctionLiteral* node) {}
+
+
+void CallPrinter::VisitConditional(Conditional* node) {
+  Find(node->condition());
+  Find(node->then_expression());
+  Find(node->else_expression());
+}
+
+
+void CallPrinter::VisitLiteral(Literal* node) {
+  PrintLiteral(node->value(), true);
+}
+
+
+void CallPrinter::VisitRegExpLiteral(RegExpLiteral* node) {
+  Print("/");
+  PrintLiteral(node->pattern(), false);
+  Print("/");
+  PrintLiteral(node->flags(), false);
+}
+
+
+void CallPrinter::VisitObjectLiteral(ObjectLiteral* node) {
+  for (int i = 0; i < node->properties()->length(); i++) {
+    Find(node->properties()->at(i)->value());
+  }
+}
+
+
+void CallPrinter::VisitArrayLiteral(ArrayLiteral* node) {
+  Print("[");
+  for (int i = 0; i < node->values()->length(); i++) {
+    if (i != 0) Print(",");
+    Find(node->values()->at(i), true);
+  }
+  Print("]");
+}
+
+
+void CallPrinter::VisitVariableProxy(VariableProxy* node) {
+  PrintLiteral(node->name(), false);
+}
+
+
+void CallPrinter::VisitAssignment(Assignment* node) {
+  Find(node->target());
+  Find(node->value());
+}
+
+
+void CallPrinter::VisitYield(Yield* node) { Find(node->expression()); }
+
+
+void CallPrinter::VisitThrow(Throw* node) { Find(node->exception()); }
+
+
+void CallPrinter::VisitProperty(Property* node) {
+  Expression* key = node->key();
+  Literal* literal = key->AsLiteral();
+  if (literal != NULL && literal->value()->IsInternalizedString()) {
+    Find(node->obj(), true);
+    Print(".");
+    PrintLiteral(literal->value(), false);
+  } else {
+    Find(node->obj(), true);
+    Print("[");
+    Find(key, true);
+    Print("]");
+  }
+}
+
+
+void CallPrinter::VisitCall(Call* node) {
+  bool was_found = !found_ && node->position() == position_;
+  if (was_found) found_ = true;
+  Find(node->expression(), true);
+  if (!was_found) Print("(...)");
+  FindArguments(node->arguments());
+  if (was_found) done_ = true;
+}
+
+
+void CallPrinter::VisitCallNew(CallNew* node) {
+  bool was_found = !found_ && node->expression()->position() == position_;
+  if (was_found) found_ = true;
+  Find(node->expression(), was_found);
+  FindArguments(node->arguments());
+  if (was_found) done_ = true;
+}
+
+
+void CallPrinter::VisitCallRuntime(CallRuntime* node) {
+  FindArguments(node->arguments());
+}
+
+
+void CallPrinter::VisitUnaryOperation(UnaryOperation* node) {
+  Token::Value op = node->op();
+  bool needsSpace =
+      op == Token::DELETE || op == Token::TYPEOF || op == Token::VOID;
+  Print("(%s%s", Token::String(op), needsSpace ? " " : "");
+  Find(node->expression(), true);
+  Print(")");
+}
+
+
+void CallPrinter::VisitCountOperation(CountOperation* node) {
+  Print("(");
+  if (node->is_prefix()) Print("%s", Token::String(node->op()));
+  Find(node->expression(), true);
+  if (node->is_postfix()) Print("%s", Token::String(node->op()));
+  Print(")");
+}
+
+
+void CallPrinter::VisitBinaryOperation(BinaryOperation* node) {
+  Print("(");
+  Find(node->left(), true);
+  Print(" %s ", Token::String(node->op()));
+  Find(node->right(), true);
+  Print(")");
+}
+
+
+void CallPrinter::VisitCompareOperation(CompareOperation* node) {
+  Print("(");
+  Find(node->left(), true);
+  Print(" %s ", Token::String(node->op()));
+  Find(node->right(), true);
+  Print(")");
+}
+
+
+void CallPrinter::VisitThisFunction(ThisFunction* node) {}
+
+
+void CallPrinter::VisitSuperReference(SuperReference* node) {}
+
+
+void CallPrinter::FindStatements(ZoneList<Statement*>* statements) {
+  if (statements == NULL) return;
+  for (int i = 0; i < statements->length(); i++) {
+    Find(statements->at(i));
+  }
+}
+
+
+void CallPrinter::FindArguments(ZoneList<Expression*>* arguments) {
+  if (found_) return;
+  for (int i = 0; i < arguments->length(); i++) {
+    Find(arguments->at(i));
+  }
+}
+
+
+void CallPrinter::PrintLiteral(Handle<Object> value, bool quote) {
+  Object* object = *value;
+  if (object->IsString()) {
+    String* string = String::cast(object);
+    if (quote) Print("\"");
+    for (int i = 0; i < string->length(); i++) {
+      Print("%c", string->Get(i));
+    }
+    if (quote) Print("\"");
+  } else if (object->IsNull()) {
+    Print("null");
+  } else if (object->IsTrue()) {
+    Print("true");
+  } else if (object->IsFalse()) {
+    Print("false");
+  } else if (object->IsUndefined()) {
+    Print("undefined");
+  } else if (object->IsNumber()) {
+    Print("%g", object->Number());
+  }
+}
+
+
+void CallPrinter::PrintLiteral(const AstRawString* value, bool quote) {
+  PrintLiteral(value->string(), quote);
+}
+
+
+//-----------------------------------------------------------------------------
+
+
 #ifdef DEBUG
 
 PrettyPrinter::PrettyPrinter(Zone* zone) {
index bf01520..c07421a 100644 (file)
 namespace v8 {
 namespace internal {
 
+class CallPrinter : public AstVisitor {
+ public:
+  explicit CallPrinter(Zone* zone);
+  virtual ~CallPrinter();
+
+  // The following routine prints the node with position |position| into a
+  // string. The result string is alive as long as the CallPrinter is alive.
+  const char* Print(FunctionLiteral* program, int position);
+
+  void Print(const char* format, ...);
+
+  void Find(AstNode* node, bool print = false);
+
+// Individual nodes
+#define DECLARE_VISIT(type) void Visit##type(type* node) OVERRIDE;
+  AST_NODE_LIST(DECLARE_VISIT)
+#undef DECLARE_VISIT
+
+ private:
+  void Init();
+  char* output_;  // output string buffer
+  int size_;      // output_ size
+  int pos_;       // current printing position
+  int position_;  // position of ast node to print
+  bool found_;
+  bool done_;
+
+  DEFINE_AST_VISITOR_SUBCLASS_MEMBERS();
+
+ protected:
+  void PrintLiteral(Handle<Object> value, bool quote);
+  void PrintLiteral(const AstRawString* value, bool quote);
+  void FindStatements(ZoneList<Statement*>* statements);
+  void FindArguments(ZoneList<Expression*>* arguments);
+};
+
+
 #ifdef DEBUG
 
 class PrettyPrinter: public AstVisitor {
index 8ae6761..6bbb375 100644 (file)
@@ -377,7 +377,9 @@ function FILTER_KEY(key) {
 function CALL_NON_FUNCTION() {
   var delegate = %GetFunctionDelegate(this);
   if (!IS_FUNCTION(delegate)) {
-    throw %MakeTypeError('called_non_callable', [typeof this]);
+    var callsite = %RenderCallSite();
+    if (callsite == "") callsite = typeof this;
+    throw %MakeTypeError('called_non_callable', [callsite]);
   }
   return %Apply(delegate, this, arguments, 0, %_ArgumentsLength());
 }
@@ -386,7 +388,9 @@ function CALL_NON_FUNCTION() {
 function CALL_NON_FUNCTION_AS_CONSTRUCTOR() {
   var delegate = %GetConstructorDelegate(this);
   if (!IS_FUNCTION(delegate)) {
-    throw %MakeTypeError('called_non_callable', [typeof this]);
+    var callsite = %RenderCallSite();
+    if (callsite == "") callsite = typeof this;
+    throw %MakeTypeError('called_non_callable', [callsite]);
   }
   return %Apply(delegate, this, arguments, 0, %_ArgumentsLength());
 }
index 79dface..38f238b 100644 (file)
@@ -7,6 +7,9 @@
 #include "src/arguments.h"
 #include "src/bootstrapper.h"
 #include "src/debug.h"
+#include "src/messages.h"
+#include "src/parser.h"
+#include "src/prettyprinter.h"
 #include "src/runtime/runtime-utils.h"
 
 namespace v8 {
@@ -153,6 +156,30 @@ RUNTIME_FUNCTION(Runtime_CollectStackTrace) {
 }
 
 
+RUNTIME_FUNCTION(Runtime_RenderCallSite) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 0);
+  MessageLocation location;
+  isolate->ComputeLocation(&location);
+  if (location.start_pos() == -1) return isolate->heap()->empty_string();
+
+  Zone zone(isolate);
+  if (location.function()->shared()->is_function()) {
+    CompilationInfo info(location.function(), &zone);
+    if (!Parser::Parse(&info)) return isolate->heap()->empty_string();
+    CallPrinter printer(&zone);
+    const char* string = printer.Print(info.function(), location.start_pos());
+    return *isolate->factory()->NewStringFromAsciiChecked(string);
+  }
+
+  CompilationInfo info(location.script(), &zone);
+  if (!Parser::Parse(&info)) return isolate->heap()->empty_string();
+  CallPrinter printer(&zone);
+  const char* string = printer.Print(info.function(), location.start_pos());
+  return *isolate->factory()->NewStringFromAsciiChecked(string);
+}
+
+
 RUNTIME_FUNCTION(Runtime_GetFromCache) {
   SealHandleScope shs(isolate);
   // This is only called from codegen, so checks might be more lax.
index 9c7c379..0b89104 100644 (file)
@@ -383,6 +383,7 @@ namespace internal {
   F(Abort, 1, 1)                                       \
   F(AbortJS, 1, 1)                                     \
   F(NativeScriptsCount, 0, 1)                          \
+  F(RenderCallSite, 0, 1)                              \
   /* ES5 */                                            \
   F(OwnKeys, 1, 1)                                     \
                                                        \
index 31a83f3..59b39e1 100644 (file)
@@ -11353,7 +11353,7 @@ THREADED_TEST(ConstructorForObject) {
     value = CompileRun("new obj2(28)");
     CHECK(try_catch.HasCaught());
     String::Utf8Value exception_value1(try_catch.Exception());
-    CHECK_EQ("TypeError: object is not a function", *exception_value1);
+    CHECK_EQ("TypeError: obj2 is not a function", *exception_value1);
     try_catch.Reset();
 
     Local<Value> args[] = { v8_num(29) };
@@ -11714,8 +11714,7 @@ THREADED_TEST(CallAsFunction) {
     CHECK(try_catch.HasCaught());
     String::Utf8Value exception_value1(try_catch.Exception());
     // TODO(verwaest): Better message
-    CHECK_EQ("TypeError: object is not a function",
-             *exception_value1);
+    CHECK_EQ("TypeError: obj2 is not a function", *exception_value1);
     try_catch.Reset();
 
     // Call an object without call-as-function handler through the API
@@ -13095,7 +13094,7 @@ THREADED_PROFILED_TEST(InterceptorCallICFastApi_SimpleSignature_Miss3) {
       "}");
   CHECK(try_catch.HasCaught());
   // TODO(verwaest): Adjust message.
-  CHECK_EQ(v8_str("TypeError: undefined is not a function"),
+  CHECK_EQ(v8_str("TypeError: receiver.method is not a function"),
            try_catch.Exception()->ToString(isolate));
   CHECK_EQ(42, context->Global()->Get(v8_str("saved_result"))->Int32Value());
   CHECK_GE(interceptor_call_count, 50);
@@ -13270,7 +13269,7 @@ THREADED_PROFILED_TEST(CallICFastApi_SimpleSignature_Miss2) {
       "}");
   CHECK(try_catch.HasCaught());
   // TODO(verwaest): Adjust message.
-  CHECK_EQ(v8_str("TypeError: undefined is not a function"),
+  CHECK_EQ(v8_str("TypeError: receiver.method is not a function"),
            try_catch.Exception()->ToString(isolate));
   CHECK_EQ(42, context->Global()->Get(v8_str("saved_result"))->Int32Value());
 }
index 3a4a7ed..1048097 100644 (file)
   'js1_5/LexicalConventions/regress-469940': [FAIL_OK],
   'js1_5/Exceptions/regress-332472': [FAIL_OK],
   'js1_5/Regress/regress-173067': [FAIL_OK],
-  'js1_5/Regress/regress-355556': [FAIL_OK],
   'js1_5/Regress/regress-328664': [FAIL_OK],
   'js1_5/Regress/regress-252892': [FAIL_OK],
   'js1_5/Regress/regress-352208': [FAIL_OK],
index e600398..b6cb95b 100644 (file)
@@ -26,7 +26,7 @@ Test for correct handling of exceptions from instanceof and 'new' expressions
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS new {}.undefined threw exception TypeError: undefined is not a function.
+PASS new {}.undefined threw exception TypeError: (intermediate value).undefined is not a function.
 PASS 1 instanceof {}.undefined threw exception TypeError: Expecting a function in instanceof check, but got undefined.
 PASS successfullyParsed is true
 
index 3e36c70..9d1c174 100644 (file)
@@ -28,13 +28,12 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
 
 PASS [1].toString() is '1'
 PASS [1].toLocaleString() is 'toLocaleString'
-FAIL [1].toLocaleString() should be 1. Threw exception TypeError: string is not a function
+FAIL [1].toLocaleString() should be 1. Threw exception TypeError: [1].toLocaleString is not a function
 PASS [/r/].toString() is 'toString2'
 PASS [/r/].toLocaleString() is 'toLocaleString2'
-FAIL [/r/].toLocaleString() should be toString2. Threw exception TypeError: string is not a function
+FAIL [/r/].toLocaleString() should be toString2. Threw exception TypeError: [/r/].toLocaleString is not a function
 PASS caught is true
 PASS successfullyParsed is true
 
-
 TEST COMPLETE
 
index 45d55c7..fef3815 100644 (file)
@@ -83,7 +83,7 @@ PASS tests[i](nativeJSON) is tests[i](JSON)
 function (jsonObject){
         return jsonObject.stringify({toJSON: Date.prototype.toJSON});
     }
-PASS tests[i](nativeJSON) threw exception TypeError: undefined is not a function.
+PASS tests[i](nativeJSON) threw exception TypeError: jsonObject.stringify is not a function.
 function (jsonObject){
         return jsonObject.stringify({toJSON: Date.prototype.toJSON, toISOString: function(){ return "custom toISOString"; }});
     }
@@ -101,7 +101,7 @@ function (jsonObject){
         d.toISOString = null;
         return jsonObject.stringify(d);
     }
-PASS tests[i](nativeJSON) threw exception TypeError: object is not a function.
+PASS tests[i](nativeJSON) threw exception TypeError: jsonObject.stringify is not a function.
 function (jsonObject){
         var d = new Date(0);
         d.toJSON = undefined;