@ 261106.4
current
// Copyright 2025 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/objects/script.h"

#include "src/ast/ast.h"
#include "src/common/globals.h"
#include "src/objects/fixed-array-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/script-inl.h"
#include "src/objects/shared-function-info-inl.h"
#include "src/objects/string-inl.h"
#include "src/tracing/traced-value.h"
#include "src/utils/hex-format.h"
#include "src/utils/sha-256.h"

namespace v8::internal {

#if V8_ENABLE_WEBASSEMBLY
void Script::set_wasm_managed_native_module(Tagged<Object> value,
                                            WriteBarrierMode mode) {
  DCHECK_EQ(type(), Type::kWasm);
  eval_from_position_.store(this, Cast<UnionOf<Smi, CppGCManagedBase>>(value),
                            mode);
}

CppGCManaged<wasm::NativeModule>::Ptr Script::wasm_native_module() const {
  return Cast<CppGCManaged<wasm::NativeModule>>(wasm_managed_native_module())
      ->ptr();
}

bool Script::has_wasm_breakpoint_infos() const {
  return type() == Type::kWasm &&
         wasm_breakpoint_infos()->ulength().value() > 0;
}
#endif  // V8_ENABLE_WEBASSEMBLY

void Script::set_eval_from_scope_info(Tagged<Object> value,
                                      WriteBarrierMode mode) {
  eval_from_scope_info_.store(this, Cast<UnionOf<ScopeInfo, Undefined>>(value),
                              mode);
}

bool Script::HasValidSource() {
  Tagged<Object> src = this->source();
  if (!IsString(src)) return true;
  Tagged<String> src_str = Cast<String>(src);
  if (!StringShape(src_str).IsExternal()) return true;
  if (src_str->IsOneByteRepresentation()) {
    return Cast<ExternalOneByteString>(src)->resource() != nullptr;
  } else if (src_str->IsTwoByteRepresentation()) {
    return Cast<ExternalTwoByteString>(src)->resource() != nullptr;
  }
  return true;
}

bool Script::HasSourceURLComment() const {
  return IsString(source_url()) && Cast<String>(source_url())->length() != 0;
}

bool Script::HasSourceMappingURLComment() const {
  return IsString(source_mapping_url()) &&
         Cast<String>(source_mapping_url())->length() != 0;
}

bool Script::IsMaybeUnfinalized(Isolate* isolate) const {
  // TODO(v8:12051): A more robust detection, e.g. with a dedicated sentinel
  // value.
  return IsUndefined(source()) || Cast<String>(source())->length() == 0;
}

const char* ToString(Script::Type type) {
  switch (type) {
    case Script::Type::kNative:
      return "native";
    case Script::Type::kExtension:
      return "extension";
    case Script::Type::kNormal:
      return "normal";
#if V8_ENABLE_WEBASSEMBLY
    case Script::Type::kWasm:
      return "wasm";
#endif  // V8_ENABLE_WEBASSEMBLY
    case Script::Type::kInspector:
      return "inspector";
  }
  UNREACHABLE();
}

const char* ToString(Script::CompilationState type) {
  switch (type) {
    case Script::CompilationState::kInitial:
      return "initial";
    case Script::CompilationState::kCompiled:
      return "compiled";
  }
  UNREACHABLE();
}

const char* ToString(Script::CompilationKind type) {
  switch (type) {
    case Script::CompilationKind::kHost:
      return "host";
    case Script::CompilationKind::kDirectEval:
      return "direct-eval";
    case Script::CompilationKind::kIndirectEval:
      return "indirect-eval";
    case Script::CompilationKind::kFunctionConstructor:
      return "function-constructor";
    case Script::CompilationKind::kWrapped:
      return "wrapped";
  }
  UNREACHABLE();
}

template <typename IsolateT>
MaybeHandle<SharedFunctionInfo> Script::FindSharedFunctionInfo(
    DirectHandle<Script> script, IsolateT* isolate,
    FunctionLiteral* function_literal) {
  DCHECK(function_literal->shared_function_info().is_null());
  int function_literal_id = function_literal->function_literal_id();
  CHECK_GE(function_literal_id, 0);
  // If this check fails, the problem is most probably the function id
  // renumbering done by AstFunctionLiteralIdReindexer; in particular, that
  // AstTraversalVisitor doesn't recurse properly in the construct which
  // triggers the mismatch.
  CHECK_LT(static_cast<uint32_t>(function_literal_id),
           script->infos()->ulength().value());
  Tagged<MaybeObject> shared =
      script->infos()->get(function_literal_id, kAcquireLoad);
  Tagged<HeapObject> heap_object;
  if (!shared.GetHeapObject(&heap_object) || IsUndefined(heap_object)) {
    return MaybeHandle<SharedFunctionInfo>();
  }
  Handle<SharedFunctionInfo> result(Cast<SharedFunctionInfo>(heap_object),
                                    isolate);
  CHECK(Is<SharedFunctionInfo>(*result));
  CHECK_EQ(result->StartPosition(), function_literal->start_position());
  CHECK_EQ(result->EndPosition(), function_literal->end_position());
  CHECK_EQ(result->function_literal_id(kRelaxedLoad),
           function_literal->function_literal_id());
  CHECK_EQ(result->syntax_kind(), function_literal->syntax_kind());
  CHECK_EQ(result->kind(), function_literal->kind());
  function_literal->set_shared_function_info(result);
  return result;
}

template MaybeHandle<SharedFunctionInfo> Script::FindSharedFunctionInfo(
    DirectHandle<Script> script, Isolate* isolate,
    FunctionLiteral* function_literal);
template MaybeHandle<SharedFunctionInfo> Script::FindSharedFunctionInfo(
    DirectHandle<Script> script, LocalIsolate* isolate,
    FunctionLiteral* function_literal);

Script::Iterator::Iterator(Isolate* isolate)
    : iterator_(isolate->heap()->script_list()) {}

Tagged<Script> Script::Iterator::Next() {
  Tagged<Object> o = iterator_.Next();
  if (o != Tagged<Object>()) {
    return Cast<Script>(o);
  }
  return Tagged<Script>();
}

// static
int Script::GetEvalPosition(Isolate* isolate, DirectHandle<Script> script) {
  DCHECK(script->has_eval_origin());
  int position = script->eval_from_position();
  if (position < 0) {
    // Due to laziness, the position may not have been translated from code
    // offset yet, which would be encoded as negative integer. In that case,
    // translate and set the position.
    if (!script->has_eval_from_shared()) {
      position = 0;
    } else {
      DirectHandle<SharedFunctionInfo> shared(script->eval_from_shared(),
                                              isolate);
      SharedFunctionInfo::EnsureSourcePositionsAvailable(isolate, shared);
      position =
          shared->abstract_code(isolate)->SourcePosition(isolate, -position);
    }
    DCHECK_GE(position, 0);
    script->set_eval_from_position(position);
  }
  return position;
}

void Script::set_eval_from_shared(Tagged<SharedFunctionInfo> shared,
                                  WriteBarrierMode mode) {
  DCHECK(!is_wrapped());
  set_eval_from_shared_or_wrapped_arguments(shared, mode);
}

Tagged<SharedFunctionInfo> Script::eval_from_shared() const {
  DCHECK(has_eval_from_shared());
  return Cast<SharedFunctionInfo>(eval_from_shared_or_wrapped_arguments());
}

Tagged<Script> Script::GetEvalOrigin() {
  DisallowGarbageCollection no_gc;
  Tagged<Script> origin_script = this;
  while (origin_script->has_eval_from_shared()) {
    Tagged<HeapObject> maybe_script =
        origin_script->eval_from_shared()->script();
    CHECK(IsScript(maybe_script));
    origin_script = Cast<Script>(maybe_script);
  }
  return origin_script;
}

String::LineEndsVector Script::GetLineEnds(Isolate* isolate,
                                           DirectHandle<Script> script) {
  DCHECK(!script->has_line_ends());
  Tagged<Object> src_obj = script->source();
  if (IsString(src_obj)) {
    DirectHandle<String> src(Cast<String>(src_obj), isolate);
    return String::CalculateLineEndsVector(isolate, src, true);
  }

  return String::LineEndsVector();
}

template <typename IsolateT>
// static
void Script::InitLineEndsInternal(IsolateT* isolate,
                                  DirectHandle<Script> script) {
  DCHECK(!script->has_line_ends());
  DCHECK(script->CanHaveLineEnds());
  Tagged<Object> src_obj = script->source();
  if (!IsString(src_obj)) {
    DCHECK(IsUndefined(src_obj));
    script->set_line_ends(ReadOnlyRoots(isolate).empty_fixed_array());
  } else {
    DCHECK(IsString(src_obj));
    DirectHandle<String> src(Cast<String>(src_obj), isolate);
    DirectHandle<FixedArray> array =
        String::CalculateLineEnds(isolate, src, true);
    script->set_line_ends(*array);
  }
  DCHECK(IsFixedArray(script->line_ends()));
  DCHECK(script->has_line_ends());
}

void Script::SetSource(Isolate* isolate, DirectHandle<Script> script,
                       DirectHandle<String> source) {
  script->set_source(*source);
  if (isolate->NeedsSourcePositions()) {
    InitLineEnds(isolate, script);
  } else if (script->line_ends() ==
             ReadOnlyRoots(isolate).empty_fixed_array()) {
    DCHECK(script->has_line_ends());
    script->set_line_ends(Smi::zero());
    DCHECK(!script->has_line_ends());
  }
}

template V8_EXPORT_PRIVATE void Script::InitLineEndsInternal(
    Isolate* isolate, DirectHandle<Script> script);
template V8_EXPORT_PRIVATE void Script::InitLineEndsInternal(
    LocalIsolate* isolate, DirectHandle<Script> script);

bool Script::GetPositionInfo(DirectHandle<Script> script, int position,
                             PositionInfo* info, OffsetFlag offset_flag) {
#if V8_ENABLE_WEBASSEMBLY
  // For wasm, we do not create an artificial line_ends array, but do the
  // translation directly.
#ifdef DEBUG
  if (script->type() == Type::kWasm) {
    DCHECK(script->has_line_ends());
    DCHECK_EQ(Cast<FixedArray>(script->line_ends())->ulength().value(), 0);
  }
#endif  // DEBUG
#endif  // V8_ENABLE_WEBASSEMBLY
  InitLineEnds(Isolate::Current(), script);
  return script->GetPositionInfo(position, info, offset_flag);
}

bool Script::IsSubjectToDebugging() const {
  switch (type()) {
    case Type::kNormal:
#if V8_ENABLE_WEBASSEMBLY
    case Type::kWasm:
#endif  // V8_ENABLE_WEBASSEMBLY
      return true;
    case Type::kNative:
    case Type::kInspector:
    case Type::kExtension:
      return false;
  }
  UNREACHABLE();
}

bool Script::IsUserJavaScript() const {
  return type() == Script::Type::kNormal;
}

void Script::TraceScriptRundown() {
  DisallowGarbageCollection no_gc;
  Isolate* isolate = Isolate::Current();
  Tagged<Object> context_value = isolate->native_context()->debug_context_id();
  int contextId = (IsSmi(context_value)) ? Smi::ToInt(context_value) : 0;
  auto value = v8::tracing::TracedValue::Create();
  value->SetInteger("scriptId", this->id());
  value->SetInteger("executionContextId", contextId);
  value->SetString("isolate", std::to_string(isolate->debug()->IsolateId()));
  value->SetBoolean("isModule", this->origin_options().IsModule());
  value->SetBoolean("hasSourceUrl", this->HasSourceURLComment());
  if (this->HasValidSource()) {
    if (this->HasSourceURLComment()) {
      value->SetString("sourceUrl",
                       Cast<String>(this->source_url())->ToCString().get());
    }
    if (this->HasSourceMappingURLComment()) {
      Tagged<String> sourceMapUrl = Cast<String>(this->source_mapping_url());
      // Source maps can be huge. Don't ever put a huge data url source map in
      // the trace. Also omit unusually large regular URLs.
      constexpr int kMaxSourceMapUrlLength = 2048;
      if (sourceMapUrl->length() > kMaxSourceMapUrlLength) {
        // Signal that there is a source map - clients may wish to request it by
        // other means (such as via CDP) or otherwise warn that a source map was
        // present but elided.
        value->SetBoolean("sourceMapUrlElided", true);
      } else {
        value->SetString("sourceMapUrl", sourceMapUrl->ToCString().get());
      }
    }
  }
  if (IsString(this->name())) {
    value->SetString("url", Cast<String>(this->name())->ToCString().get());
  }
  TRACE_EVENT(TRACE_DISABLED_BY_DEFAULT("devtools.v8-source-rundown"),
              "ScriptCatchup", "data", std::move(value));
}

void Script::TraceScriptRundownSources() {
  DisallowGarbageCollection no_gc;
  Isolate* isolate = Isolate::Current();
  if (!IsString(this->source())) return;
  Tagged<String> source = Cast<String>(this->source());
  auto script_id = this->id();
  int32_t source_length = source->length();

  const int32_t kSourceMaxLength = 25000000;  // 25mb
  const int32_t kSplitMaxLength = 1000000;    // 1mb
  if (source_length > kSourceMaxLength) {
    auto value = v8::tracing::TracedValue::Create();
    value->SetString("isolate", std::to_string(isolate->debug()->IsolateId()));
    value->SetInteger("scriptId", script_id);
    value->SetInteger("length", source_length);
    value->SetInteger("limit", kSourceMaxLength);
    TRACE_EVENT(TRACE_DISABLED_BY_DEFAULT("devtools.v8-source-rundown-sources"),
                "TooLargeScriptCatchup", "data", std::move(value));
  } else if (source_length <= kSplitMaxLength) {
    auto value = v8::tracing::TracedValue::Create();
    value->SetString("isolate", std::to_string(isolate->debug()->IsolateId()));
    value->SetInteger("scriptId", script_id);
    value->SetInteger("length", source_length);
    value->SetString("sourceText", source->ToCString().get());
    TRACE_EVENT(TRACE_DISABLED_BY_DEFAULT("devtools.v8-source-rundown-sources"),
                "ScriptCatchup", "data", std::move(value));
  } else {
    int32_t split_count = source_length / kSplitMaxLength + 1;
    for (int32_t i = 0; i < split_count; i++) {
      int32_t begin = i * kSplitMaxLength;
      int32_t length = std::min(kSplitMaxLength, source_length - begin);
      auto split_trace_value = v8::tracing::TracedValue::Create();
      split_trace_value->SetInteger("splitIndex", i);
      split_trace_value->SetInteger("splitCount", split_count);
      split_trace_value->SetString(
          "isolate", std::to_string(isolate->debug()->IsolateId()));
      split_trace_value->SetInteger("scriptId", script_id);
      split_trace_value->SetString("sourceText",
                                   source->ToCString(begin, length).get());
      TRACE_EVENT(
          TRACE_DISABLED_BY_DEFAULT("devtools.v8-source-rundown-sources"),
          "LargeScriptCatchup", "data", std::move(split_trace_value));
    }
  }
}

void Script::AddPositionInfoOffset(PositionInfo* info,
                                   OffsetFlag offset_flag) const {
  // Add offsets if requested.
  if (offset_flag == OffsetFlag::kWithOffset) {
    if (info->line == 0) {
      info->column += column_offset();
    }
    info->line += line_offset();
  } else {
    DCHECK_EQ(offset_flag, OffsetFlag::kNoOffset);
  }
}

namespace {

template <typename Char>
bool GetPositionInfoSlowImpl(base::Vector<Char> source, int position,
                             Script::PositionInfo* info) {
  DCHECK(DisallowPositionInfoSlow::IsAllowed());
  if (position < 0) {
    position = 0;
  }
  int line = 0;
  const auto begin = std::cbegin(source);
  const auto end = std::cend(source);
  for (auto line_begin = begin; line_begin < end;) {
    const auto line_end = std::find(line_begin, end, '\n');
    if (position <= (line_end - begin)) {
      info->line = line;
      info->column = static_cast<int>((begin + position) - line_begin);
      info->line_start = static_cast<int>(line_begin - begin);
      info->line_end = static_cast<int>(line_end - begin);
      return true;
    }
    ++line;
    line_begin = line_end + 1;
  }
  return false;
}
bool GetPositionInfoSlow(const Tagged<Script> script, int position,
                         const DisallowGarbageCollection& no_gc,
                         Script::PositionInfo* info) {
  if (!IsString(script->source())) {
    return false;
  }
  auto source = Cast<String>(script->source());
  const auto flat = source->GetFlatContent(no_gc);
  return flat.IsOneByte()
             ? GetPositionInfoSlowImpl(flat.ToOneByteVector(), position, info)
             : GetPositionInfoSlowImpl(flat.ToUC16Vector(), position, info);
}

int GetLineEnd(const String::LineEndsVector& vector, int line) {
  return vector[line];
}

int GetLineEnd(const Tagged<FixedArray>& array, int line) {
  return Smi::ToInt(array->get(line));
}

uint32_t GetLength(const String::LineEndsVector& vector) {
  return static_cast<uint32_t>(vector.size());
}

uint32_t GetLength(const Tagged<FixedArray>& array) {
  return array->ulength().value();
}

template <typename LineEndsContainer>
bool GetLineEndsContainerPositionInfo(const LineEndsContainer& ends,
                                      int position, Script::PositionInfo* info,
                                      const DisallowGarbageCollection& no_gc) {
  const uint32_t ends_len = GetLength(ends);
  if (ends_len == 0) return false;

  // Return early on invalid positions. Negative positions behave as if 0 was
  // passed, and positions beyond the end of the script return as failure.
  if (position < 0) {
    position = 0;
  } else if (position > GetLineEnd(ends, static_cast<int>(ends_len - 1))) {
    return false;
  }

  // Determine line number by doing a binary search on the line ends array.
  if (GetLineEnd(ends, 0) >= position) {
    info->line = 0;
    info->line_start = 0;
    info->column = position;
  } else {
    int left = 0;
    int right = static_cast<int>(ends_len - 1);

    while (right > 0) {
      DCHECK_LE(left, right);
      const int mid = left + (right - left) / 2;
      if (position > GetLineEnd(ends, mid)) {
        left = mid + 1;
      } else if (position <= GetLineEnd(ends, mid - 1)) {
        right = mid - 1;
      } else {
        info->line = mid;
        break;
      }
    }
    DCHECK(GetLineEnd(ends, info->line) >= position &&
           GetLineEnd(ends, info->line - 1) < position);
    info->line_start = GetLineEnd(ends, info->line - 1) + 1;
    info->column = position - info->line_start;
  }

  return true;
}

}  // namespace

template <typename LineEndsContainer>
bool Script::GetPositionInfoInternal(
    const LineEndsContainer& ends, int position, Script::PositionInfo* info,
    const DisallowGarbageCollection& no_gc) const {
  if (!GetLineEndsContainerPositionInfo(ends, position, info, no_gc)) {
    return false;
  }

  // Line end is position of the linebreak character.
  info->line_end = GetLineEnd(ends, info->line);
  if (info->line_end > 0) {
    DCHECK(IsString(source()));
    Tagged<String> src = Cast<String>(source());
    if (src->length() >= static_cast<uint32_t>(info->line_end) &&
        src->Get(info->line_end - 1) == '\r') {
      info->line_end--;
    }
  }

  return true;
}

template bool Script::GetPositionInfoInternal<String::LineEndsVector>(
    const String::LineEndsVector& ends, int position,
    Script::PositionInfo* info, const DisallowGarbageCollection& no_gc) const;
template bool Script::GetPositionInfoInternal<Tagged<FixedArray>>(
    const Tagged<FixedArray>& ends, int position, Script::PositionInfo* info,
    const DisallowGarbageCollection& no_gc) const;

bool Script::GetPositionInfo(int position, PositionInfo* info,
                             OffsetFlag offset_flag) const {
  DisallowGarbageCollection no_gc;

#if V8_ENABLE_WEBASSEMBLY
  // For wasm, we use the byte offset as the column.
  if (type() == Script::Type::kWasm) {
    DCHECK_LE(0, position);
    CppGCManaged<wasm::NativeModule>::Ptr native_module =
        wasm_native_module();
    const wasm::WasmModule* module = native_module->module();
    if (module->functions.empty()) return false;
    info->line = 0;
    info->column = position;
    info->line_start = module->functions[0].code.offset();
    info->line_end = module->functions.back().code.end_offset();
    return true;
  }
#endif  // V8_ENABLE_WEBASSEMBLY

  if (!has_line_ends()) {
    // Slow mode: we do not have line_ends. We have to iterate through source.
    if (!GetPositionInfoSlow(this, position, no_gc, info)) {
      return false;
    }
  } else {
    DCHECK(has_line_ends());
    Tagged<FixedArray> ends = Cast<FixedArray>(line_ends());

    if (!GetPositionInfoInternal(ends, position, info, no_gc)) return false;
  }

  AddPositionInfoOffset(info, offset_flag);

  return true;
}

bool Script::GetPositionInfoWithLineEnds(
    int position, PositionInfo* info, const String::LineEndsVector& line_ends,
    OffsetFlag offset_flag) const {
  DisallowGarbageCollection no_gc;
  if (!GetPositionInfoInternal(line_ends, position, info, no_gc)) return false;

  AddPositionInfoOffset(info, offset_flag);

  return true;
}

bool Script::GetLineColumnWithLineEnds(
    int position, int& line, int& column,
    const String::LineEndsVector& line_ends) {
  DisallowGarbageCollection no_gc;
  PositionInfo info;
  if (!GetLineEndsContainerPositionInfo(line_ends, position, &info, no_gc)) {
    line = -1;
    column = -1;
    return false;
  }

  line = info.line;
  column = info.column;

  return true;
}

int Script::GetColumnNumber(DirectHandle<Script> script, int code_pos) {
  PositionInfo info;
  GetPositionInfo(script, code_pos, &info);
  return info.column;
}

int Script::GetColumnNumber(int code_pos) const {
  PositionInfo info;
  GetPositionInfo(code_pos, &info);
  return info.column;
}

int Script::GetLineNumber(DirectHandle<Script> script, int code_pos) {
  PositionInfo info;
  GetPositionInfo(script, code_pos, &info);
  return info.line;
}

int Script::GetLineNumber(int code_pos) const {
  PositionInfo info;
  GetPositionInfo(code_pos, &info);
  return info.line;
}

Tagged<Object> Script::GetNameOrSourceURL() {
  // Keep in sync with ScriptNameOrSourceURL in messages.js.
  if (!IsUndefined(source_url())) return source_url();
  return name();
}

// static
DirectHandle<String> Script::GetScriptHash(Isolate* isolate,
                                           DirectHandle<Script> script,
                                           bool forceForInspector) {
  if (script->origin_options().IsOpaque() && !forceForInspector) {
    return isolate->factory()->empty_string();
  }

  {
    Tagged<Object> maybe_source_hash = script->source_hash();
    if (IsString(maybe_source_hash)) {
      DirectHandle<String> precomputed(Cast<String>(maybe_source_hash),
                                       isolate);
      if (precomputed->length() > 0) {
        return precomputed;
      }
    }
  }

  DirectHandle<String> src_text;
  {
    Tagged<Object> maybe_script_source = script->source();

    if (!IsString(maybe_script_source)) {
      return isolate->factory()->empty_string();
    }
    src_text = direct_handle(Cast<String>(maybe_script_source), isolate);
  }

  char formatted_hash[kSizeOfFormattedSha256Digest];

  std::unique_ptr<char[]> string_val = src_text->ToCString();
  size_t len = strlen(string_val.get());
  uint8_t hash[kSizeOfSha256Digest];
  SHA256_hash(string_val.get(), len, hash);
  FormatBytesToHex(formatted_hash, kSizeOfFormattedSha256Digest, hash,
                   kSizeOfSha256Digest);
  formatted_hash[kSizeOfSha256Digest * 2] = '\0';

  DirectHandle<String> result =
      isolate->factory()->NewStringFromAsciiChecked(formatted_hash);
  script->set_source_hash(*result);
  return result;
}

}  // namespace v8::internal