From a814786a06eeae91e2689cc9dfde47eaebc1aec7 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 27 Oct 2017 01:43:51 +0200 Subject: [PATCH] src: improve module loader readability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Various improvements on readability, performance and conformity to the Node core coding style in the ESM loader C++ code: - `isolate` for the `Isolate*`, `context` for the `Local` - Less reliance on `auto` where it’s unnecessary/increases cognitive overhead - Shorter paths to failure via `.ToLocal()` & co - Do not keep pending exceptions around e.g. for failed `JSON` parsing - Use `Maybe` types to get explicit error status forwarding - Remove an unnecessary handle scope - Add `nullptr` checks for unwrapped host objects - Remove unused code - Use `CamelCase` for function names - Use `const Foo&` instead of copying values whenever possible - Pass along the `Environment*` explicitly PR-URL: https://github.com/nodejs/node/pull/16536 Reviewed-By: Stephen Belanger Reviewed-By: James M Snell Reviewed-By: Tobias Nießen Reviewed-By: Gireesh Punathil --- src/env.h | 1 + src/module_wrap.cc | 415 +++++++++++++++++++++++---------------------- src/module_wrap.h | 8 +- src/node_url.h | 10 ++ 4 files changed, 227 insertions(+), 207 deletions(-) diff --git a/src/env.h b/src/env.h index c8bb168df4d845..a09dd092ece1b0 100644 --- a/src/env.h +++ b/src/env.h @@ -185,6 +185,7 @@ class ModuleWrap; V(kill_signal_string, "killSignal") \ V(length_string, "length") \ V(mac_string, "mac") \ + V(main_string, "main") \ V(max_buffer_string, "maxBuffer") \ V(message_string, "message") \ V(minttl_string, "minttl") \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index c28fe6580e0802..3ba90e4e02d99b 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -14,7 +14,6 @@ namespace loader { using node::url::URL; using node::url::URL_FLAGS_FAILED; using v8::Context; -using v8::EscapableHandleScope; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; @@ -23,15 +22,18 @@ using v8::Integer; using v8::IntegrityLevel; using v8::Isolate; using v8::JSON; +using v8::Just; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::Module; +using v8::Nothing; using v8::Object; using v8::Promise; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; +using v8::TryCatch; using v8::Value; static const char* const EXTENSIONS[] = {".mjs", ".js", ".json", ".node"}; @@ -61,7 +63,7 @@ ModuleWrap::~ModuleWrap() { void ModuleWrap::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Isolate* iso = args.GetIsolate(); + Isolate* isolate = args.GetIsolate(); if (!args.IsConstructCall()) { env->ThrowError("constructor must be called using new"); @@ -79,7 +81,7 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { return; } - auto source_text = args[0].As(); + Local source_text = args[0].As(); if (!args[1]->IsString()) { env->ThrowError("second argument is not a string"); @@ -88,49 +90,44 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { Local url = args[1].As(); - Local mod; + Local module; // compile { ScriptOrigin origin(url, - Integer::New(iso, 0), - Integer::New(iso, 0), - False(iso), - Integer::New(iso, 0), - FIXED_ONE_BYTE_STRING(iso, ""), - False(iso), - False(iso), - True(iso)); + Integer::New(isolate, 0), // line offset + Integer::New(isolate, 0), // column offset + False(isolate), // is cross origin + Local(), // script id + Local(), // source map URL + False(isolate), // is opaque (?) + False(isolate), // is WASM + True(isolate)); // is ES6 module ScriptCompiler::Source source(source_text, origin); - auto maybe_mod = ScriptCompiler::CompileModule(iso, &source); - if (maybe_mod.IsEmpty()) { - return; - } - mod = maybe_mod.ToLocalChecked(); + if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) + return; } - auto that = args.This(); - auto ctx = that->CreationContext(); - auto url_str = FIXED_ONE_BYTE_STRING(iso, "url"); + Local that = args.This(); + Local context = that->CreationContext(); + Local url_str = FIXED_ONE_BYTE_STRING(isolate, "url"); - if (!that->Set(ctx, url_str, url).FromMaybe(false)) { + if (!that->Set(context, url_str, url).FromMaybe(false)) { return; } - ModuleWrap* obj = - new ModuleWrap(Environment::GetCurrent(ctx), that, mod, url); + ModuleWrap* obj = new ModuleWrap(env, that, module, url); - env->module_map.emplace(mod->GetIdentityHash(), obj); + env->module_map.emplace(module->GetIdentityHash(), obj); Wrap(that, obj); - that->SetIntegrityLevel(ctx, IntegrityLevel::kFrozen); + that->SetIntegrityLevel(context, IntegrityLevel::kFrozen); args.GetReturnValue().Set(that); } void ModuleWrap::Link(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Isolate* iso = args.GetIsolate(); - EscapableHandleScope handle_scope(iso); + Isolate* isolate = args.GetIsolate(); if (!args[0]->IsFunction()) { env->ThrowError("first argument is not a function"); return; @@ -138,18 +135,19 @@ void ModuleWrap::Link(const FunctionCallbackInfo& args) { Local resolver_arg = args[0].As(); - auto that = args.This(); + Local that = args.This(); ModuleWrap* obj = Unwrap(that); - auto mod_context = that->CreationContext(); + CHECK_NE(obj, nullptr); + Local mod_context = that->CreationContext(); if (obj->linked_) return; obj->linked_ = true; - Local mod(obj->module_.Get(iso)); + Local module(obj->module_.Get(isolate)); // call the dependency resolve callbacks - for (int i = 0; i < mod->GetModuleRequestsLength(); i++) { - Local specifier = mod->GetModuleRequest(i); - Utf8Value specifier_utf(env->isolate(), specifier); - std::string specifier_std(*specifier_utf, specifier_utf.length()); + for (int i = 0; i < module->GetModuleRequestsLength(); i++) { + Local specifier = module->GetModuleRequest(i); + Utf8Value specifier_utf8(env->isolate(), specifier); + std::string specifier_std(*specifier_utf8, specifier_utf8.length()); Local argv[] = { specifier @@ -169,17 +167,19 @@ void ModuleWrap::Link(const FunctionCallbackInfo& args) { obj->resolve_cache_[specifier_std].Reset(env->isolate(), resolve_promise); } - args.GetReturnValue().Set(handle_scope.Escape(that)); + args.GetReturnValue().Set(that); } void ModuleWrap::Instantiate(const FunctionCallbackInfo& args) { - auto iso = args.GetIsolate(); - auto that = args.This(); - auto ctx = that->CreationContext(); + Isolate* isolate = args.GetIsolate(); + Local that = args.This(); + Local context = that->CreationContext(); ModuleWrap* obj = Unwrap(that); - Local mod = obj->module_.Get(iso); - Maybe ok = mod->InstantiateModule(ctx, ModuleWrap::ResolveCallback); + CHECK_NE(obj, nullptr); + Local module = obj->module_.Get(isolate); + Maybe ok = + module->InstantiateModule(context, ModuleWrap::ResolveCallback); // clear resolve cache on instantiate for (auto& entry : obj->resolve_cache_) @@ -192,28 +192,28 @@ void ModuleWrap::Instantiate(const FunctionCallbackInfo& args) { } void ModuleWrap::Evaluate(const FunctionCallbackInfo& args) { - auto iso = args.GetIsolate(); - auto that = args.This(); - auto ctx = that->CreationContext(); + Isolate* isolate = args.GetIsolate(); + Local that = args.This(); + Local context = that->CreationContext(); ModuleWrap* obj = Unwrap(that); - auto result = obj->module_.Get(iso)->Evaluate(ctx); + CHECK_NE(obj, nullptr); + MaybeLocal result = obj->module_.Get(isolate)->Evaluate(context); if (result.IsEmpty()) { return; } - auto ret = result.ToLocalChecked(); - args.GetReturnValue().Set(ret); + args.GetReturnValue().Set(result.ToLocalChecked()); } void ModuleWrap::Namespace(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - auto isolate = args.GetIsolate(); - auto that = args.This(); + Isolate* isolate = args.GetIsolate(); + Local that = args.This(); ModuleWrap* obj = Unwrap(that); CHECK_NE(obj, nullptr); - auto module = obj->module_.Get(isolate); + Local module = obj->module_.Get(isolate); switch (module->GetStatus()) { default: @@ -225,7 +225,7 @@ void ModuleWrap::Namespace(const FunctionCallbackInfo& args) { break; } - auto result = module->GetModuleNamespace(); + Local result = module->GetModuleNamespace(); args.GetReturnValue().Set(result); } @@ -233,7 +233,7 @@ MaybeLocal ModuleWrap::ResolveCallback(Local context, Local specifier, Local referrer) { Environment* env = Environment::GetCurrent(context); - Isolate* iso = Isolate::GetCurrent(); + Isolate* isolate = env->isolate(); if (env->module_map.count(referrer->GetIdentityHash()) == 0) { env->ThrowError("linking error, unknown module"); return MaybeLocal(); @@ -253,8 +253,8 @@ MaybeLocal ModuleWrap::ResolveCallback(Local context, return MaybeLocal(); } - Utf8Value specifier_utf(env->isolate(), specifier); - std::string specifier_std(*specifier_utf, specifier_utf.length()); + Utf8Value specifier_utf8(env->isolate(), specifier); + std::string specifier_std(*specifier_utf8, specifier_utf8.length()); if (dependent->resolve_cache_.count(specifier_std) != 1) { env->ThrowError("linking error, not in local cache"); @@ -262,7 +262,7 @@ MaybeLocal ModuleWrap::ResolveCallback(Local context, } Local resolve_promise = - dependent->resolve_cache_[specifier_std].Get(iso); + dependent->resolve_cache_[specifier_std].Get(isolate); if (resolve_promise->State() != Promise::kFulfilled) { env->ThrowError("linking error, dependency promises must be resolved on " @@ -270,176 +270,177 @@ MaybeLocal ModuleWrap::ResolveCallback(Local context, return MaybeLocal(); } - auto module_object = resolve_promise->Result().As(); + Local module_object = resolve_promise->Result().As(); if (module_object.IsEmpty() || !module_object->IsObject()) { env->ThrowError("linking error, expected a valid module object from " "resolver"); return MaybeLocal(); } - ModuleWrap* mod; - ASSIGN_OR_RETURN_UNWRAP(&mod, module_object, MaybeLocal()); - return mod->module_.Get(env->isolate()); + ModuleWrap* module; + ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal()); + return module->module_.Get(env->isolate()); } namespace { -URL __init_cwd() { - std::string specifier = "file://"; -#ifdef _WIN32 - // MAX_PATH is in characters, not bytes. Make sure we have enough headroom. - char buf[MAX_PATH * 4]; -#else - char buf[PATH_MAX]; -#endif - - size_t cwd_len = sizeof(buf); - int err = uv_cwd(buf, &cwd_len); - if (err) { - return URL(""); - } - specifier += buf; - specifier += "/"; - return URL(specifier); -} -static URL INITIAL_CWD(__init_cwd()); -inline bool is_relative_or_absolute_path(std::string specifier) { - auto len = specifier.length(); - if (len <= 0) { +// Tests whether a path starts with /, ./ or ../ +// In WhatWG terminology, the alternative case is called a "bare" specifier +// (e.g. in `import "jquery"`). +inline bool ShouldBeTreatedAsRelativeOrAbsolutePath( + const std::string& specifier) { + size_t len = specifier.length(); + if (len == 0) return false; - } else if (specifier[0] == '/') { + if (specifier[0] == '/') { return true; } else if (specifier[0] == '.') { - if (len == 1 || specifier[1] == '/') { + if (len == 1 || specifier[1] == '/') return true; - } else if (specifier[1] == '.') { - if (len == 2 || specifier[2] == '/') { + if (specifier[1] == '.') { + if (len == 2 || specifier[2] == '/') return true; - } } } return false; } -struct read_result { - bool had_error = false; - std::string source; -} read_result; -inline const struct read_result read_file(uv_file file) { - struct read_result ret; - std::string src; + +std::string ReadFile(uv_file file) { + std::string contents; uv_fs_t req; - void* base = malloc(4096); - if (base == nullptr) { - ret.had_error = true; - return ret; - } - uv_buf_t buf = uv_buf_init(static_cast(base), 4096); - uv_fs_read(uv_default_loop(), &req, file, &buf, 1, 0, nullptr); - while (req.result > 0) { - src += std::string(static_cast(buf.base), req.result); - uv_fs_read(uv_default_loop(), &req, file, &buf, 1, src.length(), nullptr); - } - ret.source = src; - return ret; + char buffer_memory[4096]; + uv_buf_t buf = uv_buf_init(buffer_memory, sizeof(buffer_memory)); + do { + uv_fs_read(uv_default_loop(), + &req, + file, + &buf, + 1, + contents.length(), // offset + nullptr); + if (req.result <= 0) + break; + contents.append(buf.base, req.result); + } while (true); + return contents; } -struct file_check { - bool failed = true; - uv_file file = -1; + +enum CheckFileOptions { + LEAVE_OPEN_AFTER_CHECK, + CLOSE_AFTER_CHECK }; -inline const struct file_check check_file(const URL& search, - bool close = false, - bool allow_dir = false) { - struct file_check ret; + +Maybe CheckFile(const URL& search, + CheckFileOptions opt = CLOSE_AFTER_CHECK) { uv_fs_t fs_req; std::string path = search.ToFilePath(); if (path.empty()) { - return ret; + return Nothing(); } uv_fs_open(nullptr, &fs_req, path.c_str(), O_RDONLY, 0, nullptr); - auto fd = fs_req.result; + uv_file fd = fs_req.result; if (fd < 0) { - return ret; + return Nothing(); } - if (!allow_dir) { - uv_fs_fstat(nullptr, &fs_req, fd, nullptr); - if (fs_req.statbuf.st_mode & S_IFDIR) { - uv_fs_close(nullptr, &fs_req, fd, nullptr); - return ret; - } + + uv_fs_fstat(nullptr, &fs_req, fd, nullptr); + if (fs_req.statbuf.st_mode & S_IFDIR) { + uv_fs_close(nullptr, &fs_req, fd, nullptr); + return Nothing(); } - ret.failed = false; - ret.file = fd; - if (close) uv_fs_close(nullptr, &fs_req, fd, nullptr); - return ret; + + if (opt == CLOSE_AFTER_CHECK) + uv_fs_close(nullptr, &fs_req, fd, nullptr); + return Just(fd); } -URL resolve_extensions(const URL& search, bool check_exact = true) { - if (check_exact) { - auto check = check_file(search, true); - if (!check.failed) { - return search; + +enum ResolveExtensionsOptions { + TRY_EXACT_NAME, + ONLY_VIA_EXTENSIONS +}; + +template +Maybe ResolveExtensions(const URL& search) { + if (options == TRY_EXACT_NAME) { + Maybe check = CheckFile(search); + if (!check.IsNothing()) { + return Just(search); } } - for (auto extension : EXTENSIONS) { + + for (const char* extension : EXTENSIONS) { URL guess(search.path() + extension, &search); - auto check = check_file(guess, true); - if (!check.failed) { - return guess; + Maybe check = CheckFile(guess); + if (!check.IsNothing()) { + return Just(guess); } } - return URL(""); + + return Nothing(); } -inline URL resolve_index(const URL& search) { - return resolve_extensions(URL("index", &search), false); + +inline Maybe ResolveIndex(const URL& search) { + return ResolveExtensions(URL("index", search)); } -URL resolve_main(const URL& search) { + +Maybe ResolveMain(Environment* env, const URL& search) { URL pkg("package.json", &search); - auto check = check_file(pkg); - if (!check.failed) { - auto iso = Isolate::GetCurrent(); - auto ctx = iso->GetCurrentContext(); - auto read = read_file(check.file); - uv_fs_t fs_req; - // if we fail to close :-/ - uv_fs_close(nullptr, &fs_req, check.file, nullptr); - if (read.had_error) return URL(""); - std::string pkg_src = read.source; - Local src = - String::NewFromUtf8(iso, pkg_src.c_str(), - String::kNormalString, pkg_src.length()); - if (src.IsEmpty()) return URL(""); - auto maybe_pkg_json = JSON::Parse(ctx, src); - if (maybe_pkg_json.IsEmpty()) return URL(""); - auto pkg_json_obj = maybe_pkg_json.ToLocalChecked().As(); - if (!pkg_json_obj->IsObject()) return URL(""); - auto maybe_pkg_main = pkg_json_obj->Get( - ctx, FIXED_ONE_BYTE_STRING(iso, "main")); - if (maybe_pkg_main.IsEmpty()) return URL(""); - auto pkg_main_str = maybe_pkg_main.ToLocalChecked().As(); - if (!pkg_main_str->IsString()) return URL(""); - Utf8Value main_utf8(iso, pkg_main_str); - std::string main_std(*main_utf8, main_utf8.length()); - if (!is_relative_or_absolute_path(main_std)) { - main_std.insert(0, "./"); - } - return Resolve(main_std, &search); + Maybe check = CheckFile(pkg, LEAVE_OPEN_AFTER_CHECK); + if (check.IsNothing()) { + return Nothing(); } - return URL(""); + + Isolate* isolate = env->isolate(); + Local context = isolate->GetCurrentContext(); + std::string pkg_src = ReadFile(check.FromJust()); + uv_fs_t fs_req; + uv_fs_close(nullptr, &fs_req, check.FromJust(), nullptr); + + // It's not okay for the called of this method to not be able to tell + // whether an exception is pending or not. + TryCatch try_catch(isolate); + + Local src; + if (!String::NewFromUtf8(isolate, + pkg_src.c_str(), + v8::NewStringType::kNormal, + pkg_src.length()).ToLocal(&src)) { + return Nothing(); + } + + Local pkg_json; + if (!JSON::Parse(context, src).ToLocal(&pkg_json) || !pkg_json->IsObject()) + return Nothing(); + Local pkg_main; + if (!pkg_json.As()->Get(context, env->main_string()) + .ToLocal(&pkg_main) || !pkg_main->IsString()) { + return Nothing(); + } + Utf8Value main_utf8(isolate, pkg_main.As()); + std::string main_std(*main_utf8, main_utf8.length()); + if (!ShouldBeTreatedAsRelativeOrAbsolutePath(main_std)) { + main_std.insert(0, "./"); + } + return Resolve(env, main_std, search); } -URL resolve_module(std::string specifier, const URL* base) { + +Maybe ResolveModule(Environment* env, + const std::string& specifier, + const URL& base) { URL parent(".", base); URL dir(""); do { dir = parent; - auto check = Resolve("./node_modules/" + specifier, &dir, true); - if (!(check.flags() & URL_FLAGS_FAILED)) { - const auto limit = specifier.find('/'); - const auto spec_len = limit == std::string::npos ? - specifier.length() : - limit + 1; + Maybe check = Resolve(env, "./node_modules/" + specifier, dir, true); + if (!check.IsNothing()) { + const size_t limit = specifier.find('/'); + const size_t spec_len = + limit == std::string::npos ? specifier.length() : + limit + 1; std::string chroot = dir.path() + "node_modules/" + specifier.substr(0, spec_len); - if (check.path().substr(0, chroot.length()) != chroot) { - return URL(""); + if (check.FromJust().path().substr(0, chroot.length()) != chroot) { + return Nothing(); } return check; } else { @@ -447,45 +448,51 @@ URL resolve_module(std::string specifier, const URL* base) { } parent = URL("..", &dir); } while (parent.path() != dir.path()); - return URL(""); + return Nothing(); } -URL resolve_directory(const URL& search, bool read_pkg_json) { +Maybe ResolveDirectory(Environment* env, + const URL& search, + bool read_pkg_json) { if (read_pkg_json) { - auto main = resolve_main(search); - if (!(main.flags() & URL_FLAGS_FAILED)) return main; + Maybe main = ResolveMain(env, search); + if (!main.IsNothing()) + return main; } - return resolve_index(search); + return ResolveIndex(search); } } // anonymous namespace -URL Resolve(std::string specifier, const URL* base, bool read_pkg_json) { +Maybe Resolve(Environment* env, + const std::string& specifier, + const URL& base, + bool read_pkg_json) { URL pure_url(specifier); if (!(pure_url.flags() & URL_FLAGS_FAILED)) { // just check existence, without altering - auto check = check_file(pure_url, true); - if (check.failed) { - return URL(""); + Maybe check = CheckFile(pure_url); + if (check.IsNothing()) { + return Nothing(); } - return pure_url; + return Just(pure_url); } if (specifier.length() == 0) { - return URL(""); + return Nothing(); } - if (is_relative_or_absolute_path(specifier)) { + if (ShouldBeTreatedAsRelativeOrAbsolutePath(specifier)) { URL resolved(specifier, base); - auto file = resolve_extensions(resolved); - if (!(file.flags() & URL_FLAGS_FAILED)) return file; + Maybe file = ResolveExtensions(resolved); + if (!file.IsNothing()) + return file; if (specifier.back() != '/') { resolved = URL(specifier + "/", base); } - return resolve_directory(resolved, read_pkg_json); + return ResolveDirectory(env, resolved, read_pkg_json); } else { - return resolve_module(specifier, base); + return ResolveModule(env, specifier, base); } - UNREACHABLE(); } void ModuleWrap::Resolve(const FunctionCallbackInfo& args) { @@ -504,34 +511,34 @@ void ModuleWrap::Resolve(const FunctionCallbackInfo& args) { env->ThrowError("first argument is not a string"); return; } - Utf8Value specifier_utf(env->isolate(), args[0]); + Utf8Value specifier_utf8(env->isolate(), args[0]); + std::string specifier_std(*specifier_utf8, specifier_utf8.length()); if (!args[1]->IsString()) { env->ThrowError("second argument is not a string"); return; } - Utf8Value url_utf(env->isolate(), args[1]); - URL url(*url_utf, url_utf.length()); + Utf8Value url_utf8(env->isolate(), args[1]); + URL url(*url_utf8, url_utf8.length()); if (url.flags() & URL_FLAGS_FAILED) { env->ThrowError("second argument is not a URL string"); return; } - URL result = node::loader::Resolve(*specifier_utf, &url, true); - if (result.flags() & URL_FLAGS_FAILED) { - std::string msg = "Cannot find module "; - msg += *specifier_utf; + Maybe result = node::loader::Resolve(env, specifier_std, url, true); + if (result.IsNothing() || (result.FromJust().flags() & URL_FLAGS_FAILED)) { + std::string msg = "Cannot find module " + specifier_std; env->ThrowError(msg.c_str()); return; } - args.GetReturnValue().Set(result.ToObject(env)); + args.GetReturnValue().Set(result.FromJust().ToObject(env)); } void ModuleWrap::Initialize(Local target, - Local unused, - Local context) { + Local unused, + Local context) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); diff --git a/src/module_wrap.h b/src/module_wrap.h index e0f7ce5bd2dddc..980c802c1f1952 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -12,8 +12,10 @@ namespace node { namespace loader { -node::url::URL Resolve(std::string specifier, const node::url::URL* base, - bool read_pkg_json = false); +v8::Maybe Resolve(Environment* env, + const std::string& specifier, + const url::URL& base, + bool read_pkg_json = false); class ModuleWrap : public BaseObject { public: @@ -23,7 +25,7 @@ class ModuleWrap : public BaseObject { v8::Local context); private: - ModuleWrap(node::Environment* env, + ModuleWrap(Environment* env, v8::Local object, v8::Local module, v8::Local url); diff --git a/src/node_url.h b/src/node_url.h index 872f2fbc97f65c..6b526d15b07703 100644 --- a/src/node_url.h +++ b/src/node_url.h @@ -118,6 +118,9 @@ class URL { URL(std::string input, const URL* base) : URL(input.c_str(), input.length(), base) {} + URL(std::string input, const URL& base) : + URL(input.c_str(), input.length(), &base) {} + URL(std::string input, std::string base) : URL(input.c_str(), input.length(), base.c_str(), base.length()) {} @@ -168,6 +171,13 @@ class URL { const Local ToObject(Environment* env) const; + URL(const URL&) = default; + URL& operator=(const URL&) = default; + URL(URL&&) = default; + URL& operator=(URL&&) = default; + + URL() : URL("") {} + private: struct url_data context_; };