Skip to content

Commit

Permalink
[yul] Add support for parsing debug data attributes.
Browse files Browse the repository at this point in the history
  • Loading branch information
aarlt committed Sep 4, 2024
1 parent 0dd51e5 commit 94f9716
Show file tree
Hide file tree
Showing 15 changed files with 599 additions and 28 deletions.
16 changes: 12 additions & 4 deletions liblangutil/DebugData.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#pragma once

#include <liblangutil/SourceLocation.h>
#include <libsolutil/JSON.h>
#include <optional>
#include <memory>

Expand All @@ -28,27 +29,32 @@ namespace solidity::langutil
struct DebugData
{
typedef typename std::shared_ptr<DebugData const> ConstPtr;
typedef std::optional<std::vector<std::shared_ptr<Json>>> Attributes;

explicit DebugData(
langutil::SourceLocation _nativeLocation = {},
langutil::SourceLocation _originLocation = {},
std::optional<int64_t> _astID = {}
std::optional<int64_t> _astID = {},
Attributes _attributes = {}
):
nativeLocation(std::move(_nativeLocation)),
originLocation(std::move(_originLocation)),
astID(_astID)
astID(_astID),
attributes(std::move(_attributes))
{}

static DebugData::ConstPtr create(
langutil::SourceLocation _nativeLocation,
langutil::SourceLocation _originLocation = {},
std::optional<int64_t> _astID = {}
std::optional<int64_t> _astID = {},
Attributes _attributes = {}
)
{
return std::make_shared<DebugData>(
std::move(_nativeLocation),
std::move(_originLocation),
_astID
_astID,
std::move(_attributes)
);
}

Expand All @@ -65,6 +71,8 @@ struct DebugData
langutil::SourceLocation originLocation;
/// ID in the (Solidity) source AST.
std::optional<int64_t> astID;
/// Additional debug data attributes.
Attributes attributes;
};

} // namespace solidity::langutil
1 change: 1 addition & 0 deletions libsolutil/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ set(sources
Algorithms.h
AnsiColorized.h
Assertions.h
Cache.h
Common.h
CommonData.cpp
CommonData.h
Expand Down
126 changes: 126 additions & 0 deletions libsolutil/Cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/** @file Cache.h
* @date 2024
*
* Simple cache.
*/

#pragma once

#include <liblangutil/Exceptions.h>

#include <libsolutil/FixedHash.h>
#include <libsolutil/JSON.h>
#include <libsolutil/Keccak256.h>

#include <boost/functional/hash.hpp>

#include <map>

namespace solidity::util
{

namespace detail
{

template<template<typename, typename> typename TCache ,typename THash, typename TValue>
class CacheBase
{
public:
typedef THash Hash;
typedef TValue Value;
typedef TCache<Hash, Value> Cache;
typedef std::shared_ptr<Value> Entry;
typedef std::shared_ptr<Cache> Ptr;

Entry set(Hash const& hash, Value const& value)
{
Entry result;
if (m_cache.find(hash) == m_cache.end())
{
result = std::make_shared<Value>(value);
auto [_, inserted] = m_cache.emplace(std::make_pair(hash, result));
solAssert(inserted);
}
else
result = m_cache[hash];
return result;
}

Entry set(Value const& value) { return set(Cache::hash(value), value); }

std::map<Hash, Entry> const& cache() { return m_cache; }

typename std::map<Hash, Entry>::iterator get(Hash const& hash) { return m_cache.find(hash); }

typename std::map<Hash, Entry>::iterator begin() { return m_cache.begin(); }

typename std::map<Hash, Entry>::iterator end() { return m_cache.end(); }

private:
std::map<Hash, Entry> m_cache;
};

} // namespace detail

template<typename THash, typename TValue>
class Cache;

template<typename TValue>
class Cache<size_t, TValue>: public detail::CacheBase<Cache, size_t, TValue>
{
public:
static size_t hash(TValue const& value)
{
boost::hash<TValue> hasher;
return hasher(value);
}
};

template<typename TValue>
class Cache<h256, TValue>: public detail::CacheBase<Cache, h256, TValue>
{
public:
static h256 hash(TValue const& value)
{
std::stringstream stream;
stream << value;
return keccak256(stream.str());
}
};

template<>
class Cache<size_t, Json>: public detail::CacheBase<Cache, size_t, Json>
{
public:
static size_t hash(Json const& value)
{
boost::hash<std::string> hasher;
return hasher(value.dump(0));
}
};

template<>
class Cache<h256, Json>: public detail::CacheBase<Cache, h256, Json>
{
public:
static h256 hash(Json const& value) { return keccak256(value.dump(0)); }
};

} // namespace solidity::util
148 changes: 131 additions & 17 deletions libyul/AsmParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,36 @@ std::optional<int> toInt(std::string const& _value)

langutil::DebugData::ConstPtr Parser::createDebugData() const
{
solAssert(m_debugAttributeCache);
switch (m_useSourceLocationFrom)
{
case UseSourceLocationFrom::Scanner:
return DebugData::create(ParserBase::currentLocation(), ParserBase::currentLocation());
case UseSourceLocationFrom::LocationOverride:
return DebugData::create(m_locationOverride, m_locationOverride);
case UseSourceLocationFrom::Comments:
return DebugData::create(ParserBase::currentLocation(), m_locationFromComment, m_astIDFromComment);
case UseSourceLocationFrom::Scanner:
return DebugData::create(
ParserBase::currentLocation(),
ParserBase::currentLocation(),
{},
m_currentDebugAttributes.has_value()
? DebugData::Attributes({m_debugAttributeCache->set(*m_currentDebugAttributes)})
: DebugData::Attributes({})
);
case UseSourceLocationFrom::LocationOverride:
return DebugData::create(
m_locationOverride,
m_locationOverride,
{},
m_currentDebugAttributes.has_value()
? DebugData::Attributes({m_debugAttributeCache->set(*m_currentDebugAttributes)})
: DebugData::Attributes({})
);
case UseSourceLocationFrom::Comments:
return DebugData::create(
ParserBase::currentLocation(),
m_locationFromComment,
m_astIDFromComment,
m_currentDebugAttributes.has_value()
? DebugData::Attributes({m_debugAttributeCache->set(*m_currentDebugAttributes)})
: DebugData::Attributes({})
);
}
solAssert(false, "");
}
Expand Down Expand Up @@ -138,24 +160,20 @@ std::unique_ptr<AST> Parser::parseInline(std::shared_ptr<Scanner> const& _scanne
langutil::Token Parser::advance()
{
auto const token = ParserBase::advance();
if (m_useSourceLocationFrom == UseSourceLocationFrom::Comments)
fetchDebugDataFromComment();
fetchDebugDataFromComment();
return token;
}

void Parser::fetchDebugDataFromComment()
{
solAssert(m_sourceNames.has_value(), "");

static std::regex const tagRegex = std::regex(
R"~~((?:^|\s+)(@[a-zA-Z0-9\-_]+)(?:\s+|$))~~", // tag, e.g: @src
R"~~((?:^|\s+)(@[a-zA-Z0-9\-\._]+)(?:\s+|$))~~", // tag, e.g: @src
std::regex_constants::ECMAScript | std::regex_constants::optimize
);

std::string_view commentLiteral = m_scanner->currentCommentLiteral();
std::match_results<std::string_view::const_iterator> match;

langutil::SourceLocation originLocation = m_locationFromComment;
// Empty for each new node.
std::optional<int> astID;

Expand All @@ -166,10 +184,14 @@ void Parser::fetchDebugDataFromComment()

if (match[1] == "@src")
{
if (auto parseResult = parseSrcComment(commentLiteral, m_scanner->currentCommentLocation()))
tie(commentLiteral, originLocation) = *parseResult;
else
break;
if (m_useSourceLocationFrom == UseSourceLocationFrom::Comments)
{
solAssert(m_sourceNames.has_value(), "");
if (auto parseResult = parseSrcComment(commentLiteral, m_scanner->currentCommentLocation()))
tie(commentLiteral, m_locationFromComment) = *parseResult;
else
break;
}
}
else if (match[1] == "@ast-id")
{
Expand All @@ -178,15 +200,107 @@ void Parser::fetchDebugDataFromComment()
else
break;
}
else if (match[1] == "@debug.set")
{
if (auto parseResult = parseDebugDataAttributeOperationComment(match[1], commentLiteral, m_scanner->currentCommentLocation()))
{
commentLiteral = parseResult->first;
if (parseResult->second.has_value())
m_currentDebugAttributes = parseResult->second.value();
}
else
break;
}
else if (match[1] == "@debug.merge")
{
if (auto parseResult = parseDebugDataAttributeOperationComment(match[1], commentLiteral, m_scanner->currentCommentLocation()))
{
commentLiteral = parseResult->first;
if (parseResult->second.has_value())
{
if (!m_currentDebugAttributes.has_value())
m_currentDebugAttributes = Json::object();
m_currentDebugAttributes->merge_patch(parseResult->second.value());
}
}
else
break;
}
else if (match[1] == "@debug.patch")
{
if (auto parseResult = parseDebugDataAttributeOperationComment(match[1], commentLiteral, m_scanner->currentCommentLocation()))
{
commentLiteral = parseResult->first;
if (parseResult->second.has_value())
applyDebugDataAttributePatch(parseResult->second.value(), m_scanner->currentCommentLocation());
}
else
break;
}
else
// Ignore unrecognized tags.
continue;
}

m_locationFromComment = originLocation;
m_astIDFromComment = astID;
}

std::optional<std::pair<std::string_view, std::optional<Json>>> Parser::parseDebugDataAttributeOperationComment(
std::string const& _command,
std::string_view _arguments,
langutil::SourceLocation const& _location
)
{
std::optional<Json> jsonData;
try
{
jsonData = Json::parse(_arguments.begin(), _arguments.end(), nullptr, true);
}
catch (nlohmann::json::parse_error& e)
{
try
{
jsonData = Json::parse(_arguments.substr(0, e.byte - 1), nullptr, true);
}
catch(nlohmann::json::parse_error& ee)
{
m_errorReporter.syntaxError(
5721_error,
_location,
_command + ": Could not parse debug data: " + removeNlohmannInternalErrorIdentifier(ee.what())
);
jsonData.reset();
}
_arguments = _arguments.substr(e.byte - 1);
}
return {{_arguments, jsonData}};
}

void Parser::applyDebugDataAttributePatch(Json const& _jsonPatch, langutil::SourceLocation const& _location)
{
try
{
if (!m_currentDebugAttributes.has_value())
m_currentDebugAttributes = Json::object();
if (_jsonPatch.is_object())
{
Json array = Json::array();
array.push_back(_jsonPatch);
m_currentDebugAttributes = m_currentDebugAttributes->patch(array);
}
else
m_currentDebugAttributes = m_currentDebugAttributes->patch(_jsonPatch);
}
catch(nlohmann::json::parse_error& ee)
{
m_errorReporter.syntaxError(
9426_error,
_location,
"@debug.patch: Could not patch debug data: " + removeNlohmannInternalErrorIdentifier(ee.what())
);
}
}

std::optional<std::pair<std::string_view, SourceLocation>> Parser::parseSrcComment(
std::string_view const _arguments,
langutil::SourceLocation const& _commentLocation
Expand Down
Loading

0 comments on commit 94f9716

Please sign in to comment.