libsolc: Overhauls memory management.

This commit is contained in:
Christian Parpart 2019-12-04 16:36:56 +01:00 committed by Alex Beregszaszi
parent 69b06a456c
commit 8682af2216
6 changed files with 91 additions and 27 deletions

View File

@ -6,6 +6,8 @@ Breaking changes:
* AST: Inline assembly is exported as structured JSON instead of plain string. * AST: Inline assembly is exported as structured JSON instead of plain string.
* C API (``libsolc``): Introduce context parameter to both ``solidity_compile`` and the callback. * C API (``libsolc``): Introduce context parameter to both ``solidity_compile`` and the callback.
* C API (``libsolc``): The provided callback now takes two parameters, kind and data. The callback can then be used for multiple purposes, such has file imports and SMT queries. * C API (``libsolc``): The provided callback now takes two parameters, kind and data. The callback can then be used for multiple purposes, such has file imports and SMT queries.
* C API (``libsolc``): ``solidity_free`` was renamed to ``solidity_reset``. Functions ``solidity_alloc`` and ``solidity_free`` were added.
* C API (``libsolc``): ``solidity_compile`` now returns a string that must be explicitly freed via ``solidity_free()``
* Commandline Interface: Remove the text-based AST printer (``--ast``). * Commandline Interface: Remove the text-based AST printer (``--ast``).
* Commandline Interface: Switch to the new error reporter by default. ``--old-reporter`` falls back to the deprecated old error reporter. * Commandline Interface: Switch to the new error reporter by default. ``--old-reporter`` falls back to the deprecated old error reporter.
* Commandline Interface: Add option to disable or choose hash method between IPFS and Swarm for the bytecode metadata. * Commandline Interface: Add option to disable or choose hash method between IPFS and Swarm for the bytecode metadata.

View File

@ -2,7 +2,7 @@ if (EMSCRIPTEN)
# Specify which functions to export in soljson.js. # Specify which functions to export in soljson.js.
# Note that additional Emscripten-generated methods needed by solc-js are # Note that additional Emscripten-generated methods needed by solc-js are
# defined to be exported in cmake/EthCompilerSettings.cmake. # defined to be exported in cmake/EthCompilerSettings.cmake.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_solidity_license\",\"_solidity_version\",\"_solidity_compile\"]' -s RESERVED_FUNCTION_POINTERS=20") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_solidity_license\",\"_solidity_version\",\"_solidity_compile\",\"_solidity_alloc\",\"_solidity_free\",\"_solidity_reset\"]' -s RESERVED_FUNCTION_POINTERS=20")
add_executable(soljson libsolc.cpp libsolc.h) add_executable(soljson libsolc.cpp libsolc.h)
target_link_libraries(soljson PRIVATE solidity) target_link_libraries(soljson PRIVATE solidity)
else() else()

View File

@ -27,6 +27,8 @@
#include <libdevcore/Common.h> #include <libdevcore/Common.h>
#include <libdevcore/JSON.h> #include <libdevcore/JSON.h>
#include <cstdlib>
#include <list>
#include <string> #include <string>
#include "license.h" #include "license.h"
@ -38,6 +40,28 @@ using namespace solidity;
namespace namespace
{ {
// The strings in this list must not be resized after they have been added here (via solidity_alloc()), because
// this may potentially change the pointer that was passed to the caller from solidity_alloc().
static list<string> solidityAllocations;
/// Find the equivalent to @p _data in the list of allocations of solidity_alloc(),
/// removes it from the list and returns its value.
///
/// If any invalid argument is being passed, it is considered a programming error
/// on the caller-side and hence, will call abort() then.
string takeOverAllocation(char const* _data)
{
for (auto iter = begin(solidityAllocations); iter != end(solidityAllocations); ++iter)
if (iter->data() == _data)
{
string chunk = move(*iter);
solidityAllocations.erase(iter);
return chunk;
}
abort();
}
ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback, void* _readContext) ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback, void* _readContext)
{ {
ReadCallback::Callback readCallback; ReadCallback::Callback readCallback;
@ -47,7 +71,7 @@ ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback, vo
{ {
char* contents_c = nullptr; char* contents_c = nullptr;
char* error_c = nullptr; char* error_c = nullptr;
_readCallback(_readContext, _kind.c_str(), _data.c_str(), &contents_c, &error_c); _readCallback(_readContext, _kind.data(), _data.data(), &contents_c, &error_c);
ReadCallback::Result result; ReadCallback::Result result;
result.success = true; result.success = true;
if (!contents_c && !error_c) if (!contents_c && !error_c)
@ -58,14 +82,12 @@ ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback, vo
if (contents_c) if (contents_c)
{ {
result.success = true; result.success = true;
result.responseOrErrorMessage = string(contents_c); result.responseOrErrorMessage = takeOverAllocation(contents_c);
free(contents_c);
} }
if (error_c) if (error_c)
{ {
result.success = false; result.success = false;
result.responseOrErrorMessage = string(error_c); result.responseOrErrorMessage = takeOverAllocation(error_c);
free(error_c);
} }
return result; return result;
}; };
@ -76,13 +98,11 @@ ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback, vo
string compile(string _input, CStyleReadFileCallback _readCallback, void* _readContext) string compile(string _input, CStyleReadFileCallback _readCallback, void* _readContext)
{ {
StandardCompiler compiler(wrapReadCallback(_readCallback, _readContext)); StandardCompiler compiler(wrapReadCallback(_readCallback, _readContext));
return compiler.compile(std::move(_input)); return compiler.compile(move(_input));
} }
} }
static string s_outputBuffer;
extern "C" extern "C"
{ {
extern char const* solidity_license() noexcept extern char const* solidity_license() noexcept
@ -90,20 +110,40 @@ extern char const* solidity_license() noexcept
static string fullLicenseText = otherLicenses + licenseText; static string fullLicenseText = otherLicenses + licenseText;
return fullLicenseText.c_str(); return fullLicenseText.c_str();
} }
extern char const* solidity_version() noexcept extern char const* solidity_version() noexcept
{ {
return VersionString.c_str(); return VersionString.c_str();
} }
extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback, void* _readContext) noexcept extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback, void* _readContext) noexcept
{ {
s_outputBuffer = compile(_input, _readCallback, _readContext); return solidityAllocations.emplace_back(compile(_input, _readCallback, _readContext)).data();
return s_outputBuffer.c_str();
} }
extern void solidity_free() noexcept
extern char* solidity_alloc(size_t _size) noexcept
{
try
{
return solidityAllocations.emplace_back(_size, '\0').data();
}
catch (...)
{
// most likely a std::bad_alloc(), if at all.
return nullptr;
}
}
extern void solidity_free(char* _data) noexcept
{
takeOverAllocation(_data);
}
extern void solidity_reset() noexcept
{ {
// This is called right before each compilation, but not at the end, so additional memory // This is called right before each compilation, but not at the end, so additional memory
// can be freed here. // can be freed here.
yul::YulStringRepository::reset(); yul::YulStringRepository::reset();
s_outputBuffer.clear(); solidityAllocations.clear();
} }
} }

View File

@ -23,6 +23,7 @@
#pragma once #pragma once
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus #ifdef __cplusplus
#define SOLC_NOEXCEPT noexcept #define SOLC_NOEXCEPT noexcept
@ -38,39 +39,61 @@ extern "C" {
/// ///
/// @param _context The readContext passed to solidity_compile. Can be NULL. /// @param _context The readContext passed to solidity_compile. Can be NULL.
/// @param _kind The kind of callback (a string). /// @param _kind The kind of callback (a string).
/// @param _data The data for the callback. /// @param _data The data for the callback (a string).
/// @param o_contents A pointer to the contents of the file, if found. /// @param o_contents A pointer to the contents of the file, if found. Allocated via solidity_alloc().
/// @param o_error A pointer to an error message, if there is one. /// @param o_error A pointer to an error message, if there is one.
/// ///
/// If the callback is not supported, o_contents and o_error should be set to NULL. /// The file (as well as error) contents that is to be allocated by the callback
/// implementor must use the solidity_alloc() API to allocate its underlying
/// storage. Ownership is then transferred to the compiler which will take care
/// of the deallocation.
/// ///
/// The two pointers (o_contents and o_error) should be heap-allocated and are free'd by the caller. /// If the callback is not supported, *o_contents and *o_error must be set to NULL.
typedef void (*CStyleReadFileCallback)(void* _context, char const* _kind, char const* _data, char** o_contents, char** o_error); typedef void (*CStyleReadFileCallback)(void* _context, char const* _kind, char const* _data, char** o_contents, char** o_error);
/// Returns the complete license document. /// Returns the complete license document.
/// ///
/// The pointer returned must not be freed by the caller. /// The pointer returned must NOT be freed by the caller.
char const* solidity_license() SOLC_NOEXCEPT; char const* solidity_license() SOLC_NOEXCEPT;
/// Returns the compiler version. /// Returns the compiler version.
/// ///
/// The pointer returned must not be freed by the caller. /// The pointer returned must NOT be freed by the caller.
char const* solidity_version() SOLC_NOEXCEPT; char const* solidity_version() SOLC_NOEXCEPT;
/// Allocates a chunk of memory of @p _size bytes.
///
/// Use this function inside callbacks to allocate data that is to be passed to
/// the compiler. You may use solidity_free() or solidity_reset() to free this
/// memory again but it is not required as the compiler takes ownership for any
/// data passed to it via callbacks.
///
/// This function will return NULL if the requested memory region could not be allocated.
char* solidity_alloc(size_t _size) SOLC_NOEXCEPT;
/// Explicitly frees the memory (@p _data) that was being allocated with solidity_alloc()
/// or returned by a call to solidity_compile().
///
/// Important, this call will abort() in case of any invalid argument being passed to this call.
void solidity_free(char* _data) SOLC_NOEXCEPT;
/// Takes a "Standard Input JSON" and an optional callback (can be set to null). Returns /// Takes a "Standard Input JSON" and an optional callback (can be set to null). Returns
/// a "Standard Output JSON". Both are to be UTF-8 encoded. /// a "Standard Output JSON". Both are to be UTF-8 encoded.
/// ///
/// @param _input The input JSON to process. /// @param _input The input JSON to process.
/// @param _readCallback The optional callback pointer. Can be NULL. /// @param _readCallback The optional callback pointer. Can be NULL, but if not NULL,
/// it can be called by the compiler to request additional input.
/// Please see the documentation of the type for details.
/// @param _readContext An optional context pointer passed to _readCallback. Can be NULL. /// @param _readContext An optional context pointer passed to _readCallback. Can be NULL.
/// ///
/// @returns A pointer to the result. The pointer returned must not be freed by the caller. /// @returns A pointer to the result. The pointer returned must be freed by the caller using solidity_free() or solidity_reset().
char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback, void* _readContext) SOLC_NOEXCEPT; char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback, void* _readContext) SOLC_NOEXCEPT;
/// Frees up any allocated memory. /// Frees up any allocated memory.
/// ///
/// NOTE: the pointer returned by solidity_compile is invalid after calling this! /// NOTE: the pointer returned by solidity_compile as well as any other pointer retrieved via solidity_alloc()
void solidity_free() SOLC_NOEXCEPT; /// is invalid after calling this!
void solidity_reset() SOLC_NOEXCEPT;
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -60,9 +60,10 @@ bool containsError(Json::Value const& _compilerResult, string const& _type, stri
Json::Value compile(string const& _input, CStyleReadFileCallback _callback = nullptr) Json::Value compile(string const& _input, CStyleReadFileCallback _callback = nullptr)
{ {
string output(solidity_compile(_input.c_str(), _callback, nullptr)); string output(solidity_compile(_input.c_str(), _callback, nullptr));
// This should be safe given the above copies the output.
solidity_reset();
Json::Value ret; Json::Value ret;
BOOST_REQUIRE(jsonParseStrict(output, ret)); BOOST_REQUIRE(jsonParseStrict(output, ret));
solidity_free();
return ret; return ret;
} }
@ -74,14 +75,12 @@ BOOST_AUTO_TEST_CASE(read_version)
{ {
string output(solidity_version()); string output(solidity_version());
BOOST_CHECK(output.find(VersionString) == 0); BOOST_CHECK(output.find(VersionString) == 0);
solidity_free();
} }
BOOST_AUTO_TEST_CASE(read_license) BOOST_AUTO_TEST_CASE(read_license)
{ {
string output(solidity_license()); string output(solidity_license());
BOOST_CHECK(output.find("GNU GENERAL PUBLIC LICENSE") != string::npos); BOOST_CHECK(output.find("GNU GENERAL PUBLIC LICENSE") != string::npos);
solidity_free();
} }
BOOST_AUTO_TEST_CASE(standard_compilation) BOOST_AUTO_TEST_CASE(standard_compilation)

View File

@ -47,7 +47,7 @@ void FuzzerUtil::runCompiler(string const& _input, bool _quiet)
cout << "Output JSON: " << outputString << endl; cout << "Output JSON: " << outputString << endl;
// This should be safe given the above copies the output. // This should be safe given the above copies the output.
solidity_free(); solidity_reset();
Json::Value output; Json::Value output;
if (!jsonParseStrict(outputString, output)) if (!jsonParseStrict(outputString, output))