Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use source map (if present) to translate source file references in backtrace. #278

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions react_juce/core/EcmascriptEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,140 @@ namespace reactjuce
mPimpl->debuggerDetach();
}

//==============================================================================
SourceMap::SourceMap(const juce::String& source, const juce::String& map)
: mapLoaded(false)
, sourcePath(source)
{
mapLoaded = LoadMap(map);
}

SourceMap::SourceMap(const juce::String& source, const juce::File& map)
: mapLoaded(false)
, sourcePath(source)
{
if (map.existsAsFile())
mapLoaded = LoadMap(map.loadFileAsString());
}

SourceMap::Location SourceMap::translate(int line, int col) const
{
// line and col are 1-based.
SourceMap::Location failRes{sourcePath, line, col};
if (!mapLoaded || line <= 0 || col <= 0 || static_cast<std::size_t>(line) > mappings.size())
return failRes;

const Segment* use_segment = nullptr;
for (const auto& segment : mappings[line - 1])
if (segment.segStartCol >= col)
break;
else
use_segment = &segment;

if (!use_segment || !use_segment->gotSourceMap || use_segment->origSourceId < 0 ||
static_cast<std::size_t>(use_segment->origSourceId) >= sources.size())
return failRes;

return
{
sources[use_segment->origSourceId],
use_segment->origStartLine + 1,
col - use_segment->segStartCol + use_segment->origStartCol
};
}

bool SourceMap::LoadMap(const juce::String& map)
{
auto json = juce::JSON::parse(map);
const auto fileVersion = json.getProperty("version", juce::var());
const auto fileMappings = json.getProperty("mappings", juce::var());
auto fileSources = json.getProperty("sources", juce::var());

if (!fileVersion.isInt() || static_cast<int>(fileVersion) != 3 ||
!fileMappings.isString() || !fileSources.isArray() )
return false;

for (auto &i : *fileSources.getArray())
{
if (!i.isString())
return false;
sources.emplace_back(std::move(i.toString()));
}

bool firstSegmentInLine = true;
std::vector<Segment> lineSegments;
Segment segment;
Segment lastSegment;
int segmentIntNo = 0;
int segmentIntVal = 0;
int segmentIntCharNo = 0;

for (auto p = fileMappings.toString().toRawUTF8(); *p; ++p)
{
if (*p == ',' || *p == ';')
{
segmentIntNo = segmentIntVal = segmentIntCharNo = 0;
if (firstSegmentInLine)
firstSegmentInLine = false;
else
segment.segStartCol += lastSegment.segStartCol;
segment.origSourceId += lastSegment.origSourceId;
segment.origStartLine += lastSegment.origStartLine;
segment.origStartCol += lastSegment.origStartCol;
lineSegments.push_back(segment);
lastSegment = segment;
segment = Segment();

if (*p == ';')
{
mappings.emplace_back(std::move(lineSegments));
firstSegmentInLine = true;
}
}
else
{
static const std::string base64_charset =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
auto i = base64_charset.find(*p);
if (i == std::string::npos)
return false;
segmentIntVal |= ((i & 0x1f) << (segmentIntCharNo * 5));
if (i & 0x20)
++segmentIntCharNo;
else
{
bool neg = (segmentIntVal & 1);
segmentIntVal >>= 1;
if (neg)
segmentIntVal = -segmentIntVal;
switch(segmentIntNo++)
{
case 0:
segment.segStartCol = segmentIntVal;
break;

case 1:
segment.origSourceId = segmentIntVal;
segment.gotSourceMap = true;
break;

case 2:
segment.origStartLine = segmentIntVal;
break;

case 3:
segment.origStartCol = segmentIntVal;
break;
}
segmentIntVal = segmentIntCharNo = 0;
}
}
}
// In case there's no final terminator.
mappings.emplace_back(std::move(lineSegments));

return true;
}
}
39 changes: 38 additions & 1 deletion react_juce/core/EcmascriptEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#pragma once

#include <unordered_map>

#include <vector>

namespace reactjuce
{
Expand Down Expand Up @@ -178,4 +178,41 @@ namespace reactjuce
return invoke(name, vargs);
}

//==============================================================================
/** Utility helper class for Ecmascript implementations. Read a SourceMap and
* provide translations to original source location.
*/
class SourceMap
{
public:
struct Location
{
const juce::String& file;
int line;
int col;
};

SourceMap(const juce::String& source, const juce::String& map);
SourceMap(const juce::String& source, const juce::File& map);
~SourceMap() = default;

Location translate(int line, int col) const;

private:
struct Segment
{
int segStartCol = 0;
int origSourceId = 0;
int origStartLine = 0;
int origStartCol = 0;
bool gotSourceMap = false;
};

bool LoadMap(const juce::String& map);

bool mapLoaded;
juce::String sourcePath;
std::vector<juce::String> sources;
std::vector<std::vector<Segment>> mappings;
};
}
38 changes: 34 additions & 4 deletions react_juce/core/EcmascriptEngine_Hermes.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <regex>

#include "EcmascriptEngine.h"

#if _MSC_VER
Expand Down Expand Up @@ -247,6 +249,34 @@ namespace reactjuce
return {};
}

//==============================================================================
juce::String sourceMapError(const juce::String &err)
{
std::unordered_map<juce::String, std::unique_ptr<SourceMap>> maps;
std::string s = err.toStdString();
juce::String res;
const std::regex fileRef("\\((.*):([0-9]+):([0-9]+)\\)", std::regex_constants::ECMAScript);
std::smatch m;

while (std::regex_search(s, m, fileRef))
{
const juce::String source = m[1].str();
const int line = std::stoi(m[2].str());
const int col = std::stoi(m[3].str());

if (maps.find(source) == maps.end())
maps.insert({source, std::make_unique<SourceMap>(source, juce::File(source + ".map"))});
const auto newloc = maps[source]->translate(line, col);
juce::String file = newloc.file;
if (file.startsWith("webpack:///"))
file = newloc.file.replaceFirstOccurrenceOf("webpack:///", "");

res += juce::String(m.prefix().str()) + "(" + file + ":" + juce::String(newloc.line) + ":" + juce::String(newloc.col) + ")";
s = m.suffix();
}
return res + s;
}

//==============================================================================
}

Expand Down Expand Up @@ -350,7 +380,7 @@ namespace reactjuce
}
catch (const jsi::JSIException &e)
{
throw Error(e.what());
throw Error(sourceMapError(e.what()));
}
}

Expand All @@ -366,7 +396,7 @@ namespace reactjuce
}
catch (const jsi::JSIException &e)
{
throw Error(e.what());
throw Error(sourceMapError(e.what()));
}
}

Expand All @@ -385,7 +415,7 @@ namespace reactjuce
}
catch (const jsi::JSIException &e)
{
throw Error(e.what());
throw Error(sourceMapError(e.what()));
}
}

Expand Down Expand Up @@ -451,7 +481,7 @@ namespace reactjuce
}
catch(const jsi::JSIException &e)
{
throw Error(e.what());
throw Error(sourceMapError(e.what()));
}
}

Expand Down