blob: d0fa28e388e819f705eff32e286ae7ae04a6da8d [file] [edit]
// 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/shared-function-info-inl.h"
#include "src/tracing/traced-value.h"
#include "src/utils/hex-format.h"
#include "src/utils/sha-256.h"
namespace v8::internal {
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::CompilationType type) {
switch (type) {
case Script::CompilationType::kHost:
return "host";
case Script::CompilationType::kEval:
return "eval";
}
UNREACHABLE();
}
const char* ToString(Script::CompilationState type) {
switch (type) {
case Script::CompilationState::kInitial:
return "initial";
case Script::CompilationState::kCompiled:
return "compiled";
}
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());
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->compilation_type() == Script::CompilationType::kEval);
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