// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "bin/dartutils.h"

#include "bin/crypto.h"
#include "bin/directory.h"
#include "bin/file.h"
#include "bin/io_buffer.h"
#include "bin/typed_data_utils.h"
#include "bin/utils.h"
#include "include/dart_api.h"
#include "include/dart_native_api.h"
#include "platform/assert.h"
#include "platform/globals.h"
#include "platform/mach_o.h"
#include "platform/utils.h"

namespace dart {
namespace bin {

const char* DartUtils::original_working_directory = nullptr;

dart::SimpleHashMap* DartUtils::environment_ = nullptr;

MagicNumberData appjit_magic_number = {8, {0xdc, 0xdc, 0xf6, 0xf6, 0, 0, 0, 0}};
MagicNumberData aotelf_magic_number = {4, {0x7F, 0x45, 0x4C, 0x46, 0x0}};
MagicNumberData aotpe_magic_number = {2, {0x4d, 0x5a}};
MagicNumberData aotcoff_arm32_magic_number = {2, {0x01, 0xC0}};
MagicNumberData aotcoff_arm64_magic_number = {2, {0xAA, 0x64}};
MagicNumberData aotcoff_riscv32_magic_number = {2, {0x50, 0x32}};
MagicNumberData aotcoff_riscv64_magic_number = {2, {0x50, 0x64}};
MagicNumberData kernel_magic_number = {4, {0x90, 0xab, 0xcd, 0xef}};
MagicNumberData kernel_list_magic_number = {
    7,
    {0x23, 0x40, 0x64, 0x69, 0x6c, 0x6c, 0x0a}};  // #@dill\n
MagicNumberData bytecode_magic_number = {4, {0x33, 0x43, 0x42, 0x44}};
MagicNumberData gzip_magic_number = {2, {0x1f, 0x8b, 0, 0}};

static bool IsWindowsHost() {
#if defined(DART_HOST_OS_WINDOWS)
  return true;
#else   // defined(DART_HOST_OS_WINDOWS)
  return false;
#endif  // defined(DART_HOST_OS_WINDOWS)
}

Dart_Handle CommandLineOptions::CreateRuntimeOptions() {
  Dart_Handle string_type = DartUtils::GetDartType("dart:core", "String");
  if (Dart_IsError(string_type)) {
    return string_type;
  }
  Dart_Handle dart_arguments =
      Dart_NewListOfTypeFilled(string_type, Dart_EmptyString(), count_);
  if (Dart_IsError(dart_arguments)) {
    return dart_arguments;
  }
  for (int i = 0; i < count_; i++) {
    Dart_Handle argument_value = DartUtils::NewString(GetArgument(i));
    if (Dart_IsError(argument_value)) {
      return argument_value;
    }
    Dart_Handle result = Dart_ListSetAt(dart_arguments, i, argument_value);
    if (Dart_IsError(result)) {
      return result;
    }
  }
  return dart_arguments;
}

int64_t DartUtils::GetIntegerValue(Dart_Handle value_obj) {
  int64_t value = 0;
  Dart_Handle result = Dart_IntegerToInt64(value_obj, &value);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  return value;
}

int64_t DartUtils::GetInt64ValueCheckRange(Dart_Handle value_obj,
                                           int64_t lower,
                                           int64_t upper) {
  int64_t value = DartUtils::GetIntegerValue(value_obj);
  if (value < lower || upper < value) {
    Dart_PropagateError(Dart_NewApiError("Value outside expected range"));
  }
  return value;
}

intptr_t DartUtils::GetIntptrValue(Dart_Handle value_obj) {
  int64_t value = 0;
  Dart_Handle result = Dart_IntegerToInt64(value_obj, &value);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  if (value < kIntptrMin || kIntptrMax < value) {
    Dart_PropagateError(Dart_NewApiError("Value outside intptr_t range"));
  }
  return static_cast<intptr_t>(value);
}

bool DartUtils::GetInt64Value(Dart_Handle value_obj, int64_t* value) {
  bool valid = Dart_IsInteger(value_obj);
  if (valid) {
    Dart_Handle result = Dart_IntegerFitsIntoInt64(value_obj, &valid);
    if (Dart_IsError(result)) {
      Dart_PropagateError(result);
    }
  }
  if (!valid) return false;
  Dart_Handle result = Dart_IntegerToInt64(value_obj, value);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  return true;
}

const char* DartUtils::GetStringValue(Dart_Handle str_obj) {
  const char* cstring = nullptr;
  Dart_Handle result = Dart_StringToCString(str_obj, &cstring);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  return cstring;
}

bool DartUtils::GetBooleanValue(Dart_Handle bool_obj) {
  bool value = false;
  Dart_Handle result = Dart_BooleanValue(bool_obj, &value);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  return value;
}

bool DartUtils::GetNativeBooleanArgument(Dart_NativeArguments args,
                                         intptr_t index) {
  bool value = false;
  Dart_Handle result = Dart_GetNativeBooleanArgument(args, index, &value);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  return value;
}

int64_t DartUtils::GetNativeIntegerArgument(Dart_NativeArguments args,
                                            intptr_t index) {
  int64_t value = 0;
  Dart_Handle result = Dart_GetNativeIntegerArgument(args, index, &value);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  return value;
}

intptr_t DartUtils::GetNativeIntptrArgument(Dart_NativeArguments args,
                                            intptr_t index) {
  int64_t value = GetNativeIntegerArgument(args, index);
  if (value < kIntptrMin || kIntptrMax < value) {
    Dart_PropagateError(Dart_NewApiError("Value outside intptr_t range"));
  }
  return static_cast<intptr_t>(value);
}

const char* DartUtils::GetNativeStringArgument(Dart_NativeArguments args,
                                               intptr_t index) {
  char* tmp = nullptr;
  Dart_Handle result =
      Dart_GetNativeStringArgument(args, index, reinterpret_cast<void**>(&tmp));
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  if (tmp != nullptr) {
    return tmp;
  }
  const char* cstring = nullptr;
  result = Dart_StringToCString(result, &cstring);
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  ASSERT(cstring != nullptr);
  return cstring;
}

const char* DartUtils::GetNativeTypedDataArgument(Dart_NativeArguments args,
                                                  intptr_t index) {
  Dart_Handle handle = Dart_GetNativeArgument(args, index);
  TypedDataScope data(handle);
  ASSERT(data.type() == Dart_TypedData_kUint8);
  return data.GetScopedCString();
}

Dart_Handle DartUtils::SetIntegerField(Dart_Handle handle,
                                       const char* name,
                                       int64_t val) {
  return Dart_SetField(handle, NewString(name), Dart_NewInteger(val));
}

Dart_Handle DartUtils::SetStringField(Dart_Handle handle,
                                      const char* name,
                                      const char* val) {
  return Dart_SetField(handle, NewString(name), NewString(val));
}

bool DartUtils::IsDartSchemeURL(const char* url_name) {
  static const intptr_t kDartSchemeLen = strlen(kDartScheme);
  // If the URL starts with "dart:" then it is considered as a special
  // library URL which is handled differently from other URLs.
  return (strncmp(url_name, kDartScheme, kDartSchemeLen) == 0);
}

char* DartUtils::DirName(const char* url) {
  const char* slash = strrchr(url, File::PathSeparator()[0]);
  if (slash == nullptr) {
    return Utils::StrDup(url);
  } else {
    return Utils::StrNDup(url, slash - url + 1);
  }
}

void* DartUtils::OpenFile(const char* name, bool write) {
  File* file =
      File::Open(nullptr, name, write ? File::kWriteTruncate : File::kRead);
  return reinterpret_cast<void*>(file);
}

void* DartUtils::OpenFileUri(const char* uri, bool write) {
  File* file =
      File::OpenUri(nullptr, uri, write ? File::kWriteTruncate : File::kRead);
  return reinterpret_cast<void*>(file);
}

void DartUtils::ReadFile(uint8_t** data, intptr_t* len, void* stream) {
  ASSERT(data != nullptr);
  ASSERT(len != nullptr);
  ASSERT(stream != nullptr);
  File* file_stream = reinterpret_cast<File*>(stream);
  int64_t file_len = file_stream->Length();
  if ((file_len < 0) || (file_len > kIntptrMax)) {
    *data = nullptr;
    *len = -1;  // Indicates read was not successful.
    return;
  }
  *len = static_cast<intptr_t>(file_len);
  *data = reinterpret_cast<uint8_t*>(malloc(*len));
  if (!file_stream->ReadFully(*data, *len)) {
    free(*data);
    *data = nullptr;
    *len = -1;  // Indicates read was not successful.
    return;
  }
}

void DartUtils::WriteFile(const void* buffer,
                          intptr_t num_bytes,
                          void* stream) {
  ASSERT(stream != nullptr);
  File* file_stream = reinterpret_cast<File*>(stream);
  bool bytes_written = file_stream->WriteFully(buffer, num_bytes);
  ASSERT(bytes_written);
}

void DartUtils::CloseFile(void* stream) {
  File* file = reinterpret_cast<File*>(stream);
  file->Release();
}

bool DartUtils::EntropySource(uint8_t* buffer, intptr_t length) {
  return Crypto::GetRandomBytes(length, buffer);
}

static Dart_Handle SingleArgDart_Invoke(Dart_Handle lib,
                                        const char* method,
                                        Dart_Handle arg) {
  const int kNumArgs = 1;
  Dart_Handle dart_args[kNumArgs];
  dart_args[0] = arg;
  return Dart_Invoke(lib, DartUtils::NewString(method), kNumArgs, dart_args);
}

// TODO(iposva): Allocate from the zone instead of leaking error string
// here. On the other hand the binary is about to exit anyway.
#define SET_ERROR_MSG(error_msg, format, ...)                                  \
  intptr_t len = snprintf(nullptr, 0, format, ##__VA_ARGS__);                  \
  char* msg = reinterpret_cast<char*>(malloc(len + 1));                        \
  snprintf(msg, len + 1, format, ##__VA_ARGS__);                               \
  *error_msg = msg

static uint8_t* ReadFileFully(const char* filename,
                              intptr_t* file_len,
                              const char** error_msg) {
  *file_len = -1;
  void* stream = DartUtils::OpenFile(filename, false);
  if (stream == nullptr) {
    SET_ERROR_MSG(error_msg, "Unable to open file: %s", filename);
    return nullptr;
  }
  uint8_t* text_buffer = nullptr;
  DartUtils::ReadFile(&text_buffer, file_len, stream);
  if (text_buffer == nullptr || *file_len == -1) {
    *error_msg = "Unable to read file contents";
    text_buffer = nullptr;
  }
  DartUtils::CloseFile(stream);
  return text_buffer;
}

Dart_Handle DartUtils::ReadStringFromFile(const char* filename) {
  const char* error_msg = nullptr;
  intptr_t len;
  uint8_t* text_buffer = ReadFileFully(filename, &len, &error_msg);
  if (text_buffer == nullptr) {
    return Dart_NewApiError(error_msg);
  }
  Dart_Handle str = Dart_NewStringFromUTF8(text_buffer, len);
  free(text_buffer);
  return str;
}

Dart_Handle DartUtils::MakeUint8Array(const void* buffer, intptr_t len) {
  Dart_Handle array = Dart_NewTypedData(Dart_TypedData_kUint8, len);
  RETURN_IF_ERROR(array);
  {
    Dart_TypedData_Type td_type;
    void* td_data;
    intptr_t td_len;
    Dart_Handle result =
        Dart_TypedDataAcquireData(array, &td_type, &td_data, &td_len);
    RETURN_IF_ERROR(result);
    ASSERT(td_type == Dart_TypedData_kUint8);
    ASSERT(td_len == len);
    ASSERT(td_data != nullptr);
    memmove(td_data, buffer, td_len);
    result = Dart_TypedDataReleaseData(array);
    RETURN_IF_ERROR(result);
  }
  return array;
}

Dart_Handle DartUtils::SetWorkingDirectory() {
  Dart_Handle directory = NewString(original_working_directory);
  return SingleArgDart_Invoke(LookupBuiltinLib(), "_setWorkingDirectory",
                              directory);
}

Dart_Handle DartUtils::ResolveScript(Dart_Handle url) {
  const int kNumArgs = 1;
  Dart_Handle dart_args[kNumArgs];
  dart_args[0] = url;
  return Dart_Invoke(DartUtils::LookupBuiltinLib(),
                     NewString("_resolveScriptUri"), kNumArgs, dart_args);
}

static bool CheckMagicNumber(const uint8_t* buffer,
                             intptr_t buffer_length,
                             const MagicNumberData& magic_number) {
  if ((buffer_length >= magic_number.length)) {
    return memcmp(buffer, magic_number.bytes, magic_number.length) == 0;
  }
  return false;
}

DartUtils::MagicNumber DartUtils::SniffForMagicNumber(const char* filename) {
  MagicNumber magic_number = DartUtils::kUnknownMagicNumber;
  ASSERT(kMaxMagicNumberSize == appjit_magic_number.length);
  ASSERT(aotelf_magic_number.length <= appjit_magic_number.length);
  ASSERT(static_cast<intptr_t>(sizeof(mach_o::mach_header::magic)) <=
         appjit_magic_number.length);
  ASSERT(aotpe_magic_number.length <= appjit_magic_number.length);
  ASSERT(aotcoff_arm32_magic_number.length <= appjit_magic_number.length);
  ASSERT(aotcoff_arm64_magic_number.length <= appjit_magic_number.length);
  ASSERT(aotcoff_riscv32_magic_number.length <= appjit_magic_number.length);
  ASSERT(aotcoff_riscv64_magic_number.length <= appjit_magic_number.length);
  ASSERT(kernel_magic_number.length <= appjit_magic_number.length);
  ASSERT(kernel_list_magic_number.length <= appjit_magic_number.length);
  ASSERT(bytecode_magic_number.length <= appjit_magic_number.length);
  ASSERT(gzip_magic_number.length <= appjit_magic_number.length);
  if (File::GetType(nullptr, filename, true) == File::kIsFile) {
    File* file = File::Open(nullptr, filename, File::kRead);
    if (file != nullptr) {
      RefCntReleaseScope<File> rs(file);
      uint8_t header[kMaxMagicNumberSize];
      if (file->ReadFully(&header, kMaxMagicNumberSize)) {
        magic_number = DartUtils::SniffForMagicNumber(header, sizeof(header));
      }
    }
  }
  return magic_number;
}

DartUtils::MagicNumber DartUtils::SniffForMagicNumber(const uint8_t* buffer,
                                                      intptr_t buffer_length) {
  if (CheckMagicNumber(buffer, buffer_length, appjit_magic_number)) {
    return kAppJITMagicNumber;
  }

  if (CheckMagicNumber(buffer, buffer_length, kernel_magic_number)) {
    return kKernelMagicNumber;
  }

  if (CheckMagicNumber(buffer, buffer_length, kernel_list_magic_number)) {
    return kKernelListMagicNumber;
  }

  if (CheckMagicNumber(buffer, buffer_length, bytecode_magic_number)) {
    return kBytecodeMagicNumber;
  }

  if (CheckMagicNumber(buffer, buffer_length, gzip_magic_number)) {
    return kGzipMagicNumber;
  }

  if (CheckMagicNumber(buffer, buffer_length, aotelf_magic_number)) {
    return kAotELFMagicNumber;
  }

  // Mach-O magic numbers are reported by whether the endianness of the file
  // matches the endianness of the system. Here, we only bother looking for
  // host-endian magic numbers, as our Mach-O parsing code won't handle the
  // reverse endian case.
  if (static_cast<intptr_t>(sizeof(mach_o::mach_header::magic)) <=
      buffer_length) {
    const uint32_t magic =
        reinterpret_cast<const mach_o::mach_header*>(buffer)->magic;
    if (magic == mach_o::MH_MAGIC) {
      return kAotMachO32MagicNumber;
    } else if (magic == mach_o::MH_MAGIC_64) {
      return kAotMachO64MagicNumber;
    }
  }

  if (CheckMagicNumber(buffer, buffer_length, aotpe_magic_number)) {
    return kAotPEMagicNumber;
  }

  if (CheckMagicNumber(buffer, buffer_length, aotcoff_arm32_magic_number)) {
    return kAotCoffARM32MagicNumber;
  }

  if (CheckMagicNumber(buffer, buffer_length, aotcoff_arm64_magic_number)) {
    return kAotCoffARM64MagicNumber;
  }

  if (CheckMagicNumber(buffer, buffer_length, aotcoff_riscv32_magic_number)) {
    return kAotCoffRISCV32MagicNumber;
  }

  if (CheckMagicNumber(buffer, buffer_length, aotcoff_riscv64_magic_number)) {
    return kAotCoffRISCV64MagicNumber;
  }

  return kUnknownMagicNumber;
}

Dart_Handle DartUtils::PrepareBuiltinLibrary(Dart_Handle builtin_lib,
                                             Dart_Handle internal_lib,
                                             bool is_service_isolate,
                                             bool trace_loading) {
  // Setup the internal library's 'internalPrint' function.
  Dart_Handle print =
      Dart_Invoke(builtin_lib, NewString("_getPrintClosure"), 0, nullptr);
  RETURN_IF_ERROR(print);
  Dart_Handle result =
      Dart_SetField(internal_lib, NewString("_printClosure"), print);
  RETURN_IF_ERROR(result);

  if (!is_service_isolate) {
    if (IsWindowsHost()) {
      result = Dart_SetField(builtin_lib, NewString("_isWindows"), Dart_True());
      RETURN_IF_ERROR(result);
    }
    if (trace_loading) {
      result =
          Dart_SetField(builtin_lib, NewString("_traceLoading"), Dart_True());
      RETURN_IF_ERROR(result);
    }
    // Set current working directory.
    result = SetWorkingDirectory();
    RETURN_IF_ERROR(result);
  }
  return Dart_True();
}

Dart_Handle DartUtils::PrepareCoreLibrary(Dart_Handle core_lib,
                                          Dart_Handle io_lib) {
  // Setup the 'Uri.base' getter in dart:core.
  Dart_Handle uri_base =
      Dart_Invoke(io_lib, NewString("_getUriBaseClosure"), 0, nullptr);
  RETURN_IF_ERROR(uri_base);
  Dart_Handle result =
      Dart_SetField(core_lib, NewString("_uriBaseClosure"), uri_base);
  RETURN_IF_ERROR(result);
  return Dart_True();
}

Dart_Handle DartUtils::PrepareAsyncLibrary(Dart_Handle async_lib,
                                           Dart_Handle isolate_lib,
                                           bool flag_profile_microtasks) {
#if !defined(PRODUCT)
  if (flag_profile_microtasks) {
    Dart_Handle microtask_mirror_queue_type_name =
        Dart_NewStringFromCString("_MicrotaskMirrorQueue");
    RETURN_IF_ERROR(microtask_mirror_queue_type_name);

    Dart_Handle microtask_mirror_queue_type =
        Dart_GetNonNullableType(async_lib, microtask_mirror_queue_type_name,
                                /*number_of_type_arguments=*/0,
                                /*type_arguments=*/nullptr);
    RETURN_IF_ERROR(microtask_mirror_queue_type);

    Dart_Handle should_profile_microtasks_field_name =
        Dart_NewStringFromCString("_shouldProfileMicrotasks");
    RETURN_IF_ERROR(should_profile_microtasks_field_name);

    Dart_Handle set_field_result =
        Dart_SetField(microtask_mirror_queue_type,
                      should_profile_microtasks_field_name, Dart_True());
    RETURN_IF_ERROR(set_field_result);
  }
#endif  // !defined(PRODUCT)

  Dart_Handle schedule_immediate_closure =
      Dart_Invoke(isolate_lib, NewString("_getIsolateScheduleImmediateClosure"),
                  0, nullptr);
  RETURN_IF_ERROR(schedule_immediate_closure);
  Dart_Handle args[1];
  args[0] = schedule_immediate_closure;
  return Dart_Invoke(async_lib, NewString("_setScheduleImmediateClosure"), 1,
                     args);
}

Dart_Handle DartUtils::PrepareIsolateLibrary(Dart_Handle isolate_lib) {
  return Dart_Invoke(isolate_lib, NewString("_setupHooks"), 0, nullptr);
}

Dart_Handle DartUtils::PrepareCLILibrary(Dart_Handle cli_lib) {
  return Dart_Null();
}

Dart_Handle DartUtils::SetupPackageConfig(const char* packages_config) {
  Dart_Handle result = Dart_Null();

  if (packages_config != nullptr) {
    result = NewString(packages_config);
    RETURN_IF_ERROR(result);
    const int kNumArgs = 1;
    Dart_Handle dart_args[kNumArgs];
    dart_args[0] = result;
    result = Dart_Invoke(DartUtils::LookupBuiltinLib(),
                         NewString("_setPackagesMap"), kNumArgs, dart_args);
  }
  return result;
}

Dart_Handle DartUtils::SetupCoreLibraries(
    bool is_service_isolate,
    bool trace_loading,
    bool flag_profile_microtasks,
    const DartIoSettings& dart_io_settings) {
  // First ensure all required libraries are available.
  ASSIGN_OR_RETURN(Dart_Handle core_lib,
                   Dart_LookupLibrary(NewString(kCoreLibURL)));
  ASSIGN_OR_RETURN(Dart_Handle async_lib,
                   Dart_LookupLibrary(NewString(kAsyncLibURL)));
  ASSIGN_OR_RETURN(Dart_Handle isolate_lib,
                   Dart_LookupLibrary(NewString(kIsolateLibURL)));
  ASSIGN_OR_RETURN(Dart_Handle internal_lib,
                   Dart_LookupLibrary(NewString(kInternalLibURL)));
  ASSIGN_OR_RETURN(Dart_Handle builtin_lib,
                   Dart_LookupLibrary(NewString(kBuiltinLibURL)));
  ASSIGN_OR_RETURN(Dart_Handle io_lib,
                   Dart_LookupLibrary(NewString(kIOLibURL)));
  ASSIGN_OR_RETURN(Dart_Handle cli_lib,
                   Dart_LookupLibrary(NewString(kCLILibURL)));

  // Configure native resolvers for dart:_builtin and dart:cli.
  for (auto id : {Builtin::kBuiltinLibrary, Builtin::kCLILibrary}) {
    RETURN_IF_ERROR(Builtin::TrySetNativeResolver(id));
  }

  // Initialize dart:io library.
  RETURN_IF_ERROR(SetupDartIoLibrary(dart_io_settings));

  // We need to ensure that all the scripts loaded so far are finalized
  // as we are about to invoke some Dart code below to setup closures.
  Dart_Handle result = Dart_FinalizeLoading(false);
  RETURN_IF_ERROR(result);

  result = PrepareBuiltinLibrary(builtin_lib, internal_lib, is_service_isolate,
                                 trace_loading);
  RETURN_IF_ERROR(result);

  RETURN_IF_ERROR(
      PrepareAsyncLibrary(async_lib, isolate_lib, flag_profile_microtasks));
  RETURN_IF_ERROR(PrepareCoreLibrary(core_lib, io_lib));
  RETURN_IF_ERROR(PrepareIsolateLibrary(isolate_lib));
  RETURN_IF_ERROR(PrepareCLILibrary(cli_lib));
  return result;
}

bool DartUtils::PostNull(Dart_Port port_id) {
  // Post a message with just the null object.
  return Dart_PostCObject(port_id, CObject::Null()->AsApiCObject());
}

bool DartUtils::PostInt32(Dart_Port port_id, int32_t value) {
  // Post a message with the integer value.
  int32_t min = 0xc0000000;  // -1073741824
  int32_t max = 0x3fffffff;  // 1073741823
  ASSERT(min <= value && value < max);
  Dart_CObject object;
  object.type = Dart_CObject_kInt32;
  object.value.as_int32 = value;
  return Dart_PostCObject(port_id, &object);
}

bool DartUtils::PostInt64(Dart_Port port_id, int64_t value) {
  // Post a message with the integer value.
  Dart_CObject object;
  object.type = Dart_CObject_kInt64;
  object.value.as_int64 = value;
  return Dart_PostCObject(port_id, &object);
}

bool DartUtils::PostString(Dart_Port port_id, const char* value) {
  Dart_CObject* object = CObject::NewString(value);
  return Dart_PostCObject(port_id, object);
}

Dart_Handle DartUtils::GetDartType(const char* library_url,
                                   const char* class_name) {
  return Dart_GetNonNullableType(Dart_LookupLibrary(NewString(library_url)),
                                 NewString(class_name), 0, nullptr);
}

Dart_Handle DartUtils::NewDartOSError() {
  // Extract the current OS error.
  OSError os_error;
  return NewDartOSError(&os_error);
}

Dart_Handle DartUtils::NewDartOSError(OSError* os_error) {
  // Create a dart:io OSError object with the information retrieved from the OS.
  Dart_Handle type = GetDartType(kIOLibURL, "OSError");
  ASSERT(!Dart_IsError(type));
  Dart_Handle args[2];
  args[0] = NewString(os_error->message());
  args[1] = Dart_NewInteger(os_error->code());
  return Dart_New(type, Dart_Null(), 2, args);
}

Dart_Handle DartUtils::NewDartExceptionWithOSError(const char* library_url,
                                                   const char* exception_name,
                                                   const char* message,
                                                   Dart_Handle os_error) {
  // Create a Dart Exception object with a message and an OSError.
  Dart_Handle type = GetDartType(library_url, exception_name);
  ASSERT(!Dart_IsError(type));
  Dart_Handle args[2];
  args[0] = NewString(message);
  args[1] = os_error;
  return Dart_New(type, Dart_Null(), 2, args);
}

Dart_Handle DartUtils::NewDartExceptionWithMessage(const char* library_url,
                                                   const char* exception_name,
                                                   const char* message) {
  // Create a Dart Exception object with a message.
  Dart_Handle type = GetDartType(library_url, exception_name);
  ASSERT(!Dart_IsError(type));
  if (message != nullptr) {
    Dart_Handle args[1];
    args[0] = NewString(message);
    return Dart_New(type, Dart_Null(), 1, args);
  } else {
    return Dart_New(type, Dart_Null(), 0, nullptr);
  }
}

Dart_Handle DartUtils::NewDartArgumentError(const char* message) {
  return NewDartExceptionWithMessage(kCoreLibURL, "ArgumentError", message);
}

Dart_Handle DartUtils::NewDartFormatException(const char* message) {
  return NewDartExceptionWithMessage(kCoreLibURL, "FormatException", message);
}

Dart_Handle DartUtils::NewDartUnsupportedError(const char* message) {
  return NewDartExceptionWithMessage(kCoreLibURL, "UnsupportedError", message);
}

Dart_Handle DartUtils::NewDartIOException(const char* exception_name,
                                          const char* message,
                                          Dart_Handle os_error) {
  // Create a dart:io exception object of the given type.
  return NewDartExceptionWithOSError(kIOLibURL, exception_name, message,
                                     os_error);
}

Dart_Handle DartUtils::NewError(const char* format, ...) {
  va_list measure_args;
  va_start(measure_args, format);
  intptr_t len = Utils::VSNPrint(nullptr, 0, format, measure_args);
  va_end(measure_args);

  char* buffer = reinterpret_cast<char*>(Dart_ScopeAllocate(len + 1));
  va_list print_args;
  va_start(print_args, format);
  Utils::VSNPrint(buffer, (len + 1), format, print_args);
  va_end(print_args);

  return Dart_NewApiError(buffer);
}

Dart_Handle DartUtils::NewInternalError(const char* message) {
  return NewDartExceptionWithMessage(kCoreLibURL, "_InternalError", message);
}

Dart_Handle DartUtils::NewStringFormatted(const char* format, ...) {
  va_list args;
  va_start(args, format);
  char* result = ScopedCStringVFormatted(format, args);
  va_end(args);
  return NewString(result);
}

char* DartUtils::ScopedCStringVFormatted(const char* format, va_list args) {
  va_list measure_args;
  va_copy(measure_args, args);
  intptr_t len = Utils::VSNPrint(nullptr, 0, format, measure_args);
  if (len < 0) {
    return nullptr;
  }
  va_end(measure_args);

  char* buffer = ScopedCString(len + 1);
  va_list print_args;
  va_copy(print_args, args);
  len = Utils::VSNPrint(buffer, (len + 1), format, print_args);
  if (len < 0) {
    return nullptr;
  }
  va_end(print_args);
  return buffer;
}

char* DartUtils::ScopedCStringFormatted(const char* format, ...) {
  va_list args;
  va_start(args, format);
  char* result = ScopedCStringVFormatted(format, args);
  va_end(args);
  return result;
}

bool DartUtils::SetOriginalWorkingDirectory() {
  // If we happen to re-initialize the Dart VM multiple times, make sure to free
  // the old string (allocated by getcwd()) before setting a new one.
  if (original_working_directory != nullptr) {
    free(const_cast<char*>(original_working_directory));
  }
  original_working_directory = Directory::CurrentNoScope();
  return original_working_directory != nullptr;
}

void DartUtils::SetEnvironment(dart::SimpleHashMap* environment) {
  environment_ = environment;
}

Dart_Handle DartUtils::EnvironmentCallback(Dart_Handle name) {
  uint8_t* utf8_array;
  intptr_t utf8_len;
  Dart_Handle result = Dart_Null();
  Dart_Handle handle = Dart_StringToUTF8(name, &utf8_array, &utf8_len);
  if (Dart_IsError(handle)) {
    handle = Dart_ThrowException(
        DartUtils::NewDartArgumentError(Dart_GetError(handle)));
  } else {
    char* name_chars = reinterpret_cast<char*>(malloc(utf8_len + 1));
    memmove(name_chars, utf8_array, utf8_len);
    name_chars[utf8_len] = '\0';
    const char* value = nullptr;
    if (environment_ != nullptr) {
      SimpleHashMap::Entry* entry =
          environment_->Lookup(GetHashmapKeyFromString(name_chars),
                               SimpleHashMap::StringHash(name_chars), false);
      if (entry != nullptr) {
        value = reinterpret_cast<char*>(entry->value);
      }
    }
    if (value != nullptr) {
      result = Dart_NewStringFromUTF8(reinterpret_cast<const uint8_t*>(value),
                                      strlen(value));
      if (Dart_IsError(result)) {
        result = Dart_Null();
      }
    }
    free(name_chars);
  }
  return result;
}

// Statically allocated Dart_CObject instances for immutable
// objects. As these will be used by different threads the use of
// these depends on the fact that the marking internally in the
// Dart_CObject structure is not marking simple value objects.
Dart_CObject CObject::api_null_ = {Dart_CObject_kNull, {false}};
Dart_CObject CObject::api_true_ = {Dart_CObject_kBool, {true}};
Dart_CObject CObject::api_false_ = {Dart_CObject_kBool, {false}};
CObject CObject::null_(&api_null_);
CObject CObject::true_(&api_true_);
CObject CObject::false_(&api_false_);

CObject* CObject::Null() {
  return &null_;
}

CObject* CObject::True() {
  return &true_;
}

CObject* CObject::False() {
  return &false_;
}

CObject* CObject::Bool(bool value) {
  return value ? &true_ : &false_;
}

Dart_CObject* CObject::New(Dart_CObject_Type type, int additional_bytes) {
  Dart_CObject* cobject = reinterpret_cast<Dart_CObject*>(
      Dart_ScopeAllocate(sizeof(Dart_CObject) + additional_bytes));
  cobject->type = type;
  return cobject;
}

Dart_CObject* CObject::NewInt32(int32_t value) {
  Dart_CObject* cobject = New(Dart_CObject_kInt32);
  cobject->value.as_int32 = value;
  return cobject;
}

Dart_CObject* CObject::NewInt64(int64_t value) {
  Dart_CObject* cobject = New(Dart_CObject_kInt64);
  cobject->value.as_int64 = value;
  return cobject;
}

Dart_CObject* CObject::NewIntptr(intptr_t value) {
  // Pointer values passed as intptr_t are always send as int64_t.
  Dart_CObject* cobject = New(Dart_CObject_kInt64);
  cobject->value.as_int64 = value;
  return cobject;
}

Dart_CObject* CObject::NewDouble(double value) {
  Dart_CObject* cobject = New(Dart_CObject_kDouble);
  cobject->value.as_double = value;
  return cobject;
}

Dart_CObject* CObject::NewString(const char* str) {
  intptr_t length = strlen(str);
  Dart_CObject* cobject = New(Dart_CObject_kString, length + 1);
  char* payload = reinterpret_cast<char*>(cobject + 1);
  memmove(payload, str, length + 1);
  cobject->value.as_string = payload;
  return cobject;
}

Dart_CObject* CObject::NewArray(intptr_t length) {
  Dart_CObject* cobject =
      New(Dart_CObject_kArray, length * sizeof(Dart_CObject*));  // NOLINT
  cobject->value.as_array.length = length;
  cobject->value.as_array.values =
      reinterpret_cast<Dart_CObject**>(cobject + 1);
  return cobject;
}

Dart_CObject* CObject::NewUint8Array(const void* data, intptr_t length) {
  Dart_CObject* cobject = New(Dart_CObject_kTypedData, length);
  memmove(reinterpret_cast<uint8_t*>(cobject + 1), data, length);
  cobject->value.as_typed_data.type = Dart_TypedData_kUint8;
  cobject->value.as_typed_data.length = length;
  cobject->value.as_typed_data.values =
      reinterpret_cast<const uint8_t*>(cobject + 1);
  return cobject;
}

Dart_CObject* CObject::NewExternalUint8Array(intptr_t length,
                                             uint8_t* data,
                                             void* peer,
                                             Dart_HandleFinalizer callback) {
  Dart_CObject* cobject = New(Dart_CObject_kExternalTypedData);
  cobject->value.as_external_typed_data.type = Dart_TypedData_kUint8;
  cobject->value.as_external_typed_data.length = length;
  cobject->value.as_external_typed_data.data = data;
  cobject->value.as_external_typed_data.peer = peer;
  cobject->value.as_external_typed_data.callback = callback;
  return cobject;
}

Dart_CObject* CObject::NewNativePointer(intptr_t ptr,
                                        intptr_t size,
                                        Dart_HandleFinalizer callback) {
  Dart_CObject* cobject = New(Dart_CObject_kNativePointer);
  cobject->value.as_native_pointer.ptr = ptr;
  cobject->value.as_native_pointer.size = size;
  cobject->value.as_native_pointer.callback = callback;
  return cobject;
}

Dart_CObject* CObject::NewIOBuffer(int64_t length) {
  // Make sure that we do not have an integer overflow here. Actual check
  // against max elements will be done at the time of writing, as the constant
  // is not part of the public API.
  if ((length < 0) || (length > kIntptrMax)) {
    return nullptr;
  }
  uint8_t* data = IOBuffer::Allocate(static_cast<intptr_t>(length));
  if (data == nullptr) {
    return nullptr;
  }
  return NewExternalUint8Array(static_cast<intptr_t>(length), data, data,
                               IOBuffer::Finalizer);
}

void CObject::ShrinkIOBuffer(Dart_CObject* cobject, int64_t new_length) {
  if (cobject == nullptr) return;
  ASSERT(cobject->type == Dart_CObject_kExternalTypedData);

  const auto old_data = cobject->value.as_external_typed_data.data;
  const auto old_length = cobject->value.as_external_typed_data.length;

  // We only shrink IOBuffers, never grow them.
  ASSERT(0 <= new_length && new_length <= old_length);

  // We only reallocate if we think the freed space is worth reallocating.
  // We consider it worthwhile when freed space is >=25% and we have at
  // least 100 free bytes.
  const auto free_memory = old_length - new_length;
  if ((old_length >> 2) <= free_memory && 100 <= free_memory) {
    const auto new_data = IOBuffer::Reallocate(old_data, new_length);
    if (new_data != nullptr) {
      cobject->value.as_external_typed_data.data = new_data;
      cobject->value.as_external_typed_data.peer = new_data;
    }
  }

  // The typed data object always has to have the shranken length.
  cobject->value.as_external_typed_data.length = new_length;
}

void CObject::FreeIOBufferData(Dart_CObject* cobject) {
  ASSERT(cobject->type == Dart_CObject_kExternalTypedData);
  cobject->value.as_external_typed_data.callback(
      nullptr, cobject->value.as_external_typed_data.peer);
  cobject->value.as_external_typed_data.data = nullptr;
}

CObject* CObject::IllegalArgumentError() {
  CObjectArray* result = new CObjectArray(CObject::NewArray(1));
  result->SetAt(0, new CObjectInt32(CObject::NewInt32(kArgumentError)));
  return result;
}

CObject* CObject::FileClosedError() {
  CObjectArray* result = new CObjectArray(CObject::NewArray(1));
  result->SetAt(0, new CObjectInt32(CObject::NewInt32(kFileClosedError)));
  return result;
}

CObject* CObject::NewOSError() {
  OSError os_error;
  return NewOSError(&os_error);
}

CObject* CObject::NewOSError(OSError* os_error) {
  CObject* error_message =
      new CObjectString(CObject::NewString(os_error->message()));
  CObjectArray* result = new CObjectArray(CObject::NewArray(3));
  result->SetAt(0, new CObjectInt32(CObject::NewInt32(kOSError)));
  result->SetAt(1, new CObjectInt32(CObject::NewInt32(os_error->code())));
  result->SetAt(2, error_message);
  return result;
}

}  // namespace bin
}  // namespace dart
