Browse Source

buffer: switch API to return MaybeLocal<T>

Instead of aborting in case of internal failure, return an empty
Local<Object>. Using the MaybeLocal<T> API, users must check their
return values.

PR-URL: https://github.com/nodejs/io.js/pull/1825
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Trevor Norris 10 years ago
committed by Chris Dickinson
parent
commit
2903030cb7
  1. 19
      src/js_stream.cc
  2. 132
      src/node_buffer.cc
  3. 66
      src/node_buffer.h
  4. 64
      src/node_crypto.cc
  5. 2
      src/spawn_sync.cc
  6. 3
      src/stream_wrap.cc
  7. 6
      src/string_bytes.cc
  8. 2
      src/tls_wrap.cc
  9. 2
      src/udp_wrap.cc

19
src/js_stream.cc

@ -88,8 +88,11 @@ int JSStream::DoWrite(WriteWrap* w,
HandleScope scope(env()->isolate());
Local<Array> bufs_arr = Array::New(env()->isolate(), count);
for (size_t i = 0; i < count; i++)
bufs_arr->Set(i, Buffer::New(env(), bufs[i].base, bufs[i].len));
Local<Object> buf;
for (size_t i = 0; i < count; i++) {
buf = Buffer::New(env(), bufs[i].base, bufs[i].len).ToLocalChecked();
bufs_arr->Set(i, buf);
}
Local<Value> argv[] = {
w->object(),
@ -134,11 +137,13 @@ void JSStream::DoAlloc(const FunctionCallbackInfo<Value>& args) {
uv_buf_t buf;
wrap->OnAlloc(args[0]->Int32Value(), &buf);
args.GetReturnValue().Set(Buffer::New(wrap->env(),
buf.base,
buf.len,
FreeCallback,
nullptr));
Local<Object> vbuf = Buffer::New(
wrap->env(),
buf.base,
buf.len,
FreeCallback,
nullptr).ToLocalChecked();
return args.GetReturnValue().Set(vbuf);
}

132
src/node_buffer.cc

@ -68,6 +68,7 @@ using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Number;
using v8::Object;
using v8::Persistent;
@ -228,7 +229,9 @@ size_t Length(Handle<Object> obj) {
}
Local<Object> New(Isolate* isolate, Handle<String> string, enum encoding enc) {
MaybeLocal<Object> New(Isolate* isolate,
Local<String> string,
enum encoding enc) {
EscapableHandleScope scope(isolate);
size_t length = StringBytes::Size(isolate, string, enc);
@ -245,19 +248,26 @@ Local<Object> New(Isolate* isolate, Handle<String> string, enum encoding enc) {
CHECK_NE(data, nullptr);
}
Local<Object> buf = Use(isolate, data, actual);
return scope.Escape(buf);
Local<Object> buf;
if (Use(isolate, data, actual).ToLocal(&buf))
return scope.Escape(buf);
// Object failed to be created. Clean up resources.
free(data);
return Local<Object>();
}
Local<Object> New(Isolate* isolate, size_t length) {
MaybeLocal<Object> New(Isolate* isolate, size_t length) {
EscapableHandleScope handle_scope(isolate);
Local<Object> obj = Buffer::New(Environment::GetCurrent(isolate), length);
return handle_scope.Escape(obj);
Local<Object> obj;
if (Buffer::New(Environment::GetCurrent(isolate), length).ToLocal(&obj))
return handle_scope.Escape(obj);
return Local<Object>();
}
Local<Object> New(Environment* env, size_t length) {
MaybeLocal<Object> New(Environment* env, size_t length) {
EscapableHandleScope scope(env->isolate());
if (using_old_buffer) {
@ -280,13 +290,12 @@ Local<Object> New(Environment* env, size_t length) {
void* data;
if (length > 0) {
data = malloc(length);
// NOTE: API change. Must check .IsEmpty() on the return object to see if
// the data was able to be allocated.
if (data == nullptr)
return Local<Object>();
} else {
data = nullptr;
}
Local<ArrayBuffer> ab =
ArrayBuffer::New(env->isolate(),
data,
@ -295,25 +304,27 @@ Local<Object> New(Environment* env, size_t length) {
Local<Uint8Array> ui = Uint8Array::New(ab, 0, length);
Maybe<bool> mb =
ui->SetPrototype(env->context(), env->buffer_prototype_object());
if (!mb.FromMaybe(false)) {
FatalError("node::Buffer::New(Environment*, size_t)",
"Could not set Object prototype");
UNREACHABLE();
}
return scope.Escape(ui);
if (mb.FromMaybe(false))
return scope.Escape(ui);
// Object failed to be created. Clean up resources.
free(data);
return Local<Object>();
}
Local<Object> New(Isolate* isolate, const char* data, size_t length) {
MaybeLocal<Object> New(Isolate* isolate, const char* data, size_t length) {
Environment* env = Environment::GetCurrent(isolate);
EscapableHandleScope handle_scope(env->isolate());
Local<Object> obj = Buffer::New(env, data, length);
return handle_scope.Escape(obj);
Local<Object> obj;
if (Buffer::New(env, data, length).ToLocal(&obj))
return handle_scope.Escape(obj);
return Local<Object>();
}
// Make a copy of "data". Why this isn't called "Copy", we'll never know.
Local<Object> New(Environment* env, const char* data, size_t length) {
MaybeLocal<Object> New(Environment* env, const char* data, size_t length) {
EscapableHandleScope scope(env->isolate());
if (using_old_buffer) {
@ -347,8 +358,6 @@ Local<Object> New(Environment* env, const char* data, size_t length) {
if (length > 0) {
CHECK_NE(data, nullptr);
new_data = malloc(length);
// NOTE: API change. Must check .IsEmpty() on the return object to see if
// the data was able to be allocated.
if (new_data == nullptr)
return Local<Object>();
memcpy(new_data, data, length);
@ -364,33 +373,34 @@ Local<Object> New(Environment* env, const char* data, size_t length) {
Local<Uint8Array> ui = Uint8Array::New(ab, 0, length);
Maybe<bool> mb =
ui->SetPrototype(env->context(), env->buffer_prototype_object());
if (!mb.FromMaybe(false)) {
FatalError("node::Buffer::New(Environment*, char*, size_t)",
"Could not set Object prototype");
UNREACHABLE();
}
if (mb.FromMaybe(false))
return scope.Escape(ui);
return scope.Escape(ui);
// Object failed to be created. Clean up resources.
free(new_data);
return Local<Object>();
}
Local<Object> New(Isolate* isolate,
char* data,
size_t length,
FreeCallback callback,
void* hint) {
MaybeLocal<Object> New(Isolate* isolate,
char* data,
size_t length,
FreeCallback callback,
void* hint) {
Environment* env = Environment::GetCurrent(isolate);
EscapableHandleScope handle_scope(env->isolate());
Local<Object> obj = Buffer::New(env, data, length, callback, hint);
return handle_scope.Escape(obj);
Local<Object> obj;
if (Buffer::New(env, data, length, callback, hint).ToLocal(&obj))
return handle_scope.Escape(obj);
return Local<Object>();
}
Local<Object> New(Environment* env,
char* data,
size_t length,
FreeCallback callback,
void* hint) {
MaybeLocal<Object> New(Environment* env,
char* data,
size_t length,
FreeCallback callback,
void* hint) {
EscapableHandleScope scope(env->isolate());
if (using_old_buffer) {
@ -410,26 +420,26 @@ Local<Object> New(Environment* env,
Local<Uint8Array> ui = Uint8Array::New(ab, 0, length);
Maybe<bool> mb =
ui->SetPrototype(env->context(), env->buffer_prototype_object());
if (!mb.FromMaybe(false)) {
FatalError("node::Buffer::New(Environment*, char*, size_t,"
" FreeCallback, void*)",
"Could not set Object prototype");
UNREACHABLE();
}
if (!mb.FromMaybe(false))
return Local<Object>();
CallbackInfo::New(env->isolate(), ui, callback, hint);
return scope.Escape(ui);
}
Local<Object> Use(Isolate* isolate, char* data, size_t length) {
MaybeLocal<Object> Use(Isolate* isolate, char* data, size_t length) {
Environment* env = Environment::GetCurrent(isolate);
EscapableHandleScope handle_scope(env->isolate());
Local<Object> obj = Buffer::Use(env, data, length);
return handle_scope.Escape(obj);
Local<Object> obj;
if (Buffer::Use(env, data, length).ToLocal(&obj))
return handle_scope.Escape(obj);
return Local<Object>();
}
Local<Object> Use(Environment* env, char* data, size_t length) {
MaybeLocal<Object> Use(Environment* env, char* data, size_t length) {
EscapableHandleScope scope(env->isolate());
if (using_old_buffer) {
@ -457,12 +467,9 @@ Local<Object> Use(Environment* env, char* data, size_t length) {
Local<Uint8Array> ui = Uint8Array::New(ab, 0, length);
Maybe<bool> mb =
ui->SetPrototype(env->context(), env->buffer_prototype_object());
if (!mb.FromMaybe(false)) {
FatalError("node::Buffer::Use(Environment*, char*, size_t)",
"Could not set Object prototype");
UNREACHABLE();
}
return scope.Escape(ui);
if (mb.FromMaybe(false))
return scope.Escape(ui);
return Local<Object>();
}
@ -495,8 +502,9 @@ void Create(const FunctionCallbackInfo<Value>& args) {
Local<Uint8Array> ui = Uint8Array::New(ab, 0, length);
Maybe<bool> mb =
ui->SetPrototype(env->context(), env->buffer_prototype_object());
if (mb.FromMaybe(false))
args.GetReturnValue().Set(ui);
if (!mb.FromMaybe(false))
return env->ThrowError("Unable to set Object prototype");
args.GetReturnValue().Set(ui);
}
@ -507,8 +515,9 @@ void CreateFromString(const FunctionCallbackInfo<Value>& args) {
enum encoding enc = ParseEncoding(args.GetIsolate(),
args[1].As<String>(),
UTF8);
Local<Object> buf = New(args.GetIsolate(), args[0].As<String>(), enc);
args.GetReturnValue().Set(buf);
Local<Object> buf;
if (New(args.GetIsolate(), args[0].As<String>(), enc).ToLocal(&buf))
args.GetReturnValue().Set(buf);
}
@ -529,8 +538,9 @@ void Slice(const FunctionCallbackInfo<Value>& args) {
Local<Uint8Array> ui = Uint8Array::New(ab, start, size);
Maybe<bool> mb =
ui->SetPrototype(env->context(), env->buffer_prototype_object());
if (mb.FromMaybe(false))
args.GetReturnValue().Set(ui);
if (!mb.FromMaybe(false))
env->ThrowError("Unable to set Object prototype");
args.GetReturnValue().Set(ui);
}

66
src/node_buffer.h

@ -24,50 +24,52 @@ NODE_EXTERN size_t Length(v8::Handle<v8::Value> val);
NODE_EXTERN size_t Length(v8::Handle<v8::Object> val);
// public constructor
NODE_EXTERN v8::Local<v8::Object> New(v8::Isolate* isolate, size_t length);
NODE_EXTERN v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate, size_t length);
NODE_DEPRECATED("Use New(isolate, ...)",
inline v8::Local<v8::Object> New(size_t length) {
inline v8::MaybeLocal<v8::Object> New(size_t length) {
return New(v8::Isolate::GetCurrent(), length);
})
// public constructor from string
NODE_EXTERN v8::Local<v8::Object> New(v8::Isolate* isolate,
v8::Handle<v8::String> string,
enum encoding enc = UTF8);
NODE_EXTERN v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate,
v8::Handle<v8::String> string,
enum encoding enc = UTF8);
NODE_DEPRECATED("Use New(isolate, ...)",
inline v8::Local<v8::Object> New(v8::Handle<v8::String> string,
enum encoding enc = UTF8) {
inline v8::MaybeLocal<v8::Object> New(
v8::Handle<v8::String> string,
enum encoding enc = UTF8) {
return New(v8::Isolate::GetCurrent(), string, enc);
})
// public constructor - data is copied
// TODO(trevnorris): should be something like Copy()
NODE_EXTERN v8::Local<v8::Object> New(v8::Isolate* isolate,
const char* data,
size_t len);
NODE_EXTERN v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate,
const char* data,
size_t len);
NODE_DEPRECATED("Use New(isolate, ...)",
inline v8::Local<v8::Object> New(const char* data, size_t len) {
inline v8::MaybeLocal<v8::Object> New(const char* data,
size_t len) {
return New(v8::Isolate::GetCurrent(), data, len);
})
// public constructor - data is used, callback is passed data on object gc
NODE_EXTERN v8::Local<v8::Object> New(v8::Isolate* isolate,
char* data,
size_t length,
FreeCallback callback,
void* hint);
NODE_EXTERN v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate,
char* data,
size_t length,
FreeCallback callback,
void* hint);
NODE_DEPRECATED("Use New(isolate, ...)",
inline v8::Local<v8::Object> New(char* data,
size_t length,
FreeCallback callback,
void* hint) {
inline v8::MaybeLocal<v8::Object> New(char* data,
size_t length,
FreeCallback callback,
void* hint) {
return New(v8::Isolate::GetCurrent(), data, length, callback, hint);
})
// public constructor - data is used.
// TODO(trevnorris): should be New() for consistency
NODE_EXTERN v8::Local<v8::Object> Use(v8::Isolate* isolate,
char* data,
size_t len);
NODE_EXTERN v8::MaybeLocal<v8::Object> Use(v8::Isolate* isolate,
char* data,
size_t len);
NODE_DEPRECATED("Use Use(isolate, ...)",
inline v8::Local<v8::Object> Use(char* data, size_t len) {
inline v8::MaybeLocal<v8::Object> Use(char* data, size_t len) {
return Use(v8::Isolate::GetCurrent(), data, len);
})
@ -90,14 +92,14 @@ static inline bool IsWithinBounds(size_t off, size_t len, size_t max) {
// src/node_internals.h due to a circular dependency issue with
// the smalloc.h and node_internals.h headers.
#if defined(NODE_WANT_INTERNALS)
v8::Local<v8::Object> New(Environment* env, size_t size);
v8::Local<v8::Object> New(Environment* env, const char* data, size_t len);
v8::Local<v8::Object> New(Environment* env,
char* data,
size_t length,
FreeCallback callback,
void* hint);
v8::Local<v8::Object> Use(Environment* env, char* data, size_t length);
v8::MaybeLocal<v8::Object> New(Environment* env, size_t size);
v8::MaybeLocal<v8::Object> New(Environment* env, const char* data, size_t len);
v8::MaybeLocal<v8::Object> New(Environment* env,
char* data,
size_t length,
FreeCallback callback,
void* hint);
v8::MaybeLocal<v8::Object> Use(Environment* env, char* data, size_t length);
#endif // defined(NODE_WANT_INTERNALS)
} // namespace Buffer

64
src/node_crypto.cc

@ -942,7 +942,7 @@ void SecureContext::GetTicketKeys(const FunctionCallbackInfo<Value>& args) {
SecureContext* wrap = Unwrap<SecureContext>(args.Holder());
Local<Object> buff = Buffer::New(wrap->env(), 48);
Local<Object> buff = Buffer::New(wrap->env(), 48).ToLocalChecked();
if (SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_,
Buffer::Data(buff),
Buffer::Length(buff)) != 1) {
@ -1006,7 +1006,7 @@ void SecureContext::GetCertificate(const FunctionCallbackInfo<Value>& args) {
return args.GetReturnValue().Set(Null(env->isolate()));
int size = i2d_X509(cert, nullptr);
Local<Object> buff = Buffer::New(env, size);
Local<Object> buff = Buffer::New(env, size).ToLocalChecked();
unsigned char* serialized = reinterpret_cast<unsigned char*>(
Buffer::Data(buff));
i2d_X509(cert, &serialized);
@ -1109,15 +1109,16 @@ int SSLWrap<Base>::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
return 0;
// Serialize session
Local<Object> buff = Buffer::New(env, size);
Local<Object> buff = Buffer::New(env, size).ToLocalChecked();
unsigned char* serialized = reinterpret_cast<unsigned char*>(
Buffer::Data(buff));
memset(serialized, 0, size);
i2d_SSL_SESSION(sess, &serialized);
Local<Object> session = Buffer::New(env,
reinterpret_cast<char*>(sess->session_id),
sess->session_id_length);
Local<Object> session = Buffer::New(
env,
reinterpret_cast<char*>(sess->session_id),
sess->session_id_length).ToLocalChecked();
Local<Value> argv[] = { session, buff };
w->new_session_wait_ = true;
w->MakeCallback(env->onnewsession_string(), ARRAY_SIZE(argv), argv);
@ -1138,7 +1139,7 @@ void SSLWrap<Base>::OnClientHello(void* arg,
Local<Object> buff = Buffer::New(
env,
reinterpret_cast<const char*>(hello.session_id()),
hello.session_size());
hello.session_size()).ToLocalChecked();
hello_obj->Set(env->session_id_string(), buff);
if (hello.servername() == nullptr) {
hello_obj->Set(env->servername_string(), String::Empty(env->isolate()));
@ -1349,7 +1350,7 @@ static Local<Object> X509ToObject(Environment* env, X509* cert) {
// Raw DER certificate
int size = i2d_X509(cert, nullptr);
Local<Object> buff = Buffer::New(env, size);
Local<Object> buff = Buffer::New(env, size).ToLocalChecked();
unsigned char* serialized = reinterpret_cast<unsigned char*>(
Buffer::Data(buff));
i2d_X509(cert, &serialized);
@ -1596,11 +1597,12 @@ void SSLWrap<Base>::GetTLSTicket(const FunctionCallbackInfo<Value>& args) {
if (sess == nullptr || sess->tlsext_tick == nullptr)
return;
Local<Object> buf = Buffer::New(env,
reinterpret_cast<char*>(sess->tlsext_tick),
sess->tlsext_ticklen);
Local<Object> buff = Buffer::New(
env,
reinterpret_cast<char*>(sess->tlsext_tick),
sess->tlsext_ticklen).ToLocalChecked();
args.GetReturnValue().Set(buf);
args.GetReturnValue().Set(buff);
}
@ -1880,7 +1882,7 @@ int SSLWrap<Base>::TLSExtStatusCallback(SSL* s, void* arg) {
arg = Buffer::New(
env,
reinterpret_cast<char*>(const_cast<unsigned char*>(resp)),
len);
len).ToLocalChecked();
}
w->MakeCallback(env->onocspresponse_string(), 1, &arg);
@ -2890,7 +2892,7 @@ void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
unsigned int out_len = 0;
if (cipher->GetAuthTag(&out, &out_len)) {
Local<Object> buf = Buffer::Use(env, out, out_len);
Local<Object> buf = Buffer::Use(env, out, out_len).ToLocalChecked();
args.GetReturnValue().Set(buf);
} else {
env->ThrowError("Attempting to get auth tag in unsupported state");
@ -3007,7 +3009,8 @@ void CipherBase::Update(const FunctionCallbackInfo<Value>& args) {
"Trying to add data in unsupported state");
}
Local<Object> buf = Buffer::New(env, reinterpret_cast<char*>(out), out_len);
Local<Object> buf =
Buffer::New(env, reinterpret_cast<char*>(out), out_len).ToLocalChecked();
if (out)
delete[] out;
@ -3082,8 +3085,11 @@ void CipherBase::Final(const FunctionCallbackInfo<Value>& args) {
}
}
args.GetReturnValue().Set(
Buffer::New(env, reinterpret_cast<char*>(out_value), out_len));
Local<Object> buf = Buffer::New(
env,
reinterpret_cast<char*>(out_value),
out_len).ToLocalChecked();
args.GetReturnValue().Set(buf);
delete[] out_value;
}
@ -3861,8 +3867,11 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
}
}
args.GetReturnValue().Set(
Buffer::New(env, reinterpret_cast<char*>(out_value), out_len));
Local<Object> vbuf = Buffer::New(
env,
reinterpret_cast<char*>(out_value),
out_len).ToLocalChecked();
args.GetReturnValue().Set(vbuf);
delete[] out_value;
}
@ -4357,7 +4366,8 @@ void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
return env->ThrowError("Failed to compute ECDH key");
}
args.GetReturnValue().Set(Buffer::Use(env, out, out_len));
Local<Object> buf = Buffer::Use(env, out, out_len).ToLocalChecked();
args.GetReturnValue().Set(buf);
}
@ -4393,9 +4403,9 @@ void ECDH::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
return env->ThrowError("Failed to get public key");
}
args.GetReturnValue().Set(Buffer::Use(env,
reinterpret_cast<char*>(out),
size));
Local<Object> buf =
Buffer::Use(env, reinterpret_cast<char*>(out), size).ToLocalChecked();
args.GetReturnValue().Set(buf);
}
@ -4420,9 +4430,9 @@ void ECDH::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
return env->ThrowError("Failed to convert ECDH private key to Buffer");
}
args.GetReturnValue().Set(Buffer::Use(env,
reinterpret_cast<char*>(out),
size));
Local<Object> buf =
Buffer::Use(env, reinterpret_cast<char*>(out), size).ToLocalChecked();
args.GetReturnValue().Set(buf);
}
@ -4820,7 +4830,7 @@ void RandomBytesCheck(RandomBytesRequest* req, Local<Value> argv[2]) {
size_t size;
req->return_memory(&data, &size);
argv[0] = Null(req->env()->isolate());
argv[1] = Buffer::Use(req->env()->isolate(), data, size);
argv[1] = Buffer::Use(req->env(), data, size).ToLocalChecked();
}
}

2
src/spawn_sync.cc

@ -167,7 +167,7 @@ void SyncProcessStdioPipe::Close() {
Local<Object> SyncProcessStdioPipe::GetOutputAsBuffer(Environment* env) const {
size_t length = OutputLength();
Local<Object> js_buffer = Buffer::New(env, length);
Local<Object> js_buffer = Buffer::New(env, length).ToLocalChecked();
CopyOutput(Buffer::Data(js_buffer));
return js_buffer;
}

3
src/stream_wrap.cc

@ -223,7 +223,8 @@ void StreamWrap::OnReadImpl(ssize_t nread,
CHECK_EQ(pending, UV_UNKNOWN_HANDLE);
}
wrap->EmitData(nread, Buffer::Use(env, base, nread), pending_obj);
Local<Object> obj = Buffer::Use(env, base, nread).ToLocalChecked();
wrap->EmitData(nread, obj, pending_obj);
}

6
src/string_bytes.cc

@ -19,6 +19,7 @@ using v8::Handle;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
@ -681,7 +682,10 @@ Local<Value> StringBytes::Encode(Isolate* isolate,
Local<String> val;
switch (encoding) {
case BUFFER:
return scope.Escape(Buffer::New(isolate, buf, buflen));
{
Local<Object> vbuf = Buffer::New(isolate, buf, buflen).ToLocalChecked();
return scope.Escape(vbuf);
}
case ASCII:
if (contains_non_ascii(buf, buflen)) {

2
src/tls_wrap.cc

@ -659,7 +659,7 @@ void TLSWrap::OnReadSelf(ssize_t nread,
TLSWrap* wrap = static_cast<TLSWrap*>(ctx);
Local<Object> buf_obj;
if (buf != nullptr)
buf_obj = Buffer::Use(wrap->env(), buf->base, buf->len);
buf_obj = Buffer::Use(wrap->env(), buf->base, buf->len).ToLocalChecked();
wrap->EmitData(nread, buf_obj, Local<Object>());
}

2
src/udp_wrap.cc

@ -407,7 +407,7 @@ void UDPWrap::OnRecv(uv_udp_t* handle,
}
char* base = static_cast<char*>(realloc(buf->base, nread));
argv[2] = Buffer::Use(env, base, nread);
argv[2] = Buffer::Use(env, base, nread).ToLocalChecked();
argv[3] = AddressToJS(env, addr);
wrap->MakeCallback(env->onmessage_string(), ARRAY_SIZE(argv), argv);
}

Loading…
Cancel
Save