<commit_before>/* This file is part of Ingen.
 * Copyright 2007-2011 David Robillard <http://drobilla.net>
 *
 * Ingen 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 2 of the License, or (at your option) any later
 * version.
 *
 * Ingen 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 details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <string>
#include <boost/format.hpp>
#include "raul/log.hpp"
#include "raul/Maid.hpp"
#include "ingen/PortType.hpp"
#include "shared/LV2URIMap.hpp"
#include "ClientBroadcaster.hpp"
#include "ControlBindings.hpp"
#include "CreateNode.hpp"
#include "CreatePatch.hpp"
#include "CreatePort.hpp"
#include "Driver.hpp"
#include "Engine.hpp"
#include "EngineStore.hpp"
#include "GraphObjectImpl.hpp"
#include "PatchImpl.hpp"
#include "PluginImpl.hpp"
#include "PortImpl.hpp"
#include "Request.hpp"
#include "SetMetadata.hpp"
#include "SetPortValue.hpp"

#define LOG(s) s << "[SetMetadata] "

using namespace std;
using namespace Raul;

namespace Ingen {
namespace Server {
namespace Events {

typedef Resource::Properties Properties;

SetMetadata::SetMetadata(
		Engine&             engine,
		SharedPtr<Request>  request,
		SampleCount         timestamp,
		bool                create,
		Resource::Graph     context,
		const URI&          subject,
		const Properties&   properties,
		const Properties&   remove)
	: QueuedEvent(engine, request, timestamp, false)
	, _create_event(NULL)
	, _subject(subject)
	, _properties(properties)
	, _remove(remove)
	, _object(NULL)
	, _patch(NULL)
	, _compiled_patch(NULL)
	, _create(create)
	, _context(context)
{
	if (context != Resource::DEFAULT) {
		Resource::set_context(_properties, context);
	}

	/*
	LOG(info) << "Set " << subject << " : " << context << " {" << endl;
	typedef Resource::Properties::const_iterator iterator;
	for (iterator i = properties.begin(); i != properties.end(); ++i)
		LOG(info) << "    " << i->first << " = " << i->second << " :: " << i->second.type() << endl;
	LOG(info) << "}" << endl;

	LOG(info) << "Unset " << subject << " {" << endl;
	typedef Resource::Properties::const_iterator iterator;
	for (iterator i = remove.begin(); i != remove.end(); ++i)
		LOG(info) << "    " << i->first << " = " << i->second << " :: " << i->second.type() << endl;
	LOG(info) << "}" << endl;
	*/
}

SetMetadata::~SetMetadata()
{
	for (SetEvents::iterator i = _set_events.begin(); i != _set_events.end(); ++i)
		delete *i;

	delete _create_event;
}

void
SetMetadata::pre_process()
{
	typedef Properties::const_iterator iterator;

	const bool is_graph_object = Path::is_path(_subject);

	_object = is_graph_object
		? _engine.engine_store()->find_object(Path(_subject.str()))
		: static_cast<Shared::ResourceImpl*>(_engine.node_factory()->plugin(_subject));

	if (!_object && (!is_graph_object || !_create)) {
		_error = NOT_FOUND;
		QueuedEvent::pre_process();
		return;
	}

	const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get();

	if (is_graph_object && !_object) {
		Path path(_subject.str());
		bool is_patch = false, is_node = false, is_port = false, is_output = false;
		PortType data_type(PortType::UNKNOWN);
		Shared::ResourceImpl::type(uris, _properties, is_patch, is_node, is_port, is_output, data_type);

		// Create a separate request without a source so EventSource isn't unblocked twice
		SharedPtr<Request> sub_request(new Request(NULL, _request->client(), _request->id()));

		if (is_patch) {
			uint32_t poly = 1;
			iterator p = _properties.find(uris.ingen_polyphony);
			if (p != _properties.end() && p->second.is_valid() && p->second.type() == Atom::INT)
				poly = p->second.get_int32();
			_create_event = new CreatePatch(_engine, sub_request, _time,
					path, poly, _properties);
		} else if (is_node) {
			const iterator p = _properties.find(uris.rdf_instanceOf);
			_create_event = new CreateNode(_engine, sub_request, _time,
					path, p->second.get_uri(), _properties);
		} else if (is_port) {
			_blocking = bool(_request);
			_create_event = new CreatePort(_engine, sub_request, _time,
					path, data_type.uri(), is_output, _properties);
		}
		if (_create_event)
			_create_event->pre_process();
		else
			_error = BAD_OBJECT_TYPE;
		QueuedEvent::pre_process();
		return;
	}

	_types.reserve(_properties.size());

	GraphObjectImpl* obj = dynamic_cast<GraphObjectImpl*>(_object);

#if 0
	// If we're replacing (i.e. this is a PUT, not a POST), first remove all properties
	// with keys we will later set.  This must be done first so a PUT with several properties
	// of the same predicate (e.g. rdf:type) retains the multiple values.  Only previously
	// existing properties should be replaced
	if (_replace)
		for (Properties::iterator p = _properties.begin(); p != _properties.end(); ++p)
			obj->properties().erase(p->first);
#endif

	for (Properties::const_iterator p = _remove.begin(); p != _remove.end(); ++p) {
		const Raul::URI&  key   = p->first;
		const Raul::Atom& value = p->second;
		if (key == uris.ingen_controlBinding && value == uris.wildcard) {
			PortImpl* port = dynamic_cast<PortImpl*>(_object);
			if (port)
				_old_bindings = _engine.control_bindings()->remove(port);
		}
		_object->remove_property(key, value);
	}

	for (Properties::iterator p = _properties.begin(); p != _properties.end(); ++p) {
		const Raul::URI&  key   = p->first;
		const Raul::Atom& value = p->second;
		SpecialType       op    = NONE;
		if (obj) {
			Resource& resource = *obj;
			resource.add_property(key, value);

			PortImpl* port = dynamic_cast<PortImpl*>(_object);
			if (port) {
				if (key == uris.ingen_broadcast) {
					if (value.type() == Atom::BOOL) {
						op = ENABLE_BROADCAST;
					} else {
						_error = BAD_VALUE_TYPE;
					}
				} else if (key == uris.ingen_value) {
					SetPortValue* ev = new SetPortValue(_engine, _request, _time, port, value);
					ev->pre_process();
					_set_events.push_back(ev);
				} else if (key == uris.ingen_controlBinding) {
					if (port->is_a(PortType::CONTROL)) {
						if (value == uris.wildcard) {
							_engine.control_bindings()->learn(port);
						} else if (value.type() == Atom::DICT) {
							op = CONTROL_BINDING;
						} else {
							_error = BAD_VALUE_TYPE;
						}
					} else {
						_error = BAD_OBJECT_TYPE;
					}
				}
			} else if ((_patch = dynamic_cast<PatchImpl*>(_object))) {
				if (key == uris.ingen_enabled) {
					if (value.type() == Atom::BOOL) {
						op = ENABLE;
						// FIXME: defer this until all other metadata has been processed
						if (value.get_bool() && !_patch->enabled())
							_compiled_patch = _patch->compile();
					} else {
						_error = BAD_VALUE_TYPE;
					}
				} else if (key == uris.ingen_polyphony) {
					if (value.type() == Atom::INT) {
						op = POLYPHONY;
						_blocking = true;
						_patch->prepare_internal_poly(*_engine.buffer_factory(), value.get_int32());
					} else {
						_error = BAD_VALUE_TYPE;
					}
				}
			} else if (key == uris.ingen_polyphonic) {
				PatchImpl* parent = dynamic_cast<PatchImpl*>(obj->parent());
				if (parent) {
					if (value.type() == Atom::BOOL) {
						op = POLYPHONIC;
						_blocking = true;
						obj->set_property(key, value.get_bool());
						NodeImpl* node = dynamic_cast<NodeImpl*>(obj);
						if (node)
							node->set_polyphonic(value.get_bool());
						if (value.get_bool()) {
							obj->prepare_poly(*_engine.buffer_factory(), parent->internal_poly());
						} else {
							obj->prepare_poly(*_engine.buffer_factory(), 1);
						}
					} else {
						_error = BAD_VALUE_TYPE;
					}
				} else {
					_error = BAD_OBJECT_TYPE;
				}
			}
		}

		if (_error != NO_ERROR) {
			_error_predicate += key.str();
			break;
		}

		_types.push_back(op);
	}

	QueuedEvent::pre_process();
}

void
SetMetadata::execute(ProcessContext& context)
{
	if (_error != NO_ERROR) {
		QueuedEvent::execute(context);
		return;
	}

	if (_create_event) {
		QueuedEvent::execute(context);
		_create_event->execute(context);
		if (_blocking)
			_request->unblock();
		return;
	}

	for (SetEvents::iterator i = _set_events.begin(); i != _set_events.end(); ++i)
		(*i)->execute(context);

	GraphObjectImpl* const object = dynamic_cast<GraphObjectImpl*>(_object);
	NodeImpl* const        node   = dynamic_cast<NodeImpl*>(_object);
	PortImpl* const        port   = dynamic_cast<PortImpl*>(_object);

	std::vector<SpecialType>::const_iterator t = _types.begin();
	for (Properties::const_iterator p = _properties.begin(); p != _properties.end(); ++p, ++t) {
		const Raul::Atom& value = p->second;
		switch (*t) {
		case ENABLE_BROADCAST:
			if (port)
				port->broadcast(value.get_bool());
			break;
		case ENABLE:
			if (value.get_bool()) {
				if (_compiled_patch) {
					_engine.maid()->push(_patch->compiled_patch());
					_patch->compiled_patch(_compiled_patch);
				}
				_patch->enable();
			} else {
				_patch->disable();
			}
			break;
		case POLYPHONIC:
			{
				PatchImpl* parent = reinterpret_cast<PatchImpl*>(object->parent());
				if (value.get_bool())
					object->apply_poly(*_engine.maid(), parent->internal_poly());
				else
					object->apply_poly(*_engine.maid(), 1);
			}
			break;
		case POLYPHONY:
			if (_patch->internal_poly() != static_cast<uint32_t>(value.get_int32()) &&
					!_patch->apply_internal_poly(_engine.driver()->context(),
						*_engine.buffer_factory(),
						*_engine.maid(), value.get_int32())) {
				_error = INTERNAL;
			}
			break;
		case CONTROL_BINDING:
			if (port) {
				_engine.control_bindings()->port_binding_changed(context, port);
			} else if (node) {
				if (node->plugin_impl()->type() == Plugin::Internal) {
					node->learn();
				}
			}
			break;
		case NONE:
			break;
		}
	}

	QueuedEvent::execute(context);

	if (_blocking)
		_request->unblock();
}

void
SetMetadata::post_process()
{
	for (SetEvents::iterator i = _set_events.begin(); i != _set_events.end(); ++i)
		(*i)->post_process();

	switch (_error) {
	case NO_ERROR:
		if (_create_event) {
			_create_event->post_process();
		} else {
			_request->respond_ok();
			if (_create)
				_engine.broadcaster()->put(_subject, _properties, _context);
			else
				_engine.broadcaster()->delta(_subject, _remove, _properties);
		}
		break;
	case NOT_FOUND:
		_request->respond_error((boost::format(
				"Unable to find object `%1%'") % _subject).str());
		break;
	case INTERNAL:
		_request->respond_error("Internal error");
		break;
	case BAD_OBJECT_TYPE:
		_request->respond_error((boost::format(
				"Bad type for object `%1%'") % _subject).str());
		break;
	case BAD_VALUE_TYPE:
		_request->respond_error((boost::format(
				"Bad metadata value type for subject `%1%' predicate `%2%'")
					% _subject % _error_predicate).str());
		break;
	}
}

} // namespace Server
} // namespace Ingen
} // namespace Events

<commit_msg>Obey "special" properties on object creation (patch from Paul Giblock, ticket #723)<commit_after>/* This file is part of Ingen.
 * Copyright 2007-2011 David Robillard <http://drobilla.net>
 *
 * Ingen 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 2 of the License, or (at your option) any later
 * version.
 *
 * Ingen 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 details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <string>
#include <boost/format.hpp>
#include "raul/log.hpp"
#include "raul/Maid.hpp"
#include "ingen/PortType.hpp"
#include "shared/LV2URIMap.hpp"
#include "ClientBroadcaster.hpp"
#include "ControlBindings.hpp"
#include "CreateNode.hpp"
#include "CreatePatch.hpp"
#include "CreatePort.hpp"
#include "Driver.hpp"
#include "Engine.hpp"
#include "EngineStore.hpp"
#include "GraphObjectImpl.hpp"
#include "PatchImpl.hpp"
#include "PluginImpl.hpp"
#include "PortImpl.hpp"
#include "Request.hpp"
#include "SetMetadata.hpp"
#include "SetPortValue.hpp"

#define LOG(s) s << "[SetMetadata] "

using namespace std;
using namespace Raul;

namespace Ingen {
namespace Server {
namespace Events {

typedef Resource::Properties Properties;

SetMetadata::SetMetadata(
		Engine&             engine,
		SharedPtr<Request>  request,
		SampleCount         timestamp,
		bool                create,
		Resource::Graph     context,
		const URI&          subject,
		const Properties&   properties,
		const Properties&   remove)
	: QueuedEvent(engine, request, timestamp, false)
	, _create_event(NULL)
	, _subject(subject)
	, _properties(properties)
	, _remove(remove)
	, _object(NULL)
	, _patch(NULL)
	, _compiled_patch(NULL)
	, _create(create)
	, _context(context)
{
	if (context != Resource::DEFAULT) {
		Resource::set_context(_properties, context);
	}

	/*
	LOG(info) << "Set " << subject << " : " << context << " {" << endl;
	typedef Resource::Properties::const_iterator iterator;
	for (iterator i = properties.begin(); i != properties.end(); ++i)
		LOG(info) << "    " << i->first << " = " << i->second << " :: " << i->second.type() << endl;
	LOG(info) << "}" << endl;

	LOG(info) << "Unset " << subject << " {" << endl;
	typedef Resource::Properties::const_iterator iterator;
	for (iterator i = remove.begin(); i != remove.end(); ++i)
		LOG(info) << "    " << i->first << " = " << i->second << " :: " << i->second.type() << endl;
	LOG(info) << "}" << endl;
	*/
}

SetMetadata::~SetMetadata()
{
	for (SetEvents::iterator i = _set_events.begin(); i != _set_events.end(); ++i)
		delete *i;

	delete _create_event;
}

void
SetMetadata::pre_process()
{
	typedef Properties::const_iterator iterator;

	const bool is_graph_object = Path::is_path(_subject);

	_object = is_graph_object
		? _engine.engine_store()->find_object(Path(_subject.str()))
		: static_cast<Shared::ResourceImpl*>(_engine.node_factory()->plugin(_subject));

	if (!_object && (!is_graph_object || !_create)) {
		_error = NOT_FOUND;
		QueuedEvent::pre_process();
		return;
	}

	const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get();

	if (is_graph_object && !_object) {
		Path path(_subject.str());
		bool is_patch = false, is_node = false, is_port = false, is_output = false;
		PortType data_type(PortType::UNKNOWN);
		Shared::ResourceImpl::type(uris, _properties, is_patch, is_node, is_port, is_output, data_type);

		// Create a separate request without a source so EventSource isn't unblocked twice
		SharedPtr<Request> sub_request(new Request(NULL, _request->client(), _request->id()));

		if (is_patch) {
			uint32_t poly = 1;
			iterator p = _properties.find(uris.ingen_polyphony);
			if (p != _properties.end() && p->second.is_valid() && p->second.type() == Atom::INT)
				poly = p->second.get_int32();
			_create_event = new CreatePatch(_engine, sub_request, _time,
					path, poly, _properties);
		} else if (is_node) {
			const iterator p = _properties.find(uris.rdf_instanceOf);
			_create_event = new CreateNode(_engine, sub_request, _time,
					path, p->second.get_uri(), _properties);
		} else if (is_port) {
			_blocking = bool(_request);
			_create_event = new CreatePort(_engine, sub_request, _time,
					path, data_type.uri(), is_output, _properties);
		}
		if (_create_event) {
			_create_event->pre_process();
			// Grab the object for applying properties, if the create-event succeeded
			_object = _engine.engine_store()->find_object(Path(_subject.str()));
		} else {
			_error = BAD_OBJECT_TYPE;
		}
	}

	_types.reserve(_properties.size());

	GraphObjectImpl* obj = dynamic_cast<GraphObjectImpl*>(_object);

#if 0
	// If we're replacing (i.e. this is a PUT, not a POST), first remove all properties
	// with keys we will later set.  This must be done first so a PUT with several properties
	// of the same predicate (e.g. rdf:type) retains the multiple values.  Only previously
	// existing properties should be replaced
	if (_replace)
		for (Properties::iterator p = _properties.begin(); p != _properties.end(); ++p)
			obj->properties().erase(p->first);
#endif

	for (Properties::const_iterator p = _remove.begin(); p != _remove.end(); ++p) {
		const Raul::URI&  key   = p->first;
		const Raul::Atom& value = p->second;
		if (key == uris.ingen_controlBinding && value == uris.wildcard) {
			PortImpl* port = dynamic_cast<PortImpl*>(_object);
			if (port)
				_old_bindings = _engine.control_bindings()->remove(port);
		}
		_object->remove_property(key, value);
	}

	for (Properties::iterator p = _properties.begin(); p != _properties.end(); ++p) {
		const Raul::URI&  key   = p->first;
		const Raul::Atom& value = p->second;
		SpecialType       op    = NONE;
		if (obj) {
			Resource& resource = *obj;
			resource.add_property(key, value);

			PortImpl* port = dynamic_cast<PortImpl*>(_object);
			if (port) {
				if (key == uris.ingen_broadcast) {
					if (value.type() == Atom::BOOL) {
						op = ENABLE_BROADCAST;
					} else {
						_error = BAD_VALUE_TYPE;
					}
				} else if (key == uris.ingen_value) {
					SetPortValue* ev = new SetPortValue(_engine, _request, _time, port, value);
					ev->pre_process();
					_set_events.push_back(ev);
				} else if (key == uris.ingen_controlBinding) {
					if (port->is_a(PortType::CONTROL)) {
						if (value == uris.wildcard) {
							_engine.control_bindings()->learn(port);
						} else if (value.type() == Atom::DICT) {
							op = CONTROL_BINDING;
						} else {
							_error = BAD_VALUE_TYPE;
						}
					} else {
						_error = BAD_OBJECT_TYPE;
					}
				}
			} else if ((_patch = dynamic_cast<PatchImpl*>(_object))) {
				if (key == uris.ingen_enabled) {
					if (value.type() == Atom::BOOL) {
						op = ENABLE;
						// FIXME: defer this until all other metadata has been processed
						if (value.get_bool() && !_patch->enabled())
							_compiled_patch = _patch->compile();
					} else {
						_error = BAD_VALUE_TYPE;
					}
				} else if (key == uris.ingen_polyphony) {
					if (value.type() == Atom::INT) {
						op = POLYPHONY;
						_blocking = true;
						_patch->prepare_internal_poly(*_engine.buffer_factory(), value.get_int32());
					} else {
						_error = BAD_VALUE_TYPE;
					}
				}
			} else if (key == uris.ingen_polyphonic) {
				PatchImpl* parent = dynamic_cast<PatchImpl*>(obj->parent());
				if (parent) {
					if (value.type() == Atom::BOOL) {
						op = POLYPHONIC;
						_blocking = true;
						obj->set_property(key, value.get_bool());
						NodeImpl* node = dynamic_cast<NodeImpl*>(obj);
						if (node)
							node->set_polyphonic(value.get_bool());
						if (value.get_bool()) {
							obj->prepare_poly(*_engine.buffer_factory(), parent->internal_poly());
						} else {
							obj->prepare_poly(*_engine.buffer_factory(), 1);
						}
					} else {
						_error = BAD_VALUE_TYPE;
					}
				} else {
					_error = BAD_OBJECT_TYPE;
				}
			}
		}

		if (_error != NO_ERROR) {
			_error_predicate += key.str();
			break;
		}

		_types.push_back(op);
	}

	QueuedEvent::pre_process();
}

void
SetMetadata::execute(ProcessContext& context)
{
	if (_error != NO_ERROR) {
		QueuedEvent::execute(context);
		return;
	}

	if (_create_event) {
		_create_event->execute(context);
	}

	for (SetEvents::iterator i = _set_events.begin(); i != _set_events.end(); ++i)
		(*i)->execute(context);

	GraphObjectImpl* const object = dynamic_cast<GraphObjectImpl*>(_object);
	NodeImpl* const        node   = dynamic_cast<NodeImpl*>(_object);
	PortImpl* const        port   = dynamic_cast<PortImpl*>(_object);

	std::vector<SpecialType>::const_iterator t = _types.begin();
	for (Properties::const_iterator p = _properties.begin(); p != _properties.end(); ++p, ++t) {
		const Raul::Atom& value = p->second;
		switch (*t) {
		case ENABLE_BROADCAST:
			if (port)
				port->broadcast(value.get_bool());
			break;
		case ENABLE:
			if (value.get_bool()) {
				if (_compiled_patch) {
					_engine.maid()->push(_patch->compiled_patch());
					_patch->compiled_patch(_compiled_patch);
				}
				_patch->enable();
			} else {
				_patch->disable();
			}
			break;
		case POLYPHONIC:
			{
				PatchImpl* parent = reinterpret_cast<PatchImpl*>(object->parent());
				if (value.get_bool())
					object->apply_poly(*_engine.maid(), parent->internal_poly());
				else
					object->apply_poly(*_engine.maid(), 1);
			}
			break;
		case POLYPHONY:
			if (_patch->internal_poly() != static_cast<uint32_t>(value.get_int32()) &&
					!_patch->apply_internal_poly(_engine.driver()->context(),
						*_engine.buffer_factory(),
						*_engine.maid(), value.get_int32())) {
				_error = INTERNAL;
			}
			break;
		case CONTROL_BINDING:
			if (port) {
				_engine.control_bindings()->port_binding_changed(context, port);
			} else if (node) {
				if (node->plugin_impl()->type() == Plugin::Internal) {
					node->learn();
				}
			}
			break;
		case NONE:
			break;
		}
	}

	QueuedEvent::execute(context);

	if (_blocking)
		_request->unblock();
}

void
SetMetadata::post_process()
{
	for (SetEvents::iterator i = _set_events.begin(); i != _set_events.end(); ++i)
		(*i)->post_process();

	switch (_error) {
	case NO_ERROR:
		if (_create_event) {
			_create_event->post_process();
		} else {
			_request->respond_ok();
			if (_create)
				_engine.broadcaster()->put(_subject, _properties, _context);
			else
				_engine.broadcaster()->delta(_subject, _remove, _properties);
		}
		break;
	case NOT_FOUND:
		_request->respond_error((boost::format(
				"Unable to find object `%1%'") % _subject).str());
		break;
	case INTERNAL:
		_request->respond_error("Internal error");
		break;
	case BAD_OBJECT_TYPE:
		_request->respond_error((boost::format(
				"Bad type for object `%1%'") % _subject).str());
		break;
	case BAD_VALUE_TYPE:
		_request->respond_error((boost::format(
				"Bad metadata value type for subject `%1%' predicate `%2%'")
					% _subject % _error_predicate).str());
		break;
	}
}

} // namespace Server
} // namespace Ingen
} // namespace Events

<|endoftext|>


<commit_before>/*
 * Copyright 2014 Milian Wolff <mail@milianw.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/**
 * @file heaptrack_interpret.cpp
 *
 * @brief Interpret raw heaptrack data and add Dwarf based debug information.
 */

#include <iostream>
#include <sstream>
#include <unordered_map>
#include <vector>
#include <tuple>
#include <algorithm>
#include <stdio_ext.h>
#include <inttypes.h>

#include <cxxabi.h>

#include <boost/algorithm/string/predicate.hpp>

#include "libbacktrace/backtrace.h"
#include "libbacktrace/internal.h"
#include "linereader.h"
#include "pointermap.h"

using namespace std;

namespace {

string demangle(const char* function)
{
    if (!function) {
        return {};
    } else if (function[0] != '_' || function[1] != 'Z') {
        return {function};
    }

    string ret;
    int status = 0;
    char* demangled = abi::__cxa_demangle(function, 0, 0, &status);
    if (demangled) {
        ret = demangled;
        free(demangled);
    }
    return ret;
}

struct AddressInformation
{
    string function;
    string file;
    int line = 0;
};

struct Module
{
    Module(uintptr_t addressStart, uintptr_t addressEnd, backtrace_state* backtraceState, size_t moduleIndex)
        : addressStart(addressStart)
        , addressEnd(addressEnd)
        , moduleIndex(moduleIndex)
        , backtraceState(backtraceState)
    {
    }

    AddressInformation resolveAddress(uintptr_t address) const
    {
        AddressInformation info;
        if (!backtraceState) {
            return info;
        }

        backtrace_pcinfo(backtraceState, address,
                         [] (void *data, uintptr_t /*addr*/, const char *file, int line, const char *function) -> int {
                            auto info = reinterpret_cast<AddressInformation*>(data);
                            info->function = demangle(function);
                            info->file = file ? file : "";
                            info->line = line;
                            return 0;
                         }, [] (void */*data*/, const char */*msg*/, int /*errnum*/) {}, &info);

        if (info.function.empty()) {
            backtrace_syminfo(backtraceState, address,
                              [] (void *data, uintptr_t /*pc*/, const char *symname, uintptr_t /*symval*/, uintptr_t /*symsize*/) {
                                if (symname) {
                                    reinterpret_cast<AddressInformation*>(data)->function = demangle(symname);
                                }
                              }, [] (void */*data*/, const char *msg, int errnum) {
                                cerr << "Module backtrace error (code " << errnum << "): " << msg << endl;
                              }, &info);
        }

        return info;
    }

    bool operator<(const Module& module) const
    {
        return tie(addressStart, addressEnd, moduleIndex)
             < tie(module.addressStart, module.addressEnd, module.moduleIndex);
    }

    bool operator!=(const Module& module) const
    {
        return tie(addressStart, addressEnd, moduleIndex)
            != tie(module.addressStart, module.addressEnd, module.moduleIndex);
    }

    uintptr_t addressStart;
    uintptr_t addressEnd;
    size_t moduleIndex;
    backtrace_state* backtraceState;
};

struct ResolvedIP
{
    size_t moduleIndex = 0;
    size_t fileIndex = 0;
    size_t functionIndex = 0;
    int line = 0;
};

struct AccumulatedTraceData
{
    AccumulatedTraceData()
    {
        m_modules.reserve(256);
        m_backtraceStates.reserve(64);
        m_internedData.reserve(4096);
        m_encounteredIps.reserve(32768);
    }

    ~AccumulatedTraceData()
    {
        fprintf(stdout, "# strings: %zu\n# ips: %zu\n",
                m_internedData.size(), m_encounteredIps.size());
    }

    ResolvedIP resolve(const uintptr_t ip)
    {
        if (m_modulesDirty) {
            // sort by addresses, required for binary search below
            sort(m_modules.begin(), m_modules.end());

#ifndef NDEBUG
            for (size_t i = 0; i < m_modules.size(); ++i) {
                const auto& m1 = m_modules[i];
                for (size_t j = i + 1; j < m_modules.size(); ++j) {
                    if (i == j) {
                        continue;
                    }
                    const auto& m2 = m_modules[j];
                    if ((m1.addressStart <= m2.addressStart && m1.addressEnd > m2.addressStart) ||
                        (m1.addressStart < m2.addressEnd && m1.addressEnd >= m2.addressEnd))
                    {
                        cerr << "OVERLAPPING MODULES: " << hex
                             << m1.moduleIndex << " (" << m1.addressStart << " to " << m1.addressEnd << ") and "
                             << m1.moduleIndex << " (" << m2.addressStart << " to " << m2.addressEnd << ")\n"
                             << dec;
                    } else if (m2.addressStart >= m1.addressEnd) {
                        break;
                    }
                }
            }
#endif

            m_modulesDirty = false;
        }

        ResolvedIP data;
        // find module for this instruction pointer
        auto module = lower_bound(m_modules.begin(), m_modules.end(), ip,
                                    [] (const Module& module, const uintptr_t ip) -> bool {
                                        return module.addressEnd < ip;
                                    });
        if (module != m_modules.end() && module->addressStart <= ip && module->addressEnd >= ip) {
            data.moduleIndex = module->moduleIndex;
            const auto info = module->resolveAddress(ip);
            data.fileIndex = intern(info.file);
            data.functionIndex = intern(info.function);
            data.line = info.line;
        }
        return data;
    }

    size_t intern(const string& str)
    {
        if (str.empty()) {
            return 0;
        }

        auto it = m_internedData.find(str);
        if (it != m_internedData.end()) {
            return it->second;
        }
        const size_t id = m_internedData.size() + 1;
        m_internedData.insert(it, make_pair(str, id));
        fprintf(stdout, "s %s\n", str.c_str());
        return id;
    }

    void addModule(backtrace_state* backtraceState, const size_t moduleIndex,
                   const uintptr_t addressStart, const uintptr_t addressEnd)
    {
        m_modules.emplace_back(addressStart, addressEnd, backtraceState, moduleIndex);
        m_modulesDirty = true;
    }

    void clearModules()
    {
        // TODO: optimize this, reuse modules that are still valid
        m_modules.clear();
        m_modulesDirty = true;
    }

    size_t addIp(const uintptr_t instructionPointer)
    {
        if (!instructionPointer) {
            return 0;
        }

        auto it = m_encounteredIps.find(instructionPointer);
        if (it != m_encounteredIps.end()) {
            return it->second;
        }

        const size_t ipId = m_encounteredIps.size() + 1;
        m_encounteredIps.insert(it, make_pair(instructionPointer, ipId));

        const auto ip = resolve(instructionPointer);
        fprintf(stdout, "i %zx %zx", instructionPointer, ip.moduleIndex);
        if (ip.functionIndex || ip.fileIndex) {
            fprintf(stdout, " %zx", ip.functionIndex);
            if (ip.fileIndex) {
                fprintf(stdout, " %zx %x", ip.fileIndex, ip.line);
            }
        }
        fputc('\n', stdout);
        return ipId;
    }

    /**
     * Prevent the same file from being initialized multiple times.
     * This drastically cuts the memory consumption down
     */
    backtrace_state* findBacktraceState(const string& fileName, uintptr_t addressStart)
    {
        if (boost::algorithm::starts_with(fileName, "linux-vdso.so")) {
            // prevent warning, since this will always fail
            return nullptr;
        }

        auto it = m_backtraceStates.find(fileName);
        if (it != m_backtraceStates.end()) {
            return it->second;
        }

        struct CallbackData {
            const string* fileName;
        };
        CallbackData data = {&fileName};

        auto errorHandler = [] (void *rawData, const char *msg, int errnum) {
            auto data = reinterpret_cast<const CallbackData*>(rawData);
            cerr << "Failed to create backtrace state for module " << *data->fileName << ": "
                 << msg << " / " << strerror(errnum) << " (error code " << errnum << ")" << endl;
        };

        auto state = backtrace_create_state(fileName.c_str(), /* we are single threaded, so: not thread safe */ false,
                                            errorHandler, &data);

        if (state) {
            const int descriptor = backtrace_open(fileName.c_str(), errorHandler, &data, nullptr);
            if (descriptor >= 1) {
                int foundSym = 0;
                int foundDwarf = 0;
                auto ret = elf_add(state, descriptor, addressStart, errorHandler, &data,
                                   &state->fileline_fn, &foundSym, &foundDwarf, false);
                if (ret && foundSym) {
                    state->syminfo_fn = &elf_syminfo;
                }
            }
        }

        m_backtraceStates.insert(it, make_pair(fileName, state));

        return state;
    }

private:
    vector<Module> m_modules;
    unordered_map<string, backtrace_state*> m_backtraceStates;
    bool m_modulesDirty = false;

    unordered_map<string, size_t> m_internedData;
    unordered_map<uintptr_t, size_t> m_encounteredIps;
};

}

int main(int /*argc*/, char** /*argv*/)
{
    // optimize: we only have a single thread
    ios_base::sync_with_stdio(false);
    __fsetlocking(stdout, FSETLOCKING_BYCALLER);
    __fsetlocking(stdin, FSETLOCKING_BYCALLER);

    AccumulatedTraceData data;

    LineReader reader;

    string exe;

    PointerMap ptrToIndex;
    uint64_t lastPtr = 0;
    AllocationInfoSet allocationInfos;

    uint64_t allocations = 0;
    uint64_t leakedAllocations = 0;
    uint64_t temporaryAllocations = 0;

    while (reader.getLine(cin)) {
        if (reader.mode() == 'x') {
            reader >> exe;
        } else if (reader.mode() == 'm') {
            string fileName;
            reader >> fileName;
            if (fileName == "-") {
                data.clearModules();
            } else {
                if (fileName == "x") {
                    fileName = exe;
                }
                const auto moduleIndex = data.intern(fileName);
                uintptr_t addressStart = 0;
                if (!(reader >> addressStart)) {
                    cerr << "failed to parse line: " << reader.line() << endl;
                    return 1;
                }
                auto state = data.findBacktraceState(fileName, addressStart);
                uintptr_t vAddr = 0;
                uintptr_t memSize = 0;
                while ((reader >> vAddr) && (reader >> memSize)) {
                    data.addModule(state, moduleIndex,
                                   addressStart + vAddr,
                                   addressStart + vAddr + memSize);
                }
            }
        } else if (reader.mode() == 't') {
            uintptr_t instructionPointer = 0;
            size_t parentIndex = 0;
            if (!(reader >> instructionPointer) || !(reader >> parentIndex)) {
                cerr << "failed to parse line: " << reader.line() << endl;
                return 1;
            }
            // ensure ip is encountered
            const auto ipId = data.addIp(instructionPointer);
            // trace point, map current output index to parent index
            fprintf(stdout, "t %zx %zx\n", ipId, parentIndex);
        } else if (reader.mode() == '+') {
            ++allocations;
            ++leakedAllocations;
            uint64_t size = 0;
            TraceIndex traceId;
            uint64_t ptr = 0;
            if (!(reader >> size) || !(reader >> traceId.index) || !(reader >> ptr)) {
                cerr << "failed to parse line: " << reader.line() << endl;
                continue;
            }

            AllocationIndex index;
            if (allocationInfos.add(size, traceId, &index)) {
                fprintf(stdout, "a %" PRIx64 " %x\n", size, traceId.index);
            }
            ptrToIndex.addPointer(ptr, index);
            lastPtr = ptr;
            fprintf(stdout, "+ %x\n", index.index);
        } else if (reader.mode() == '-') {
            --leakedAllocations;
            uint64_t ptr = 0;
            if (!(reader >> ptr)) {
                cerr << "failed to parse line: " << reader.line() << endl;
                continue;
            }
            bool temporary = lastPtr == ptr;
            lastPtr = 0;
            auto allocation = ptrToIndex.takePointer(ptr);
            if (!allocation.second) {
                continue;
            }
            fprintf(stdout, "- %x\n", allocation.first.index);
            if (temporary) {
                ++temporaryAllocations;
            }
        } else {
            fputs(reader.line().c_str(), stdout);
            fputc('\n', stdout);
        }
    }

    fprintf(stderr, "heaptrack stats:\n"
           "\tallocations:          \t%" PRIu64 "\n"
           "\tleaked allocations:   \t%" PRIu64 "\n"
           "\ttemporary allocations:\t%" PRIu64 "\n",
           allocations, leakedAllocations, temporaryAllocations);

    return 0;
}
<commit_msg>Fix use-after-free in usage of libbacktrace.<commit_after>/*
 * Copyright 2014 Milian Wolff <mail@milianw.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/**
 * @file heaptrack_interpret.cpp
 *
 * @brief Interpret raw heaptrack data and add Dwarf based debug information.
 */

#include <iostream>
#include <sstream>
#include <unordered_map>
#include <vector>
#include <tuple>
#include <algorithm>
#include <stdio_ext.h>
#include <inttypes.h>

#include <cxxabi.h>

#include <boost/algorithm/string/predicate.hpp>

#include "libbacktrace/backtrace.h"
#include "libbacktrace/internal.h"
#include "linereader.h"
#include "pointermap.h"

using namespace std;

namespace {

string demangle(const char* function)
{
    if (!function) {
        return {};
    } else if (function[0] != '_' || function[1] != 'Z') {
        return {function};
    }

    string ret;
    int status = 0;
    char* demangled = abi::__cxa_demangle(function, 0, 0, &status);
    if (demangled) {
        ret = demangled;
        free(demangled);
    }
    return ret;
}

struct AddressInformation
{
    string function;
    string file;
    int line = 0;
};

struct Module
{
    Module(uintptr_t addressStart, uintptr_t addressEnd, backtrace_state* backtraceState, size_t moduleIndex)
        : addressStart(addressStart)
        , addressEnd(addressEnd)
        , moduleIndex(moduleIndex)
        , backtraceState(backtraceState)
    {
    }

    AddressInformation resolveAddress(uintptr_t address) const
    {
        AddressInformation info;
        if (!backtraceState) {
            return info;
        }

        backtrace_pcinfo(backtraceState, address,
                         [] (void *data, uintptr_t /*addr*/, const char *file, int line, const char *function) -> int {
                            auto info = reinterpret_cast<AddressInformation*>(data);
                            info->function = demangle(function);
                            info->file = file ? file : "";
                            info->line = line;
                            return 0;
                         }, [] (void */*data*/, const char */*msg*/, int /*errnum*/) {}, &info);

        if (info.function.empty()) {
            backtrace_syminfo(backtraceState, address,
                              [] (void *data, uintptr_t /*pc*/, const char *symname, uintptr_t /*symval*/, uintptr_t /*symsize*/) {
                                if (symname) {
                                    reinterpret_cast<AddressInformation*>(data)->function = demangle(symname);
                                }
                              }, [] (void */*data*/, const char *msg, int errnum) {
                                cerr << "Module backtrace error (code " << errnum << "): " << msg << endl;
                              }, &info);
        }

        return info;
    }

    bool operator<(const Module& module) const
    {
        return tie(addressStart, addressEnd, moduleIndex)
             < tie(module.addressStart, module.addressEnd, module.moduleIndex);
    }

    bool operator!=(const Module& module) const
    {
        return tie(addressStart, addressEnd, moduleIndex)
            != tie(module.addressStart, module.addressEnd, module.moduleIndex);
    }

    uintptr_t addressStart;
    uintptr_t addressEnd;
    size_t moduleIndex;
    backtrace_state* backtraceState;
};

struct ResolvedIP
{
    size_t moduleIndex = 0;
    size_t fileIndex = 0;
    size_t functionIndex = 0;
    int line = 0;
};

struct AccumulatedTraceData
{
    AccumulatedTraceData()
    {
        m_modules.reserve(256);
        m_backtraceStates.reserve(64);
        m_internedData.reserve(4096);
        m_encounteredIps.reserve(32768);
    }

    ~AccumulatedTraceData()
    {
        fprintf(stdout, "# strings: %zu\n# ips: %zu\n",
                m_internedData.size(), m_encounteredIps.size());
    }

    ResolvedIP resolve(const uintptr_t ip)
    {
        if (m_modulesDirty) {
            // sort by addresses, required for binary search below
            sort(m_modules.begin(), m_modules.end());

#ifndef NDEBUG
            for (size_t i = 0; i < m_modules.size(); ++i) {
                const auto& m1 = m_modules[i];
                for (size_t j = i + 1; j < m_modules.size(); ++j) {
                    if (i == j) {
                        continue;
                    }
                    const auto& m2 = m_modules[j];
                    if ((m1.addressStart <= m2.addressStart && m1.addressEnd > m2.addressStart) ||
                        (m1.addressStart < m2.addressEnd && m1.addressEnd >= m2.addressEnd))
                    {
                        cerr << "OVERLAPPING MODULES: " << hex
                             << m1.moduleIndex << " (" << m1.addressStart << " to " << m1.addressEnd << ") and "
                             << m1.moduleIndex << " (" << m2.addressStart << " to " << m2.addressEnd << ")\n"
                             << dec;
                    } else if (m2.addressStart >= m1.addressEnd) {
                        break;
                    }
                }
            }
#endif

            m_modulesDirty = false;
        }

        ResolvedIP data;
        // find module for this instruction pointer
        auto module = lower_bound(m_modules.begin(), m_modules.end(), ip,
                                    [] (const Module& module, const uintptr_t ip) -> bool {
                                        return module.addressEnd < ip;
                                    });
        if (module != m_modules.end() && module->addressStart <= ip && module->addressEnd >= ip) {
            data.moduleIndex = module->moduleIndex;
            const auto info = module->resolveAddress(ip);
            data.fileIndex = intern(info.file);
            data.functionIndex = intern(info.function);
            data.line = info.line;
        }
        return data;
    }

    size_t intern(const string& str, const char** internedString = nullptr)
    {
        if (str.empty()) {
            return 0;
        }

        auto it = m_internedData.find(str);
        if (it != m_internedData.end()) {
            if (internedString) {
                *internedString = it->first.c_str();
            }
            return it->second;
        }
        const size_t id = m_internedData.size() + 1;
        it = m_internedData.insert(it, make_pair(str, id));
        if (internedString) {
            *internedString = it->first.c_str();
        }
        fprintf(stdout, "s %s\n", str.c_str());
        return id;
    }

    void addModule(backtrace_state* backtraceState, const size_t moduleIndex,
                   const uintptr_t addressStart, const uintptr_t addressEnd)
    {
        m_modules.emplace_back(addressStart, addressEnd, backtraceState, moduleIndex);
        m_modulesDirty = true;
    }

    void clearModules()
    {
        // TODO: optimize this, reuse modules that are still valid
        m_modules.clear();
        m_modulesDirty = true;
    }

    size_t addIp(const uintptr_t instructionPointer)
    {
        if (!instructionPointer) {
            return 0;
        }

        auto it = m_encounteredIps.find(instructionPointer);
        if (it != m_encounteredIps.end()) {
            return it->second;
        }

        const size_t ipId = m_encounteredIps.size() + 1;
        m_encounteredIps.insert(it, make_pair(instructionPointer, ipId));

        const auto ip = resolve(instructionPointer);
        fprintf(stdout, "i %zx %zx", instructionPointer, ip.moduleIndex);
        if (ip.functionIndex || ip.fileIndex) {
            fprintf(stdout, " %zx", ip.functionIndex);
            if (ip.fileIndex) {
                fprintf(stdout, " %zx %x", ip.fileIndex, ip.line);
            }
        }
        fputc('\n', stdout);
        return ipId;
    }

    /**
     * Prevent the same file from being initialized multiple times.
     * This drastically cuts the memory consumption down
     */
    backtrace_state* findBacktraceState(const char* fileName, uintptr_t addressStart)
    {
        if (boost::algorithm::starts_with(fileName, "linux-vdso.so")) {
            // prevent warning, since this will always fail
            return nullptr;
        }

        auto it = m_backtraceStates.find(fileName);
        if (it != m_backtraceStates.end()) {
            return it->second;
        }

        struct CallbackData {
            const char* fileName;
        };
        CallbackData data = {fileName};

        auto errorHandler = [] (void *rawData, const char *msg, int errnum) {
            auto data = reinterpret_cast<const CallbackData*>(rawData);
            cerr << "Failed to create backtrace state for module " << data->fileName << ": "
                 << msg << " / " << strerror(errnum) << " (error code " << errnum << ")" << endl;
        };

        auto state = backtrace_create_state(fileName, /* we are single threaded, so: not thread safe */ false,
                                            errorHandler, &data);

        if (state) {
            const int descriptor = backtrace_open(fileName, errorHandler, &data, nullptr);
            if (descriptor >= 1) {
                int foundSym = 0;
                int foundDwarf = 0;
                auto ret = elf_add(state, descriptor, addressStart, errorHandler, &data,
                                   &state->fileline_fn, &foundSym, &foundDwarf, false);
                if (ret && foundSym) {
                    state->syminfo_fn = &elf_syminfo;
                }
            }
        }

        m_backtraceStates.insert(it, make_pair(fileName, state));

        return state;
    }

private:
    vector<Module> m_modules;
    unordered_map<const char*, backtrace_state*> m_backtraceStates;
    bool m_modulesDirty = false;

    unordered_map<string, size_t> m_internedData;
    unordered_map<uintptr_t, size_t> m_encounteredIps;
};

}

int main(int /*argc*/, char** /*argv*/)
{
    // optimize: we only have a single thread
    ios_base::sync_with_stdio(false);
    __fsetlocking(stdout, FSETLOCKING_BYCALLER);
    __fsetlocking(stdin, FSETLOCKING_BYCALLER);

    AccumulatedTraceData data;

    LineReader reader;

    string exe;

    PointerMap ptrToIndex;
    uint64_t lastPtr = 0;
    AllocationInfoSet allocationInfos;

    uint64_t allocations = 0;
    uint64_t leakedAllocations = 0;
    uint64_t temporaryAllocations = 0;

    while (reader.getLine(cin)) {
        if (reader.mode() == 'x') {
            reader >> exe;
        } else if (reader.mode() == 'm') {
            string fileName;
            reader >> fileName;
            if (fileName == "-") {
                data.clearModules();
            } else {
                if (fileName == "x") {
                    fileName = exe;
                }
                const char *internedString = nullptr;
                const auto moduleIndex = data.intern(fileName, &internedString);
                uintptr_t addressStart = 0;
                if (!(reader >> addressStart)) {
                    cerr << "failed to parse line: " << reader.line() << endl;
                    return 1;
                }
                auto state = data.findBacktraceState(internedString, addressStart);
                uintptr_t vAddr = 0;
                uintptr_t memSize = 0;
                while ((reader >> vAddr) && (reader >> memSize)) {
                    data.addModule(state, moduleIndex,
                                   addressStart + vAddr,
                                   addressStart + vAddr + memSize);
                }
            }
        } else if (reader.mode() == 't') {
            uintptr_t instructionPointer = 0;
            size_t parentIndex = 0;
            if (!(reader >> instructionPointer) || !(reader >> parentIndex)) {
                cerr << "failed to parse line: " << reader.line() << endl;
                return 1;
            }
            // ensure ip is encountered
            const auto ipId = data.addIp(instructionPointer);
            // trace point, map current output index to parent index
            fprintf(stdout, "t %zx %zx\n", ipId, parentIndex);
        } else if (reader.mode() == '+') {
            ++allocations;
            ++leakedAllocations;
            uint64_t size = 0;
            TraceIndex traceId;
            uint64_t ptr = 0;
            if (!(reader >> size) || !(reader >> traceId.index) || !(reader >> ptr)) {
                cerr << "failed to parse line: " << reader.line() << endl;
                continue;
            }

            AllocationIndex index;
            if (allocationInfos.add(size, traceId, &index)) {
                fprintf(stdout, "a %" PRIx64 " %x\n", size, traceId.index);
            }
            ptrToIndex.addPointer(ptr, index);
            lastPtr = ptr;
            fprintf(stdout, "+ %x\n", index.index);
        } else if (reader.mode() == '-') {
            --leakedAllocations;
            uint64_t ptr = 0;
            if (!(reader >> ptr)) {
                cerr << "failed to parse line: " << reader.line() << endl;
                continue;
            }
            bool temporary = lastPtr == ptr;
            lastPtr = 0;
            auto allocation = ptrToIndex.takePointer(ptr);
            if (!allocation.second) {
                continue;
            }
            fprintf(stdout, "- %x\n", allocation.first.index);
            if (temporary) {
                ++temporaryAllocations;
            }
        } else {
            fputs(reader.line().c_str(), stdout);
            fputc('\n', stdout);
        }
    }

    fprintf(stderr, "heaptrack stats:\n"
           "\tallocations:          \t%" PRIu64 "\n"
           "\tleaked allocations:   \t%" PRIu64 "\n"
           "\ttemporary allocations:\t%" PRIu64 "\n",
           allocations, leakedAllocations, temporaryAllocations);

    return 0;
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2007 The Hewlett-Packard Development Company
 * All rights reserved.
 *
 * Redistribution and use of this software in source and binary forms,
 * with or without modification, are permitted provided that the
 * following conditions are met:
 *
 * The software must be used only for Non-Commercial Use which means any
 * use which is NOT directed to receiving any direct monetary
 * compensation for, or commercial advantage from such use.  Illustrative
 * examples of non-commercial use are academic research, personal study,
 * teaching, education and corporate research & development.
 * Illustrative examples of commercial use are distributing products for
 * commercial advantage and providing services using the software for
 * commercial advantage.
 *
 * If you wish to use this software or functionality therein that may be
 * covered by patents for commercial use, please contact:
 *     Director of Intellectual Property Licensing
 *     Office of Strategy and Technology
 *     Hewlett-Packard Company
 *     1501 Page Mill Road
 *     Palo Alto, California  94304
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.  Redistributions
 * in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.  Neither the name of
 * the COPYRIGHT HOLDER(s), HEWLETT-PACKARD COMPANY, nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.  No right of
 * sublicense is granted herewith.  Derivatives of the software and
 * output created using the software may be prepared, but only for
 * Non-Commercial Uses.  Derivatives of the software may be shared with
 * others provided: (i) the others agree to abide by the list of
 * conditions herein which includes the Non-Commercial Use restrictions;
 * and (ii) such Derivatives of the software include the above copyright
 * notice to acknowledge the contribution from this software where
 * applicable, this list of conditions and the disclaimer below.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Gabe Black
 */

#ifndef __ARCH_X86_INTERRUPTS_HH__
#define __ARCH_X86_INTERRUPTS_HH__

#include "arch/x86/apicregs.hh"
#include "arch/x86/faults.hh"
#include "arch/x86/intmessage.hh"
#include "base/bitfield.hh"
#include "cpu/thread_context.hh"
#include "dev/io_device.hh"
#include "dev/x86/intdev.hh"
#include "params/X86LocalApic.hh"
#include "sim/eventq.hh"

class ThreadContext;
class BaseCPU;

namespace X86ISA {

class Interrupts : public BasicPioDevice, IntDev
{
  protected:
    // Storage for the APIC registers
    uint32_t regs[NUM_APIC_REGS];

    BitUnion32(LVTEntry)
        Bitfield<7, 0> vector;
        Bitfield<10, 8> deliveryMode;
        Bitfield<12> status;
        Bitfield<13> polarity;
        Bitfield<14> remoteIRR;
        Bitfield<15> trigger;
        Bitfield<16> masked;
        Bitfield<17> periodic;
    EndBitUnion(LVTEntry)

    /*
     * Timing related stuff.
     */
    Tick latency;
    Tick clock;

    class ApicTimerEvent : public Event
    {
      private:
        Interrupts *localApic;
      public:
        ApicTimerEvent(Interrupts *_localApic) :
            Event(), localApic(_localApic)
        {}

        void process()
        {
            assert(localApic);
            if (localApic->triggerTimerInterrupt()) {
                localApic->setReg(APIC_INITIAL_COUNT,
                        localApic->readReg(APIC_INITIAL_COUNT));
            }
        }
    };

    ApicTimerEvent apicTimerEvent;

    /*
     * A set of variables to keep track of interrupts that don't go through
     * the IRR.
     */
    bool pendingSmi;
    uint8_t smiVector;
    bool pendingNmi;
    uint8_t nmiVector;
    bool pendingExtInt;
    uint8_t extIntVector;
    bool pendingInit;
    uint8_t initVector;
    bool pendingStartup;
    uint8_t startupVector;
    bool startedUp;

    // This is a quick check whether any of the above (except ExtInt) are set.
    bool pendingUnmaskableInt;

    // A count of how many IPIs are in flight.
    int pendingIPIs;

    /*
     * IRR and ISR maintenance.
     */
    uint8_t IRRV;
    uint8_t ISRV;

    int
    findRegArrayMSB(ApicRegIndex base)
    {
        int offset = 7;
        do {
            if (regs[base + offset] != 0) {
                return offset * 32 + findMsbSet(regs[base + offset]);
            }
        } while (offset--);
        return 0;
    }

    void
    updateIRRV()
    {
        IRRV = findRegArrayMSB(APIC_INTERRUPT_REQUEST_BASE);
    }

    void
    updateISRV()
    {
        ISRV = findRegArrayMSB(APIC_IN_SERVICE_BASE);
    }

    void
    setRegArrayBit(ApicRegIndex base, uint8_t vector)
    {
        regs[base + (vector % 32)] |= (1 << (vector >> 5));
    }

    void
    clearRegArrayBit(ApicRegIndex base, uint8_t vector)
    {
        regs[base + (vector % 32)] &= ~(1 << (vector >> 5));
    }

    bool
    getRegArrayBit(ApicRegIndex base, uint8_t vector)
    {
        return bits(regs[base + (vector % 32)], vector >> 5);
    }

    void requestInterrupt(uint8_t vector, uint8_t deliveryMode, bool level);

    BaseCPU *cpu;

  public:
    /*
     * Params stuff.
     */
    typedef X86LocalApicParams Params;

    void setCPU(BaseCPU * newCPU);

    void
    setClock(Tick newClock)
    {
        clock = newClock;
    }

    const Params *
    params() const
    {
        return dynamic_cast<const Params *>(_params);
    }

    /*
     * Functions to interact with the interrupt port from IntDev.
     */
    Tick read(PacketPtr pkt);
    Tick write(PacketPtr pkt);
    Tick recvMessage(PacketPtr pkt);
    Tick recvResponse(PacketPtr pkt);

    bool
    triggerTimerInterrupt()
    {
        LVTEntry entry = regs[APIC_LVT_TIMER];
        if (!entry.masked)
            requestInterrupt(entry.vector, entry.deliveryMode, entry.trigger);
        return entry.periodic;
    }

    void addressRanges(AddrRangeList &range_list);
    void getIntAddrRange(AddrRangeList &range_list);

    Port *getPort(const std::string &if_name, int idx = -1)
    {
        if (if_name == "int_port")
            return intPort;
        return BasicPioDevice::getPort(if_name, idx);
    }

    /*
     * Functions to access and manipulate the APIC's registers.
     */

    uint32_t readReg(ApicRegIndex miscReg);
    void setReg(ApicRegIndex reg, uint32_t val);
    void
    setRegNoEffect(ApicRegIndex reg, uint32_t val)
    {
        regs[reg] = val;
    }

    /*
     * Constructor.
     */

    Interrupts(Params * p);

    /*
     * Functions for retrieving interrupts for the CPU to handle.
     */

    bool checkInterrupts(ThreadContext *tc) const;
    Fault getInterrupt(ThreadContext *tc);
    void updateIntrInfo(ThreadContext *tc);

    /*
     * Serialization.
     */

    void
    serialize(std::ostream &os)
    {
        panic("Interrupts::serialize unimplemented!\n");
    }

    void
    unserialize(Checkpoint *cp, const std::string &section)
    {
        panic("Interrupts::unserialize unimplemented!\n");
    }

    /*
     * Old functions needed for compatability but which will be phased out
     * eventually.
     */
    void
    post(int int_num, int index)
    {
        panic("Interrupts::post unimplemented!\n");
    }

    void
    clear(int int_num, int index)
    {
        panic("Interrupts::clear unimplemented!\n");
    }

    void
    clearAll()
    {
        panic("Interrupts::clearAll unimplemented!\n");
    }
};

} // namespace X86ISA

#endif // __ARCH_X86_INTERRUPTS_HH__
<commit_msg>X86: Fix the functions that manipulate large bit arrays in the local APIC.<commit_after>/*
 * Copyright (c) 2007 The Hewlett-Packard Development Company
 * All rights reserved.
 *
 * Redistribution and use of this software in source and binary forms,
 * with or without modification, are permitted provided that the
 * following conditions are met:
 *
 * The software must be used only for Non-Commercial Use which means any
 * use which is NOT directed to receiving any direct monetary
 * compensation for, or commercial advantage from such use.  Illustrative
 * examples of non-commercial use are academic research, personal study,
 * teaching, education and corporate research & development.
 * Illustrative examples of commercial use are distributing products for
 * commercial advantage and providing services using the software for
 * commercial advantage.
 *
 * If you wish to use this software or functionality therein that may be
 * covered by patents for commercial use, please contact:
 *     Director of Intellectual Property Licensing
 *     Office of Strategy and Technology
 *     Hewlett-Packard Company
 *     1501 Page Mill Road
 *     Palo Alto, California  94304
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.  Redistributions
 * in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.  Neither the name of
 * the COPYRIGHT HOLDER(s), HEWLETT-PACKARD COMPANY, nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.  No right of
 * sublicense is granted herewith.  Derivatives of the software and
 * output created using the software may be prepared, but only for
 * Non-Commercial Uses.  Derivatives of the software may be shared with
 * others provided: (i) the others agree to abide by the list of
 * conditions herein which includes the Non-Commercial Use restrictions;
 * and (ii) such Derivatives of the software include the above copyright
 * notice to acknowledge the contribution from this software where
 * applicable, this list of conditions and the disclaimer below.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Gabe Black
 */

#ifndef __ARCH_X86_INTERRUPTS_HH__
#define __ARCH_X86_INTERRUPTS_HH__

#include "arch/x86/apicregs.hh"
#include "arch/x86/faults.hh"
#include "arch/x86/intmessage.hh"
#include "base/bitfield.hh"
#include "cpu/thread_context.hh"
#include "dev/io_device.hh"
#include "dev/x86/intdev.hh"
#include "params/X86LocalApic.hh"
#include "sim/eventq.hh"

class ThreadContext;
class BaseCPU;

namespace X86ISA {

class Interrupts : public BasicPioDevice, IntDev
{
  protected:
    // Storage for the APIC registers
    uint32_t regs[NUM_APIC_REGS];

    BitUnion32(LVTEntry)
        Bitfield<7, 0> vector;
        Bitfield<10, 8> deliveryMode;
        Bitfield<12> status;
        Bitfield<13> polarity;
        Bitfield<14> remoteIRR;
        Bitfield<15> trigger;
        Bitfield<16> masked;
        Bitfield<17> periodic;
    EndBitUnion(LVTEntry)

    /*
     * Timing related stuff.
     */
    Tick latency;
    Tick clock;

    class ApicTimerEvent : public Event
    {
      private:
        Interrupts *localApic;
      public:
        ApicTimerEvent(Interrupts *_localApic) :
            Event(), localApic(_localApic)
        {}

        void process()
        {
            assert(localApic);
            if (localApic->triggerTimerInterrupt()) {
                localApic->setReg(APIC_INITIAL_COUNT,
                        localApic->readReg(APIC_INITIAL_COUNT));
            }
        }
    };

    ApicTimerEvent apicTimerEvent;

    /*
     * A set of variables to keep track of interrupts that don't go through
     * the IRR.
     */
    bool pendingSmi;
    uint8_t smiVector;
    bool pendingNmi;
    uint8_t nmiVector;
    bool pendingExtInt;
    uint8_t extIntVector;
    bool pendingInit;
    uint8_t initVector;
    bool pendingStartup;
    uint8_t startupVector;
    bool startedUp;

    // This is a quick check whether any of the above (except ExtInt) are set.
    bool pendingUnmaskableInt;

    // A count of how many IPIs are in flight.
    int pendingIPIs;

    /*
     * IRR and ISR maintenance.
     */
    uint8_t IRRV;
    uint8_t ISRV;

    int
    findRegArrayMSB(ApicRegIndex base)
    {
        int offset = 7;
        do {
            if (regs[base + offset] != 0) {
                return offset * 32 + findMsbSet(regs[base + offset]);
            }
        } while (offset--);
        return 0;
    }

    void
    updateIRRV()
    {
        IRRV = findRegArrayMSB(APIC_INTERRUPT_REQUEST_BASE);
    }

    void
    updateISRV()
    {
        ISRV = findRegArrayMSB(APIC_IN_SERVICE_BASE);
    }

    void
    setRegArrayBit(ApicRegIndex base, uint8_t vector)
    {
        regs[base + (vector / 32)] |= (1 << (vector % 32));
    }

    void
    clearRegArrayBit(ApicRegIndex base, uint8_t vector)
    {
        regs[base + (vector / 32)] &= ~(1 << (vector % 32));
    }

    bool
    getRegArrayBit(ApicRegIndex base, uint8_t vector)
    {
        return bits(regs[base + (vector / 32)], vector % 5);
    }

    void requestInterrupt(uint8_t vector, uint8_t deliveryMode, bool level);

    BaseCPU *cpu;

  public:
    /*
     * Params stuff.
     */
    typedef X86LocalApicParams Params;

    void setCPU(BaseCPU * newCPU);

    void
    setClock(Tick newClock)
    {
        clock = newClock;
    }

    const Params *
    params() const
    {
        return dynamic_cast<const Params *>(_params);
    }

    /*
     * Functions to interact with the interrupt port from IntDev.
     */
    Tick read(PacketPtr pkt);
    Tick write(PacketPtr pkt);
    Tick recvMessage(PacketPtr pkt);
    Tick recvResponse(PacketPtr pkt);

    bool
    triggerTimerInterrupt()
    {
        LVTEntry entry = regs[APIC_LVT_TIMER];
        if (!entry.masked)
            requestInterrupt(entry.vector, entry.deliveryMode, entry.trigger);
        return entry.periodic;
    }

    void addressRanges(AddrRangeList &range_list);
    void getIntAddrRange(AddrRangeList &range_list);

    Port *getPort(const std::string &if_name, int idx = -1)
    {
        if (if_name == "int_port")
            return intPort;
        return BasicPioDevice::getPort(if_name, idx);
    }

    /*
     * Functions to access and manipulate the APIC's registers.
     */

    uint32_t readReg(ApicRegIndex miscReg);
    void setReg(ApicRegIndex reg, uint32_t val);
    void
    setRegNoEffect(ApicRegIndex reg, uint32_t val)
    {
        regs[reg] = val;
    }

    /*
     * Constructor.
     */

    Interrupts(Params * p);

    /*
     * Functions for retrieving interrupts for the CPU to handle.
     */

    bool checkInterrupts(ThreadContext *tc) const;
    Fault getInterrupt(ThreadContext *tc);
    void updateIntrInfo(ThreadContext *tc);

    /*
     * Serialization.
     */

    void
    serialize(std::ostream &os)
    {
        panic("Interrupts::serialize unimplemented!\n");
    }

    void
    unserialize(Checkpoint *cp, const std::string &section)
    {
        panic("Interrupts::unserialize unimplemented!\n");
    }

    /*
     * Old functions needed for compatability but which will be phased out
     * eventually.
     */
    void
    post(int int_num, int index)
    {
        panic("Interrupts::post unimplemented!\n");
    }

    void
    clear(int int_num, int index)
    {
        panic("Interrupts::clear unimplemented!\n");
    }

    void
    clearAll()
    {
        panic("Interrupts::clearAll unimplemented!\n");
    }
};

} // namespace X86ISA

#endif // __ARCH_X86_INTERRUPTS_HH__
<|endoftext|>


<commit_before>
            class original_market_engine
            {
               public:
                  original_market_engine( pending_chain_state_ptr ps, chain_database_impl& cdi )
                  :_pending_state(ps),_db_impl(cdi)
                  {
                      _pending_state = std::make_shared<pending_chain_state>( ps );
                      _prior_state = ps;
                  }

                  void execute( asset_id_type quote_id, asset_id_type base_id, const fc::time_point_sec& timestamp )
                  {
                     try {
                         _quote_id = quote_id;
                         _base_id = base_id;
                         auto quote_asset = _pending_state->get_asset_record( _quote_id );

                         // DISABLE MARKET ISSUED ASSETS
                         if( quote_asset->is_market_issued() )
                            return; // don't execute anything.

                         // the order book is soreted from low to high price, so to get the last item (highest bid), we need to go to the first item in the
                         // next market class and then back up one
                         auto next_pair  = base_id+1 == quote_id ? price( 0, quote_id+1, 0) : price( 0, quote_id, base_id+1 );
                         _bid_itr        = _db_impl._bid_db.lower_bound( market_index_key( next_pair ) );
                         _ask_itr        = _db_impl._ask_db.lower_bound( market_index_key( price( 0, quote_id, base_id) ) );
                         _short_itr      = _db_impl._short_db.lower_bound( market_index_key( next_pair ) );
                         _collateral_itr      = _db_impl._collateral_db.lower_bound( market_index_key( next_pair ) );

                         if( !_ask_itr.valid() )
                         {
                            wlog( "ask iter invalid..." );
                            _ask_itr = _db_impl._ask_db.begin();
                         }

                         if( _short_itr.valid() ) --_short_itr;
                         else _short_itr = _db_impl._short_db.last();

                         if( _bid_itr.valid() )   --_bid_itr;
                         else _bid_itr = _db_impl._bid_db.last();

                         if( _collateral_itr.valid() )   --_collateral_itr;
                         else _collateral_itr = _db_impl._collateral_db.last();

                         asset consumed_bid_depth(0,base_id);
                         asset consumed_ask_depth(0,base_id);

                         asset usd_fees_collected(0,quote_id);
                         asset trading_volume(0, base_id);

                         omarket_status market_stat = _pending_state->get_market_status( _quote_id, _base_id );
                         if( !market_stat.valid() )
                         {
                            if( quote_asset->is_market_issued() ) FC_CAPTURE_AND_THROW( insufficient_depth, (market_stat) );
                            FC_ASSERT( market_stat.valid() );
                         }

                         while( get_next_bid() && get_next_ask() )
                         {
                            idump( (_current_bid)(_current_ask) );
                            price ask_price = _current_ask->get_price();
                            // this works for bids, asks, and shorts.... but in the case of a cover
                            // the current ask can go lower than the call price in order to match
                            // the bid....
                            if( _current_ask->type == cover_order )
                            {
                               ask_price = std::min( _current_bid->get_price(), _current_ask->get_highest_cover_price() );
                            }

                            if( _current_bid->get_price() < ask_price )
                               break;

                            if( quote_asset->is_market_issued() )
                            {
                               if( !market_stat ||
                                   market_stat->ask_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2 ||
                                   market_stat->bid_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2
                                 )
                                 FC_CAPTURE_AND_THROW( insufficient_depth, (market_stat) );
                            }

                            auto quantity = std::min( _current_bid->get_quantity(), _current_ask->get_quantity() );

                            auto usd_paid_by_bid     = quantity * _current_bid->get_price();
                            auto usd_received_by_ask = quantity * _current_ask->get_price();
                            auto xts_paid_by_ask     = quantity;
                            auto xts_received_by_bid = quantity;

                            consumed_bid_depth += quantity;
                            consumed_ask_depth += quantity;

                            if( _current_bid->type == short_order )
                            {
                               usd_paid_by_bid = usd_received_by_ask;
                            }

                            if( _current_ask->type == cover_order )
                            {
                                usd_received_by_ask = usd_paid_by_bid;
                            }

                            FC_ASSERT( usd_paid_by_bid.amount >= 0 );
                            FC_ASSERT( xts_paid_by_ask.amount >= 0 );
                            FC_ASSERT( usd_received_by_ask.amount >= 0 );
                            FC_ASSERT( xts_received_by_bid.amount >= 0 );
                            FC_ASSERT( usd_paid_by_bid >= usd_received_by_ask );
                            FC_ASSERT( xts_paid_by_ask >= xts_received_by_bid );

                            // sanity check to keep supply from growing without bound
                            FC_ASSERT( usd_paid_by_bid < asset(quote_asset->maximum_share_supply,quote_id), "", ("usd_paid_by_bid",usd_paid_by_bid)("asset",quote_asset)  )

                            usd_fees_collected += usd_paid_by_bid - usd_received_by_ask;
                            idump( (usd_fees_collected)(xts_paid_by_ask)(xts_received_by_bid)(quantity) );

                            market_transaction mtrx;
                            mtrx.bid_owner       = _current_bid->get_owner();
                            mtrx.ask_owner       = _current_ask->get_owner();
                            mtrx.bid_price       = _current_bid->get_price();
                            mtrx.ask_price       = ask_price;
                            mtrx.bid_paid        = usd_paid_by_bid;
                            mtrx.bid_received    = xts_received_by_bid;
                            mtrx.ask_paid        = xts_paid_by_ask;
                            mtrx.ask_received    = usd_received_by_ask;
                            mtrx.bid_type        = _current_bid->type;
                            mtrx.fees_collected  = xts_paid_by_ask - xts_received_by_bid;

                            _market_transactions.push_back(mtrx);
                            trading_volume += mtrx.bid_received;

                            market_stat->ask_depth -= xts_paid_by_ask.amount;
                            if( _current_ask->type == ask_order )
                            {
                               /* rounding errors on price cause this not to go to 0 in some cases */
                               if( quantity == _current_ask->get_quantity() )
                                  _current_ask->state.balance = 0;
                               else
                                  _current_ask->state.balance -= xts_paid_by_ask.amount;

                               FC_ASSERT( _current_ask->state.balance >= 0 );

                               auto ask_balance_address = withdraw_condition( withdraw_with_signature(_current_ask->get_owner()), quote_id ).get_address();
                               auto ask_payout = _pending_state->get_balance_record( ask_balance_address );
                               if( !ask_payout )
                                  ask_payout = balance_record( _current_ask->get_owner(), asset(0,quote_id), 0 );
                               ask_payout->balance += usd_received_by_ask.amount;
                               ask_payout->last_update = _pending_state->now();

                               _pending_state->store_balance_record( *ask_payout );
                               _pending_state->store_ask_record( _current_ask->market_index, _current_ask->state );
                            }
                            else if( _current_ask->type == cover_order )
                            {
                               elog( "MATCHING COVER ORDER recv_usd: ${usd}  paid_collat: ${c}",
                                     ("usd",usd_received_by_ask)("c",xts_paid_by_ask) );
                               wlog( "current ask: ${c}", ("c",_current_ask) );
                               // we are in the margin call range...
                               _current_ask->state.balance  -= usd_received_by_ask.amount;
                               *(_current_ask->collateral)  -= xts_paid_by_ask.amount;

                               FC_ASSERT( _current_ask->state.balance >= 0 );
                               FC_ASSERT( *_current_ask->collateral >= 0 );

                               if( _current_ask->state.balance == 0 ) // no more USD left
                               { // send collateral home to mommy & daddy
                                     wlog( "            collateral balance is now 0!" );
                                     auto ask_balance_address = withdraw_condition(
                                                                       withdraw_with_signature(_current_ask->get_owner()),
                                                                       base_id ).get_address();

                                     auto ask_payout = _pending_state->get_balance_record( ask_balance_address );
                                     if( !ask_payout )
                                        ask_payout = balance_record( _current_ask->get_owner(), asset(0,base_id), 0 );
                                     ask_payout->balance += (*_current_ask->collateral);
                                     ask_payout->last_update = _pending_state->now();

                                     _pending_state->store_balance_record( *ask_payout );
                                     _current_ask->collateral = 0;

                               }
                               wlog( "storing collateral ${c}", ("c",_current_ask) );
                               _pending_state->store_collateral_record( _current_ask->market_index,
                                                                        collateral_record( *_current_ask->collateral,
                                                                                _current_ask->state.balance ) );
                            }

                            if( _current_bid->type == bid_order )
                            {
                               _current_bid->state.balance -= usd_paid_by_bid.amount;
                               FC_ASSERT( _current_bid->state.balance >= 0 );

                               auto bid_payout = _pending_state->get_balance_record(
                                                         withdraw_condition( withdraw_with_signature(_current_bid->get_owner()), base_id ).get_address() );
                               if( !bid_payout )
                                  bid_payout = balance_record( _current_bid->get_owner(), asset(0,base_id), 0 );
                               bid_payout->balance += xts_received_by_bid.amount;
                               bid_payout->last_update = _pending_state->now();
                               _pending_state->store_balance_record( *bid_payout );
                               _pending_state->store_bid_record( _current_bid->market_index, _current_bid->state );

                            }
                            else if( _current_bid->type == short_order )
                            {
                               market_stat->bid_depth -= xts_received_by_bid.amount;

                               // TODO: what if the amount paid is 0 for bid and ask due to rounding errors,
                               // make sure this doesn't put us in an infinite loop.
                               if( quantity == _current_bid->get_quantity() )
                                  _current_bid->state.balance = 0;
                               else
                                  _current_bid->state.balance -= xts_received_by_bid.amount;
                               FC_ASSERT( _current_bid->state.balance >= 0 );

                               auto collateral = (xts_paid_by_ask + xts_received_by_bid).amount;
                               auto cover_price = usd_received_by_ask / asset( (3*collateral)/4, base_id );

                               market_index_key cover_index( cover_price, _current_ask->get_owner() );
                               auto ocover_record = _pending_state->get_collateral_record( cover_index );

                               if( NOT ocover_record )
                                  ocover_record = collateral_record();

                               ocover_record->collateral_balance += collateral;
                               ocover_record->payoff_balance += usd_received_by_ask.amount;
                               FC_ASSERT( ocover_record->payoff_balance >= 0 );
                               FC_ASSERT( ocover_record->collateral_balance >= 0 );
                               _pending_state->store_collateral_record( cover_index, *ocover_record );

                               _pending_state->store_short_record( _current_bid->market_index, _current_bid->state );
                            }
                         } // while bid && ask

                         if( quote_asset->is_market_issued() )
                         {
                            if( !market_stat ||
                                market_stat->ask_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2 ||
                                market_stat->bid_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2
                              )
                              FC_CAPTURE_AND_THROW( insufficient_depth, (market_stat) );
                         }
                         _pending_state->store_market_status( *market_stat );

                         if( trading_volume.amount > 0 && get_next_bid() && get_next_ask() )
                         {
                           market_history_key key(quote_id, base_id, market_history_key::each_block, _db_impl._head_block_header.timestamp);
                           market_history_record new_record(_current_bid->get_price(), _current_ask->get_price(), trading_volume.amount);
                           //LevelDB iterators are dumb and don't support proper past-the-end semantics.
                           auto last_key_itr = _db_impl._market_history_db.lower_bound(key);
                           if( !last_key_itr.valid() )
                             last_key_itr = _db_impl._market_history_db.last();
                           else
                             --last_key_itr;

                           key.timestamp = timestamp;

                           //Unless the previous record for this market is the same as ours...
                           if( (!(last_key_itr.valid()
                               && last_key_itr.key().quote_id == quote_id
                               && last_key_itr.key().base_id == base_id
                               && last_key_itr.key().granularity == market_history_key::each_block
                               && last_key_itr.value() == new_record)) )
                           {
                             //...add a new entry to the history table.
                             _pending_state->market_history[key] = new_record;
                           }

                           fc::time_point_sec start_of_this_hour = timestamp - (timestamp.sec_since_epoch() % (60*60));
                           market_history_key old_key(quote_id, base_id, market_history_key::each_hour, start_of_this_hour);
                           if( auto opt = _db_impl._market_history_db.fetch_optional(old_key) )
                           {
                             auto old_record = *opt;
                             old_record.volume += new_record.volume;
                             if( new_record.highest_bid > old_record.highest_bid || new_record.lowest_ask < old_record.lowest_ask )
                             {
                               old_record.highest_bid = std::max(new_record.highest_bid, old_record.highest_bid);
                               old_record.lowest_ask = std::min(new_record.lowest_ask, old_record.lowest_ask);
                               _pending_state->market_history[old_key] = old_record;
                             }
                           }
                           else
                             _pending_state->market_history[old_key] = new_record;

                           fc::time_point_sec start_of_this_day = timestamp - (timestamp.sec_since_epoch() % (60*60*24));
                           old_key = market_history_key(quote_id, base_id, market_history_key::each_day, start_of_this_day);
                           if( auto opt = _db_impl._market_history_db.fetch_optional(old_key) )
                           {
                             auto old_record = *opt;
                             old_record.volume += new_record.volume;
                             if( new_record.highest_bid > old_record.highest_bid || new_record.lowest_ask < old_record.lowest_ask )
                             {
                               old_record.highest_bid = std::max(new_record.highest_bid, old_record.highest_bid);
                               old_record.lowest_ask = std::min(new_record.lowest_ask, old_record.lowest_ask);
                               _pending_state->market_history[old_key] = old_record;
                             }
                           }
                           else
                             _pending_state->market_history[old_key] = new_record;
                         }

                         auto market_state = _pending_state->get_market_status( quote_id, base_id );
                          if( !market_state )
                             market_state = market_status( quote_id, base_id, 0, 0 );
                         market_state->last_error.reset();
                         _pending_state->store_market_status( *market_state );

                         wlog( "done matching orders" );
                         _pending_state->apply_changes();
                    }
                    catch( const fc::exception& e )
                    {
                       wlog( "error executing market ${quote} / ${base}\n ${e}", ("quote",quote_id)("base",base_id)("e",e.to_detail_string()) );
                       auto market_state = _prior_state->get_market_status( quote_id, base_id );
                       if( !market_state )
                          market_state = market_status( quote_id, base_id, 0, 0 );
                       market_state->last_error = e;
                       _prior_state->store_market_status( *market_state );
                    }
                  } // execute(...)

                  bool get_next_bid()
                  { try {
                     if( _current_bid && _current_bid->get_quantity().amount > 0 )
                        return _current_bid.valid();

                     _current_bid.reset();
                     if( _bid_itr.valid() )
                     {
                        auto bid = market_order( bid_order, _bid_itr.key(), _bid_itr.value() );
                        if( bid.get_price().quote_asset_id == _quote_id &&
                            bid.get_price().base_asset_id == _base_id )
                        {
                            _current_bid = bid;
                        }
                     }

                     if( _short_itr.valid() )
                     {
                        auto bid = market_order( short_order, _short_itr.key(), _short_itr.value() );
                        wlog( "SHORT ITER VALID: ${o}", ("o",bid) );
                        if( bid.get_price().quote_asset_id == _quote_id &&
                            bid.get_price().base_asset_id == _base_id )
                        {
                            if( !_current_bid || _current_bid->get_price() < bid.get_price() )
                            {
                               --_short_itr;
                               _current_bid = bid;
                               ilog( "returning ${v}", ("v",_current_bid.valid() ) );
                               return _current_bid.valid();
                            }
                        }
                     }
                     else
                     {
                        wlog( "           No Shorts         ****   " );
                     }
                     if( _bid_itr.valid() ) --_bid_itr;
                     return _current_bid.valid();
                  } FC_CAPTURE_AND_RETHROW() }

                  bool get_next_ask()
                  { try {
                     if( _current_ask && _current_ask->state.balance > 0 )
                     {
                        wlog( "current ask" );
                        return _current_ask.valid();
                     }
                     _current_ask.reset();

                     /**
                      *  Margin calls take priority over all other ask orders
                      */
                     while( _current_bid && _collateral_itr.valid() )
                     {
                        auto cover_ask = market_order( cover_order,
                                                 _collateral_itr.key(),
                                                 order_record(_collateral_itr.value().payoff_balance),
                                                 _collateral_itr.value().collateral_balance  );

                        if( cover_ask.get_price().quote_asset_id == _quote_id &&
                            cover_ask.get_price().base_asset_id == _base_id )
                        {
                            if( _current_bid->get_price() < cover_ask.get_highest_cover_price()  )
                            {
                               // cover position has been blown out, current bid is not able to
                               // cover the position, so it will sit until the price recovers
                               // enough to fill it.
                               //
                               // The idea here is that the longs have agreed to a maximum
                               // protection equal to the collateral.  If they would like to
                               // sell their USD for XTS this is the best price the short is
                               // obligated to offer.
                               FC_CAPTURE_AND_THROW( insufficient_collateral, (_current_bid)(cover_ask)(cover_ask.get_highest_cover_price()));
                               --_collateral_itr;
                               continue;
                            }
                            // max bid must be greater than call price
                            if( _current_bid->get_price() < cover_ask.get_price() )
                            {
                             //  if( _current_ask->get_price() > cover_ask.get_price() )
                               {
                                  _current_ask = cover_ask;
                                  _current_payoff_balance = _collateral_itr.value().payoff_balance;
                                  --_collateral_itr;
                                  return _current_ask.valid();
                               }
                            }
                        }
                        break;
                     }

                     if( _ask_itr.valid() )
                     {
                        auto ask = market_order( ask_order, _ask_itr.key(), _ask_itr.value() );
                        wlog( "ASK ITER VALID: ${o}", ("o",ask) );
                        if( ask.get_price().quote_asset_id == _quote_id &&
                            ask.get_price().base_asset_id == _base_id )
                        {
                            _current_ask = ask;
                        }
                        ++_ask_itr;
                        return true;
                     }
                     return _current_ask.valid();
                  } FC_CAPTURE_AND_RETHROW() }

                  pending_chain_state_ptr     _pending_state;
                  pending_chain_state_ptr     _prior_state;
                  chain_database_impl&        _db_impl;

                  optional<market_order>      _current_bid;
                  optional<market_order>      _current_ask;
                  share_type                  _current_payoff_balance;
                  asset_id_type               _quote_id;
                  asset_id_type               _base_id;

                  vector<market_transaction>  _market_transactions;

                  bts::db::level_map< market_index_key, order_record >::iterator       _bid_itr;
                  bts::db::level_map< market_index_key, order_record >::iterator       _ask_itr;
                  bts::db::level_map< market_index_key, order_record >::iterator       _short_itr;
                  bts::db::level_map< market_index_key, collateral_record >::iterator  _collateral_itr;
            };
<commit_msg>Revert "added ability to support old and new market engine"<commit_after><|endoftext|>