#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 {
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);
CHECK_LT(function_literal_id, script->infos()->length());
Tagged<MaybeObject> shared = script->infos()->get(function_literal_id);
Tagged<HeapObject> heap_object;
if (!shared.GetHeapObject(&heap_object) ||
IsUndefined(heap_object, isolate)) {
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 Script();
}
int Script::GetEvalPosition(Isolate* isolate, DirectHandle<Script> script) {
DCHECK(script->compilation_type() == Script::CompilationType::kEval);
int position = script->eval_from_position();
if (position < 0) {
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;
}
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>
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, isolate));
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
#ifdef DEBUG
if (script->type() == Type::kWasm) {
DCHECK(script->has_line_ends());
DCHECK_EQ(Cast<FixedArray>(script->line_ends())->length(), 0);
}
#endif
#endif
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
return true;
case Type::kNative:
case Type::kInspector:
case Type::kExtension:
return false;
}
UNREACHABLE();
}
bool Script::IsUserJavaScript() const {
return type() == Script::Type::kNormal;
}
#if V8_ENABLE_WEBASSEMBLY
bool Script::ContainsAsmModule() {
DisallowGarbageCollection no_gc;
SharedFunctionInfo::ScriptIterator iter(Isolate::Current(), *this);
for (Tagged<SharedFunctionInfo> sfi = iter.Next(); !sfi.is_null();
sfi = iter.Next()) {
if (sfi->HasAsmWasmData()) return true;
}
return false;
}
#endif
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());
constexpr int kMaxSourceMapUrlLength = 2048;
if (sourceMapUrl->length() > kMaxSourceMapUrlLength) {
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_EVENT1(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;
const int32_t kSplitMaxLength = 1000000;
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_EVENT1(
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_EVENT1(
TRACE_DISABLED_BY_DEFAULT("devtools.v8-source-rundown-sources"),
"ScriptCatchup", "data", std::move(value));
} else {
int32_t split_count = source_length / kSplitMaxLength + 1;
std::unique_ptr<char[]> source_ptr = source->ToCString();
for (int32_t i = 0; i < split_count; i++) {
int32_t begin = i * kSplitMaxLength;
int32_t end = std::min(begin + kSplitMaxLength, source_length);
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", std::string(source_ptr.get() + begin, end - begin));
TRACE_EVENT1(
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 {
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));
}
int GetLength(const String::LineEndsVector& vector) {
return static_cast<int>(vector.size());
}
int GetLength(const Tagged<FixedArray>& array) { return array->length(); }
template <typename LineEndsContainer>
bool GetLineEndsContainerPositionInfo(const LineEndsContainer& ends,
int position, Script::PositionInfo* info,
const DisallowGarbageCollection& no_gc) {
const int ends_len = GetLength(ends);
if (ends_len == 0) return false;
if (position < 0) {
position = 0;
} else if (position > GetLineEnd(ends, ends_len - 1)) {
return false;
}
if (GetLineEnd(ends, 0) >= position) {
info->line = 0;
info->line_start = 0;
info->column = position;
} else {
int left = 0;
int right = 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;
}
}
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;
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
if (type() == Script::Type::kWasm) {
DCHECK_LE(0, position);
wasm::NativeModule* 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
if (!has_line_ends()) {
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() {
if (!IsUndefined(source_url())) return source_url();
return name();
}
DirectHandle<String> Script::GetScriptHash(Isolate* isolate,
DirectHandle<Script> script,
bool forceForInspector) {
if (script->origin_options().IsOpaque() && !forceForInspector) {
return isolate->factory()->empty_string();
}
PtrComprCageBase cage_base(isolate);
{
Tagged<Object> maybe_source_hash = script->source_hash(cage_base);
if (IsString(maybe_source_hash, cage_base)) {
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(cage_base);
if (!IsString(maybe_script_source, cage_base)) {
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;
}
}