Skip to content
Permalink
Browse files
[ES6] Return JSInternalPromise as result of evaluateModule
https://bugs.webkit.org/show_bug.cgi?id=148173

Reviewed by Saam Barati.

Now evaluateModule returns JSInternalPromise* as its result value.
When an error occurs while loading or executing the modules,
this promise is rejected by that error. By leveraging this, we implemented
asynchronous error reporting when executing the modules in JSC shell.

And this patch also changes the evaluateModule signature to accept the entry
point by the moduleName. By using it, JSC shell can start executing the modules
with the entry point module name.

* builtins/ModuleLoaderObject.js:
(loadModule):
* jsc.cpp:
(dumpException):
(runWithScripts):
* runtime/Completion.cpp:
(JSC::evaluateModule):
* runtime/Completion.h:
* runtime/JSInternalPromise.cpp:
(JSC::JSInternalPromise::then):
* runtime/JSInternalPromise.h:
* runtime/ModuleLoaderObject.cpp:
(JSC::ModuleLoaderObject::requestInstantiateAll):
(JSC::ModuleLoaderObject::loadModule):
(JSC::ModuleLoaderObject::resolve):
(JSC::ModuleLoaderObject::fetch):
(JSC::ModuleLoaderObject::translate):
(JSC::ModuleLoaderObject::instantiate):
(JSC::moduleLoaderObjectParseModule):
* runtime/ModuleLoaderObject.h:

Canonical link: https://commits.webkit.org/166521@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@188894 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
Constellation committed Aug 24, 2015
1 parent 36ffdd0 commit 4e4c32b4d13cdcd9bab285bdc89503d658b1e4c9
@@ -1,3 +1,40 @@
2015-08-24 Yusuke Suzuki <utatane.tea@gmail.com>

[ES6] Return JSInternalPromise as result of evaluateModule
https://bugs.webkit.org/show_bug.cgi?id=148173

Reviewed by Saam Barati.

Now evaluateModule returns JSInternalPromise* as its result value.
When an error occurs while loading or executing the modules,
this promise is rejected by that error. By leveraging this, we implemented
asynchronous error reporting when executing the modules in JSC shell.

And this patch also changes the evaluateModule signature to accept the entry
point by the moduleName. By using it, JSC shell can start executing the modules
with the entry point module name.

* builtins/ModuleLoaderObject.js:
(loadModule):
* jsc.cpp:
(dumpException):
(runWithScripts):
* runtime/Completion.cpp:
(JSC::evaluateModule):
* runtime/Completion.h:
* runtime/JSInternalPromise.cpp:
(JSC::JSInternalPromise::then):
* runtime/JSInternalPromise.h:
* runtime/ModuleLoaderObject.cpp:
(JSC::ModuleLoaderObject::requestInstantiateAll):
(JSC::ModuleLoaderObject::loadModule):
(JSC::ModuleLoaderObject::resolve):
(JSC::ModuleLoaderObject::fetch):
(JSC::ModuleLoaderObject::translate):
(JSC::ModuleLoaderObject::instantiate):
(JSC::moduleLoaderObjectParseModule):
* runtime/ModuleLoaderObject.h:

2015-08-24 Basile Clement <basile_clement@apple.com>

REPTACH is not a word
@@ -396,6 +396,24 @@ function requestInstantiateAll(key)
return this.requestResolveDependencies(key);
}

function loadModule(moduleName, referrer)
{
"use strict";

var loader = this;
// Loader.resolve hook point.
// resolve: moduleName => Promise(moduleKey)
// Take the name and resolve it to the unique identifier for the resource location.
// For example, take the "jquery" and return the URL for the resource.
return this.resolve(moduleName, referrer).then(function (key) {
// FIXME: Now, we don't implement the linking phase yet.
// So here, we just call requestInstantiateAll to only perform the module loading.
// At last, it should be replaced with requestReady.
// https://bugs.webkit.org/show_bug.cgi?id=148172
return loader.requestInstantiateAll(key);
});
}

function provide(key, stage, value)
{
"use strict";
@@ -1336,15 +1336,19 @@ int main(int argc, char** argv)
jscExit(res);
}

static void dumpException(GlobalObject* globalObject, JSValue exception)
{
printf("Exception: %s\n", exception.toString(globalObject->globalExec())->value(globalObject->globalExec()).utf8().data());
Identifier stackID = Identifier::fromString(globalObject->globalExec(), "stack");
JSValue stackValue = exception.get(globalObject->globalExec(), stackID);
if (!stackValue.isUndefinedOrNull())
printf("%s\n", stackValue.toString(globalObject->globalExec())->value(globalObject->globalExec()).utf8().data());
}

static void dumpException(GlobalObject* globalObject, NakedPtr<Exception> evaluationException)
{
if (evaluationException) {
printf("Exception: %s\n", evaluationException->value().toString(globalObject->globalExec())->value(globalObject->globalExec()).utf8().data());
Identifier stackID = Identifier::fromString(globalObject->globalExec(), "stack");
JSValue stackValue = evaluationException->value().get(globalObject->globalExec(), stackID);
if (!stackValue.isUndefinedOrNull())
printf("%s\n", stackValue.toString(globalObject->globalExec())->value(globalObject->globalExec()).utf8().data());
}
if (evaluationException)
dumpException(globalObject, evaluationException->value());
}

static bool runWithScripts(GlobalObject* globalObject, const Vector<Script>& scripts, bool dump, bool module)
@@ -1357,18 +1361,37 @@ static bool runWithScripts(GlobalObject* globalObject, const Vector<Script>& scr
JSC::Options::dumpGeneratedBytecodes() = true;

VM& vm = globalObject->vm();
bool success = true;

#if ENABLE(ES6_MODULES)
JSFunction* errorHandler = JSFunction::create(vm, globalObject, 1, String(), [&](ExecState* exec) {
success = false;
dumpException(globalObject, exec->argument(0));
return JSValue::encode(jsUndefined());
});
#endif

#if ENABLE(SAMPLING_FLAGS)
SamplingFlags::start();
#endif

bool success = true;
for (size_t i = 0; i < scripts.size(); i++) {
#if ENABLE(ES6_MODULES)
JSInternalPromise* promise = nullptr;
#endif
if (scripts[i].isFile) {
fileName = scripts[i].argument;
if (!fillBufferWithContentsOfFile(fileName, scriptBuffer))
return false; // fail early so we can catch missing files
script = scriptBuffer.data();
if (module) {
#if ENABLE(ES6_MODULES)
promise = evaluateModule(globalObject->globalExec(), fileName);
#else
RELEASE_ASSERT_NOT_REACHED();
#endif
} else {
if (!fillBufferWithContentsOfFile(fileName, scriptBuffer))
return false; // fail early so we can catch missing files
script = scriptBuffer.data();
}
} else {
script = scripts[i].argument;
fileName = ASCIILiteral("[Command Line]");
@@ -1378,10 +1401,10 @@ static bool runWithScripts(GlobalObject* globalObject, const Vector<Script>& scr

if (module) {
#if ENABLE(ES6_MODULES)
NakedPtr<Exception> evaluationException;
evaluateModule(globalObject->globalExec(), jscSource(script, fileName), evaluationException);
dumpException(globalObject, evaluationException);
if (!promise)
promise = evaluateModule(globalObject->globalExec(), jscSource(script, fileName));
globalObject->globalExec()->clearException();
promise->then(globalObject->globalExec(), nullptr, errorHandler);
globalObject->vm().drainMicrotasks();
#else
RELEASE_ASSERT_NOT_REACHED();
@@ -30,6 +30,8 @@
#include "Interpreter.h"
#include "JSCInlines.h"
#include "JSGlobalObject.h"
#include "JSInternalPromise.h"
#include "JSInternalPromiseDeferred.h"
#include "JSLock.h"
#include "JSModuleRecord.h"
#include "ModuleAnalyzer.h"
@@ -111,43 +113,63 @@ JSValue evaluate(ExecState* exec, const SourceCode& source, JSValue thisValue, N
return result;
}

void evaluateModule(ExecState* exec, const SourceCode& source, NakedPtr<Exception>& returnedException)
static JSInternalPromise* evaluateModule(const JSLockHolder&, ExecState* exec, JSGlobalObject* globalObject, JSValue moduleName, JSValue referrer)
{
return globalObject->moduleLoader()->loadModule(exec, moduleName, referrer);
}

static JSInternalPromise* evaluateModule(const JSLockHolder& lock, ExecState* exec, JSGlobalObject* globalObject, const Identifier& moduleName)
{
JSValue moduleNameValue;
if (moduleName.isSymbol())
moduleNameValue = Symbol::create(exec->vm(), static_cast<SymbolImpl&>(*moduleName.impl()));
else
moduleNameValue = jsString(&exec->vm(), moduleName.impl());

return evaluateModule(lock, exec, globalObject, moduleNameValue, jsUndefined());
}

JSInternalPromise* evaluateModule(ExecState* exec, const SourceCode& source)
{
JSLockHolder lock(exec);
RELEASE_ASSERT(exec->vm().atomicStringTable() == wtfThreadData().atomicStringTable());
RELEASE_ASSERT(!exec->vm().isCollectorBusy());

CodeProfiling profile(source);

JSGlobalObject* globalObject = exec->vmEntryGlobalObject();

// Generate the unique key for the source-provided module.
PrivateName privateName(PrivateName::Description, "EntryPointModule");
Symbol* key = Symbol::create(exec->vm(), *privateName.uid());

ModuleLoaderObject* moduleLoader = globalObject->moduleLoader();

// Insert the given source code to the ModuleLoader registry as the fetched registry entry.
moduleLoader->provide(exec, key, ModuleLoaderObject::Status::Fetch, source.toString());
globalObject->moduleLoader()->provide(exec, key, ModuleLoaderObject::Status::Fetch, source.toString());
if (exec->hadException()) {
returnedException = exec->exception();
JSValue exception = exec->exception()->value();
exec->clearException();
return;
JSInternalPromiseDeferred* deferred = JSInternalPromiseDeferred::create(exec, globalObject);
deferred->reject(exec, exception);
return deferred->promise();
}

// FIXME: Now, we don't implement the linking phase yet.
// So here, we just call requestInstantiateAll to only perform the module loading.
// At last, it should be replaced with requestReady.
// https://bugs.webkit.org/show_bug.cgi?id=148172
moduleLoader->requestInstantiateAll(exec, key);
return evaluateModule(lock, exec, globalObject, key, jsUndefined());
}

// FIXME: We should also handle the asynchronous Syntax Errors that will be delivered by the rejected promise.
// https://bugs.webkit.org/show_bug.cgi?id=148173
if (exec->hadException()) {
returnedException = exec->exception();
exec->clearException();
return;
}
JSInternalPromise* evaluateModule(ExecState* exec, const Identifier& moduleName)
{
JSLockHolder lock(exec);
RELEASE_ASSERT(exec->vm().atomicStringTable() == wtfThreadData().atomicStringTable());
RELEASE_ASSERT(!exec->vm().isCollectorBusy());

return evaluateModule(lock, exec, exec->vmEntryGlobalObject(), moduleName);
}

JSInternalPromise* evaluateModule(ExecState* exec, const String& moduleName)
{
JSLockHolder lock(exec);
RELEASE_ASSERT(exec->vm().atomicStringTable() == wtfThreadData().atomicStringTable());
RELEASE_ASSERT(!exec->vm().isCollectorBusy());

return evaluateModule(lock, exec, exec->vmEntryGlobalObject(), Identifier::fromString(exec, moduleName));
}

} // namespace JSC
@@ -34,6 +34,7 @@ class JSScope;
class ParserError;
class SourceCode;
class VM;
class JSInternalPromise;

JS_EXPORT_PRIVATE bool checkSyntax(VM&, const SourceCode&, ParserError&);
JS_EXPORT_PRIVATE bool checkSyntax(ExecState*, const SourceCode&, JSValue* exception = 0);
@@ -44,7 +45,9 @@ inline JSValue evaluate(ExecState* exec, const SourceCode& sourceCode, JSValue t
NakedPtr<Exception> unused;
return evaluate(exec, sourceCode, thisValue, unused);
}
JS_EXPORT_PRIVATE void evaluateModule(ExecState*, const SourceCode&, NakedPtr<Exception>& returnedException);
JS_EXPORT_PRIVATE JSInternalPromise* evaluateModule(ExecState*, const SourceCode&);
JS_EXPORT_PRIVATE JSInternalPromise* evaluateModule(ExecState*, const Identifier& moduleName);
JS_EXPORT_PRIVATE JSInternalPromise* evaluateModule(ExecState*, const String& moduleName);

} // namespace JSC

@@ -26,6 +26,7 @@
#include "config.h"
#include "JSInternalPromise.h"

#include "BuiltinNames.h"
#include "JSCJSValueInlines.h"
#include "JSCellInlines.h"
#include "StructureInlines.h"
@@ -51,4 +52,18 @@ JSInternalPromise::JSInternalPromise(VM& vm, Structure* structure)
{
}

JSInternalPromise* JSInternalPromise::then(ExecState* exec, JSFunction* onFulfilled, JSFunction* onRejected)
{
JSObject* function = jsCast<JSObject*>(get(exec, exec->propertyNames().builtinNames().thenPublicName()));
CallData callData;
CallType callType = JSC::getCallData(function, callData);
ASSERT(callType != CallTypeNone);

MarkedArgumentBuffer arguments;
arguments.append(onFulfilled ? onFulfilled : jsUndefined());
arguments.append(onRejected ? onRejected : jsUndefined());

return jsCast<JSInternalPromise*>(call(exec, function, callType, callData, this, arguments));
}

} // namespace JSC
@@ -30,6 +30,16 @@

namespace JSC {

class JSFunction;

// JSInternalPromise is completely separated instance from the JSPromise.
// Since its prototype and constructor are different from the exposed Promises' ones,
// all the user modification onto the exposed Promise does not have effect on JSInternalPromise.
//
// e.g.
// Replacing Promise.prototype.then with the user-customized one does not effect on JSInternalPromise.
//
// CAUTION: Must not leak the JSInternalPromise to the user space to keep its integrity.
class JSInternalPromise : public JSPromise {
public:
typedef JSPromise Base;
@@ -39,6 +49,8 @@ class JSInternalPromise : public JSPromise {

DECLARE_EXPORT_INFO;

JS_EXPORT_PRIVATE JSInternalPromise* then(ExecState*, JSFunction* = nullptr, JSFunction* = nullptr);

private:
JSInternalPromise(VM&, Structure*);
};

0 comments on commit 4e4c32b

Please sign in to comment.