Skip to content
40 changes: 26 additions & 14 deletions src/jsc/bindings/CodeCoverage.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
#include "root.h"
#include "CodeCoverage.h"
#include "ZigSourceProvider.h"
#include <JavaScriptCore/ControlFlowProfiler.h>

using namespace JSC;

namespace Bun {

void appendFunctionRangesForCoverage(Vector<BasicBlockRange>& basicBlocks, VM& vm, SourceID sourceID)
{
const Vector<std::tuple<bool, unsigned, unsigned>>& functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(sourceID);

basicBlocks.reserveCapacity(functionRanges.size() + basicBlocks.size());

for (const auto& functionRange : functionRanges) {
BasicBlockRange range;
range.m_hasExecuted = std::get<0>(functionRange);
range.m_startOffset = static_cast<int>(std::get<1>(functionRange));
range.m_endOffset = static_cast<int>(std::get<2>(functionRange));
range.m_executionCount = range.m_hasExecuted
? 1
: 0; // This is a hack. We don't actually count this.
if (isSynthesizedDefaultConstructorRange(range.m_hasExecuted, range.m_startOffset, range.m_endOffset))
continue;
basicBlocks.append(range);
}
}

}

extern "C" bool CodeCoverage__withBlocksAndFunctions(
JSC::VM* vmPtr,
JSC::SourceID sourceID,
Expand All @@ -24,20 +49,7 @@ extern "C" bool CodeCoverage__withBlocksAndFunctions(

size_t functionStartOffset = basicBlocks.size();

const Vector<std::tuple<bool, unsigned, unsigned>>& functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(sourceID);

basicBlocks.reserveCapacity(functionRanges.size() + basicBlocks.size());

for (const auto& functionRange : functionRanges) {
BasicBlockRange range;
range.m_hasExecuted = std::get<0>(functionRange);
range.m_startOffset = static_cast<int>(std::get<1>(functionRange));
range.m_endOffset = static_cast<int>(std::get<2>(functionRange));
range.m_executionCount = range.m_hasExecuted
? 1
: 0; // This is a hack. We don't actually count this.
basicBlocks.append(range);
}
Bun::appendFunctionRangesForCoverage(basicBlocks, vm, sourceID);

blockCallback(ctx, basicBlocks.begin(), basicBlocks.size(), functionStartOffset, ignoreSourceMap);
return true;
Expand Down
26 changes: 26 additions & 0 deletions src/jsc/bindings/CodeCoverage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include "root.h"
#include <JavaScriptCore/ControlFlowProfiler.h>

namespace Bun {

// JSC records the constructor it synthesizes for a class without one in the defining file's
// function table (CodeBlock::finishCreation), with offsets into its own template string
// (BuiltinExecutables::defaultConstructorSourceCode), and never marks it executed because its
// code block is linked against the template (UnlinkedFunctionExecutable::linkedSourceCode).
Comment thread
robobun marked this conversation as resolved.
Resolved
constexpr bool isSynthesizedDefaultConstructorRange(bool hasExecuted, int start, int end)
{
// The range starts at the `function` keyword and ends at the closing brace of the body.
constexpr int functionKeyword = static_cast<int>(sizeof("(") - 1);
constexpr int baseClosingBrace = static_cast<int>(sizeof("(function () { })") - sizeof("})"));
constexpr int derivedClosingBrace = static_cast<int>(sizeof("(function (...args) { super(...args); })") - sizeof("})"));
static_assert(functionKeyword == 1 && baseClosingBrace == 15 && derivedClosingBrace == 38);
return !hasExecuted && start == functionKeyword && (end == baseClosingBrace || end == derivedClosingBrace);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}

// Appends every function range of `sourceID` to `basicBlocks` as a BasicBlockRange, except the
// synthesized default constructors above.
Comment thread
robobun marked this conversation as resolved.
Resolved
void appendFunctionRangesForCoverage(Vector<JSC::BasicBlockRange>& basicBlocks, JSC::VM& vm, JSC::SourceID sourceID);

}
12 changes: 9 additions & 3 deletions src/jsc/bindings/JSInspectorProfiler.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "root.h"
#include "helpers.h"
#include "BunCPUProfiler.h"
#include "CodeCoverage.h"
#include "NodeValidator.h"
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/VM.h>
Expand Down Expand Up @@ -144,10 +145,15 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_collectPreciseCoverage, (JSGlobalObject * gl

auto functionArray = JSON::Array::create();
for (const auto& functionRange : functionRanges) {
bool hasExecuted = std::get<0>(functionRange);
int start = static_cast<int>(std::get<1>(functionRange));
int end = static_cast<int>(std::get<2>(functionRange));
if (Bun::isSynthesizedDefaultConstructorRange(hasExecuted, start, end))
continue;
auto range = JSON::Array::create();
range->pushDouble(static_cast<double>(std::get<1>(functionRange)));
range->pushDouble(static_cast<double>(std::get<2>(functionRange)));
range->pushBoolean(std::get<0>(functionRange));
range->pushDouble(static_cast<double>(start));
range->pushDouble(static_cast<double>(end));
range->pushBoolean(hasExecuted);
functionArray->pushValue(WTF::move(range));
}
script->setValue("functions"_s, WTF::move(functionArray));
Expand Down
16 changes: 2 additions & 14 deletions src/jsc/modules/BunJSCModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ extern "C" char* mi_stats_get_json(size_t, char*);
extern "C" char* mi_heap_dump_json(bool include_blocks, bool hash_addresses);

#include <JavaScriptCore/ControlFlowProfiler.h>
#include "CodeCoverage.h"

#if OS(DARWIN)
#if ASSERT_ENABLED
Expand Down Expand Up @@ -933,20 +934,7 @@ JSC_DEFINE_HOST_FUNCTION(functionCodeCoverageForFile,

size_t functionStartOffset = basicBlocks.size();

const Vector<std::tuple<bool, unsigned, unsigned>>& functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(sourceID);

basicBlocks.reserveCapacity(functionRanges.size() + basicBlocks.size());

for (const auto& functionRange : functionRanges) {
BasicBlockRange range;
range.m_hasExecuted = std::get<0>(functionRange);
range.m_startOffset = static_cast<int>(std::get<1>(functionRange));
range.m_endOffset = static_cast<int>(std::get<2>(functionRange));
range.m_executionCount = range.m_hasExecuted
? 1
: 0; // This is a hack. We don't actually count this.
basicBlocks.append(range);
}
Bun::appendFunctionRangesForCoverage(basicBlocks, vm, sourceID);

BunString fileNameBunString = Bun::toString(fileName);
return ByteRangeMapping__findExecutedLines(
Expand Down
31 changes: 0 additions & 31 deletions src/sourcemap/LineOffsetTable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,37 +118,6 @@ impl LineOffsetTable {
Self::find_line(offsets, loc)
}

pub fn find_index(byte_offsets_to_start_of_line: &[u32], loc: Loc) -> Option<usize> {
debug_assert!(loc.start > -1); // checked by caller
let mut original_line: usize = 0;
let loc_start = usize::try_from(loc.start).expect("int cast");

let mut count = byte_offsets_to_start_of_line.len();
while count > 0 {
let step = count / 2;
let i = original_line + step;
let byte_offset = byte_offsets_to_start_of_line[i] as usize;
if byte_offset == loc_start {
return Some(i);
}
if i + 1 < byte_offsets_to_start_of_line.len() {
let next_byte_offset = byte_offsets_to_start_of_line[i + 1] as usize;
if byte_offset < loc_start && loc_start < next_byte_offset {
return Some(i);
}
}

if byte_offset < loc_start {
original_line = i + 1;
count = count - step - 1;
} else {
count = step;
}
}

None
}

/// `Global`-allocator convenience wrapper around [`generate_in`].
pub fn generate(contents: &[u8], approximate_line_count: i32) -> Result<List, AllocError> {
Self::generate_in::<Global>(contents, approximate_line_count)
Expand Down
2 changes: 1 addition & 1 deletion src/sourcemap/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ pub struct ParseFail {
/// byte-compatible with this struct and bulk-copy them into the
/// `MultiArrayList` column.
#[repr(C)]
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct LineColumnOffset {
/// The zero-based line offset
pub lines: Ordinal,
Expand Down
Loading
Loading