mirror of https://github.com/pulumi/pulumi.git
231 lines
10 KiB
C++
231 lines
10 KiB
C++
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
|
|
|
#include <cstring>
|
|
#include <vector>
|
|
#include <node.h>
|
|
#include <src/api.h> // v8 internal APIs
|
|
#include <src/objects.h> // v8 internal APIs
|
|
#include <src/contexts.h> // v8 internal APIs
|
|
|
|
#if NODE_MAJOR_VERSION != 6 || NODE_MINOR_VERSION != 10
|
|
#error "The Pulumi Fabric SDK only supports Node.js 6.10.x at the moment"
|
|
#endif
|
|
|
|
namespace nativeruntime {
|
|
|
|
using v8::Array;
|
|
using v8::Context;
|
|
using v8::Exception;
|
|
using v8::Function;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::Integer;
|
|
using v8::Isolate;
|
|
using v8::Local;
|
|
using v8::MaybeLocal;
|
|
using v8::Null;
|
|
using v8::Object;
|
|
using v8::Script;
|
|
using v8::String;
|
|
using v8::Value;
|
|
|
|
// CreateClosure allocates a new closure object which matches the definition on the JavaScript side.
|
|
Local<Object> CreateClosure(Isolate* isolate, Local<String> code, Local<Object> environment) {
|
|
Local<Object> closure = Object::New(isolate);
|
|
closure->Set(String::NewFromUtf8(isolate, "code"), code);
|
|
closure->Set(String::NewFromUtf8(isolate, "runtime"), String::NewFromUtf8(isolate, "nodejs"));
|
|
closure->Set(String::NewFromUtf8(isolate, "environment"), environment);
|
|
return closure;
|
|
}
|
|
|
|
// Lookup restores a context and looks up a variable name inside of it.
|
|
Local<Value> Lookup(Isolate* isolate, v8::internal::Handle<v8::internal::Context> context, Local<String> name) {
|
|
// First perform the lookup in the current chain. This unfortunately requires accessing internal
|
|
// V8 APIs so that we can inspect the chain with the necessary flags and resulting objects.
|
|
int index;
|
|
v8::internal::PropertyAttributes attributes;
|
|
v8::internal::BindingFlags bflags;
|
|
v8::internal::Handle<v8::internal::String> hackname(
|
|
reinterpret_cast<v8::internal::String**>(const_cast<String*>(*name)));
|
|
v8::internal::Handle<v8::internal::Object> lookup =
|
|
context->Lookup(
|
|
hackname,
|
|
v8::internal::ContextLookupFlags::FOLLOW_CHAINS,
|
|
&index, &attributes, &bflags);
|
|
|
|
// Now check the result. There are several legal possibilities.
|
|
if (!lookup.is_null()) {
|
|
if (lookup->IsContext()) {
|
|
// The result was found in a context; index contains the slot number within that context.
|
|
v8::internal::Isolate* hackiso =
|
|
reinterpret_cast<v8::internal::Isolate*>(isolate);
|
|
return v8::Utils::Convert<v8::internal::Object, Object>(
|
|
v8::internal::FixedArray::get(v8::internal::Context::cast(*lookup), index, hackiso));
|
|
} else if (lookup->IsJSObject()) {
|
|
// The result was a named property inside of a context extension (such as eval); we can return it as-is.
|
|
return v8::Utils::Convert<v8::internal::Object, Object>(lookup);
|
|
}
|
|
}
|
|
|
|
// If we fell through, either the lookup is null, or the object wasn't of the expected type. In either case,
|
|
// this is an error (possibly a bug), and we will throw and return undefined so we can keep going.
|
|
char namestr[255];
|
|
name->WriteUtf8(namestr, 255);
|
|
Local<String> errormsg = String::Concat(
|
|
String::NewFromUtf8(isolate, "Unexpected missing variable in closure environment: "),
|
|
String::NewFromUtf8(isolate, namestr));
|
|
isolate->ThrowException(Exception::Error(errormsg));
|
|
return Local<Value>();
|
|
}
|
|
|
|
// MSVC 2015 (which we build with on Windows) does not treat strlen of a literal as a constexpr,
|
|
// so we can't use it to intialize our buffer size. So instead, we use sizeof() to compute the
|
|
// length, so we need a literal string instead of just a const char*.
|
|
#define BAD_PREFIX "[Function:"
|
|
#define STRLEN_LITERAL_ONLY(S) (sizeof((S))/sizeof(char) - 1)
|
|
|
|
Local<String> SerializeFunctionCode(Isolate *isolate, Local<Function> func) {
|
|
// Serialize the code simply by calling toString on the Function.
|
|
Local<Function> toString = Local<Function>::Cast(
|
|
func->Get(String::NewFromUtf8(isolate, "toString")));
|
|
Local<String> code = Local<String>::Cast(toString->Call(func, 0, nullptr));
|
|
|
|
// Ensure that the code is a function expression (including arrows), and not a definition, etc.
|
|
constexpr size_t badprefixLength = STRLEN_LITERAL_ONLY(BAD_PREFIX);
|
|
if (code->Length() >= (int)badprefixLength) {
|
|
char buf[badprefixLength];
|
|
code->WriteUtf8(buf, badprefixLength);
|
|
if (!strncmp(BAD_PREFIX, buf, badprefixLength)) {
|
|
isolate->ThrowException(Exception::TypeError(
|
|
String::NewFromUtf8(isolate,
|
|
"Cannot serialize non-expression functions (such as definitions and generators)")));
|
|
}
|
|
}
|
|
|
|
// Wrap the serialized function text in ()s so that it's a legal top-level script/module element.
|
|
return String::Concat(
|
|
String::Concat(String::NewFromUtf8(isolate, "("), code),
|
|
String::NewFromUtf8(isolate, ")"));
|
|
}
|
|
#undef BAD_PREFIX
|
|
#undef STRLEN_LITERAL_ONLY
|
|
|
|
// SerializeFunction serializes a JavaScript function expression and its associated closure environment.
|
|
Local<Value> SerializeFunction(Isolate *isolate, Local<Function> func,
|
|
Local<Function> freeVarsFunc, Local<Function> envEntryFunc) {
|
|
// Get at the innards of the function. Unfortunately, we need to use internal V8 APIs to do this,
|
|
// as the closest public function, CreationContext, intentionally returns the non-closure Context for
|
|
// Function objects (it returns the constructor context, which is not what we want).
|
|
v8::internal::Handle<v8::internal::JSFunction> hackfunc(
|
|
reinterpret_cast<v8::internal::JSFunction**>(const_cast<Function*>(*func)));
|
|
v8::internal::Handle<v8::internal::Context> lexical(hackfunc->context());
|
|
|
|
// Get the code as a string.
|
|
Local<String> code = SerializeFunctionCode(isolate, func);
|
|
|
|
// Compute the free variables by invoking the callback.
|
|
const unsigned freeVarsArgc = 1;
|
|
Local<Value> freeVarsArgv[freeVarsArgc] = { code };
|
|
Local<Value> freeVarsRet = freeVarsFunc->Call(Null(isolate), freeVarsArgc, freeVarsArgv);
|
|
if (freeVarsRet.IsEmpty()) {
|
|
// Only empty if the function threw an exception. Return early to propagate it.
|
|
return Local<Value>();
|
|
} else if (!freeVarsRet->IsArray()) {
|
|
isolate->ThrowException(Exception::TypeError(
|
|
String::NewFromUtf8(isolate, "Free variables return expected to be an Array")));
|
|
return Local<Value>();
|
|
}
|
|
|
|
// Now check all elements and produce a vector we can use below.
|
|
std::vector<Local<String>> freeVars;
|
|
Local<Array> freeVarsArray = Local<Array>::Cast(freeVarsRet);
|
|
for (uint32_t i = 0; i < freeVarsArray->Length(); i++) {
|
|
Local<Integer> index = Integer::New(isolate, i);
|
|
Local<Value> elem = freeVarsArray->Get(index);
|
|
if (elem.IsEmpty() || !elem->IsString()) {
|
|
isolate->ThrowException(Exception::TypeError(
|
|
String::NewFromUtf8(isolate, "Free variable Array must contain only String elements")));
|
|
return Local<Value>();
|
|
}
|
|
freeVars.push_back(Local<String>::Cast(elem));
|
|
}
|
|
|
|
// Next, serialize all free variables as they exist in the function's original lexical environment.
|
|
Local<Object> environment = Object::New(isolate);
|
|
for (std::vector<Local<String>>::iterator it = freeVars.begin(); it != freeVars.end(); ++it) {
|
|
// Look up the variable in the lexical closure of the function and then serialize it.
|
|
Local<String> freevar = *it;
|
|
Local<Value> v = Lookup(isolate, lexical, freevar);
|
|
if (v.IsEmpty()) {
|
|
// Only empty if an error was thrown; bail eagerly to propagate it.
|
|
return Local<Value>();
|
|
}
|
|
const unsigned envEntryArgc = 1;
|
|
Local<Value> envEntryArgv[envEntryArgc] = { v };
|
|
Local<Value> envEntry = envEntryFunc->Call(Null(isolate), envEntryArgc, envEntryArgv);
|
|
if (envEntry.IsEmpty()) {
|
|
return Local<Value>();
|
|
}
|
|
environment->Set(freevar, envEntry);
|
|
}
|
|
|
|
// Finally, produce a closure object with all the appropriate records, and return it.
|
|
return CreateClosure(isolate, code, environment);
|
|
}
|
|
|
|
// serializeClosure serializes a function and its closure environment into a form that is amenable to persistence
|
|
// as simple JSON. Like toString, it includes the full text of the function's source code, suitable for execution.
|
|
// Unlike toString, it actually includes information about the captured environment.
|
|
void SerializeClosure(const FunctionCallbackInfo<Value>& args) {
|
|
Isolate* isolate = args.GetIsolate();
|
|
|
|
// Ensure the first argument is a proper function expression object.
|
|
if (args.Length() < 1 || args[0]->IsUndefined()) {
|
|
isolate->ThrowException(Exception::TypeError(
|
|
String::NewFromUtf8(isolate, "Missing required function argument")));
|
|
return;
|
|
} else if (!args[0]->IsFunction()) {
|
|
isolate->ThrowException(Exception::TypeError(
|
|
String::NewFromUtf8(isolate, "Function argument must be a Function object")));
|
|
return;
|
|
}
|
|
Local<Function> func = Local<Function>::Cast(args[0]);
|
|
|
|
// And that the second is a callback we can use to compute the free variables list.
|
|
if (args.Length() < 2 || args[1]->IsUndefined()) {
|
|
isolate->ThrowException(Exception::TypeError(
|
|
String::NewFromUtf8(isolate, "Missing required free variables calculator")));
|
|
return;
|
|
} else if (!args[1]->IsFunction()) {
|
|
isolate->ThrowException(Exception::TypeError(
|
|
String::NewFromUtf8(isolate, "Free variables argument must be a Function object")));
|
|
return;
|
|
}
|
|
Local<Function> freeVarsFunc = Local<Function>::Cast(args[1]);
|
|
|
|
// And that the third is a callback we can use to serialize environment entries.
|
|
if (args.Length() < 3 || args[2]->IsUndefined()) {
|
|
isolate->ThrowException(Exception::TypeError(
|
|
String::NewFromUtf8(isolate, "Missing required env-entry serializer function")));
|
|
return;
|
|
} else if (!args[2]->IsFunction()) {
|
|
isolate->ThrowException(Exception::TypeError(
|
|
String::NewFromUtf8(isolate, "Env-entry serializer argument must be a Function object")));
|
|
return;
|
|
}
|
|
Local<Function> envEntryFunc = Local<Function>::Cast(args[2]);
|
|
|
|
// Now go ahead and serialize it, and return the result.
|
|
Local<Value> closure = SerializeFunction(isolate, func, freeVarsFunc, envEntryFunc);
|
|
if (!closure.IsEmpty()) {
|
|
args.GetReturnValue().Set(closure);
|
|
}
|
|
}
|
|
|
|
void init(Local<Object> exports) {
|
|
NODE_SET_METHOD(exports, "serializeClosure", SerializeClosure);
|
|
}
|
|
|
|
NODE_MODULE(nativeruntime, init)
|
|
|
|
} // namespace nativeruntime
|