/*
 * Copyright 2009-2012, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */
 
#include "BreakpointManager.h"
 
#include <stdio.h>
 
#include <new>
 
#include <AutoLocker.h>
 
#include "DebuggerInterface.h"
#include "Function.h"
#include "SpecificImageDebugInfo.h"
#include "Statement.h"
#include "Team.h"
#include "Tracing.h"
 
 
BreakpointManager::BreakpointManager(Team* team,
	DebuggerInterface* debuggerInterface)
	:
	fLock("breakpoint manager"),
	fTeam(team),
	fDebuggerInterface(debuggerInterface)
{
	fDebuggerInterface->AcquireReference();
}
 
 
BreakpointManager::~BreakpointManager()
{
	fDebuggerInterface->ReleaseReference();
}
 
 
status_t
BreakpointManager::Init()
{
	return fLock.InitCheck();
}
 
 
status_t
BreakpointManager::InstallUserBreakpoint(UserBreakpoint* userBreakpoint,
	bool enabled)
{
	TRACE_CONTROL("BreakpointManager::InstallUserBreakpoint(%p, %d)\n",
		userBreakpoint, enabled);
 
	AutoLocker<BLocker> installLocker(fLock);
	AutoLocker<Team> teamLocker(fTeam);
 
	bool oldEnabled = userBreakpoint->IsEnabled();
	if (userBreakpoint->IsValid() && enabled == oldEnabled) {
		TRACE_CONTROL("  user breakpoint already valid and with same enabled "
			"state\n");
		return B_OK;
	}
 
	// get/create the breakpoints for all instances
	TRACE_CONTROL("  creating breakpoints for breakpoint instances\n");
 
	status_t error = B_OK;
	for (int32 i = 0;
		UserBreakpointInstance* instance = userBreakpoint->InstanceAt(i); i++) {
 
		TRACE_CONTROL("    breakpoint instance %p\n", instance);
 
		if (instance->GetBreakpoint() != NULL) {
			TRACE_CONTROL("    -> already has breakpoint\n");
			continue;
		}
 
		target_addr_t address = instance->Address();
		Breakpoint* breakpoint = fTeam->BreakpointAtAddress(address);
		if (breakpoint == NULL) {
			TRACE_CONTROL("    -> no breakpoint at that address yet\n");
 
			Image* image = fTeam->ImageByAddress(address);
			if (image == NULL) {
				TRACE_CONTROL("    -> no image at that address\n");
				error = B_BAD_ADDRESS;
				break;
			}
 
			breakpoint = new(std::nothrow) Breakpoint(image, address);
			if (breakpoint == NULL || !fTeam->AddBreakpoint(breakpoint)) {
				delete breakpoint;
				error = B_NO_MEMORY;
				break;
			}
		}
 
		TRACE_CONTROL("    -> adding instance to breakpoint %p\n", breakpoint);
 
		breakpoint->AddUserBreakpoint(instance);
		instance->SetBreakpoint(breakpoint);
	}
 
	// If everything looks good so far mark the user breakpoint according to
	// its new state.
	if (error == B_OK)
		userBreakpoint->SetEnabled(enabled);
 
	// notify user breakpoint listeners
	if (error == B_OK)
		fTeam->NotifyUserBreakpointChanged(userBreakpoint);
 
	teamLocker.Unlock();
 
	// install/uninstall the breakpoints as needed
	TRACE_CONTROL("  updating breakpoints\n");
 
	if (error == B_OK) {
		for (int32 i = 0;
			UserBreakpointInstance* instance = userBreakpoint->InstanceAt(i);
			i++) {
			TRACE_CONTROL("    breakpoint instance %p\n", instance);
 
			error = _UpdateBreakpointInstallation(instance->GetBreakpoint());
			if (error != B_OK)
				break;
		}
	}
 
	if (error == B_OK) {
		TRACE_CONTROL("  success, marking user breakpoint valid\n");
 
		// everything went fine -- mark the user breakpoint valid
		if (!userBreakpoint->IsValid()) {
			teamLocker.Lock();
			userBreakpoint->SetValid(true);
			userBreakpoint->AcquireReference();
			fTeam->AddUserBreakpoint(userBreakpoint);
			fTeam->NotifyUserBreakpointChanged(userBreakpoint);
				// notify again -- the breakpoint hadn't been added before
			teamLocker.Unlock();
		}
	} else {
		// something went wrong -- revert the situation
		TRACE_CONTROL("  error, reverting\n");
 
		teamLocker.Lock();
		userBreakpoint->SetEnabled(oldEnabled);
		teamLocker.Unlock();
 
		if (!oldEnabled || !userBreakpoint->IsValid()) {
			for (int32 i = 0;  UserBreakpointInstance* instance
					= userBreakpoint->InstanceAt(i);
				i++) {
				Breakpoint* breakpoint = instance->GetBreakpoint();
				if (breakpoint == NULL)
					continue;
 
				if (!userBreakpoint->IsValid()) {
					instance->SetBreakpoint(NULL);
					breakpoint->RemoveUserBreakpoint(instance);
				}
 
				_UpdateBreakpointInstallation(breakpoint);
 
				teamLocker.Lock();
 
				if (breakpoint->IsUnused())
					fTeam->RemoveBreakpoint(breakpoint);
				teamLocker.Unlock();
			}
 
			teamLocker.Lock();
			fTeam->NotifyUserBreakpointChanged(userBreakpoint);
			teamLocker.Unlock();
		}
	}
 
	installLocker.Unlock();
 
	return error;
}
 
 
void
BreakpointManager::UninstallUserBreakpoint(UserBreakpoint* userBreakpoint)
{
	AutoLocker<BLocker> installLocker(fLock);
	AutoLocker<Team> teamLocker(fTeam);
 
	if (!userBreakpoint->IsValid())
		return;
 
	fTeam->RemoveUserBreakpoint(userBreakpoint);
 
	userBreakpoint->SetValid(false);
	userBreakpoint->SetEnabled(false);
 
	teamLocker.Unlock();
 
	// uninstall the breakpoints as needed
	for (int32 i = 0;
		UserBreakpointInstance* instance = userBreakpoint->InstanceAt(i); i++) {
		if (Breakpoint* breakpoint = instance->GetBreakpoint())
			_UpdateBreakpointInstallation(breakpoint);
	}
 
	teamLocker.Lock();
 
	// detach the breakpoints from the user breakpoint instances
	for (int32 i = 0;
		UserBreakpointInstance* instance = userBreakpoint->InstanceAt(i); i++) {
		if (Breakpoint* breakpoint = instance->GetBreakpoint()) {
			instance->SetBreakpoint(NULL);
			breakpoint->RemoveUserBreakpoint(instance);
 
			if (breakpoint->IsUnused())
				fTeam->RemoveBreakpoint(breakpoint);
		}
	}
 
	fTeam->NotifyUserBreakpointChanged(userBreakpoint);
 
	teamLocker.Unlock();
	installLocker.Unlock();
 
	// release the reference from InstallUserBreakpoint()
	userBreakpoint->ReleaseReference();
}
 
 
status_t
BreakpointManager::InstallTemporaryBreakpoint(target_addr_t address,
	BreakpointClient* client)
{
	AutoLocker<BLocker> installLocker(fLock);
	AutoLocker<Team> teamLocker(fTeam);
 
	// create a breakpoint, if it doesn't exist yet
	Breakpoint* breakpoint = fTeam->BreakpointAtAddress(address);
	if (breakpoint == NULL) {
		Image* image = fTeam->ImageByAddress(address);
		if (image == NULL)
			return B_BAD_ADDRESS;
 
		breakpoint = new(std::nothrow) Breakpoint(image, address);
		if (breakpoint == NULL)
			return B_NO_MEMORY;
 
		if (!fTeam->AddBreakpoint(breakpoint))
			return B_NO_MEMORY;
	}
 
	BReference<Breakpoint> breakpointReference(breakpoint);
 
	// add the client
	status_t error;
	if (breakpoint->AddClient(client)) {
		if (breakpoint->IsInstalled())
			return B_OK;
 
		// install
		teamLocker.Unlock();
 
		error = fDebuggerInterface->InstallBreakpoint(address);
		if (error == B_OK) {
			breakpoint->SetInstalled(true);
			return B_OK;
		}
 
		teamLocker.Lock();
 
		breakpoint->RemoveClient(client);
	} else
		error = B_NO_MEMORY;
 
	// clean up on error
	if (breakpoint->IsUnused())
		fTeam->RemoveBreakpoint(breakpoint);
 
	return error;
}
 
 
void
BreakpointManager::UninstallTemporaryBreakpoint(target_addr_t address,
	BreakpointClient* client)
{
	AutoLocker<BLocker> installLocker(fLock);
	AutoLocker<Team> teamLocker(fTeam);
 
	Breakpoint* breakpoint = fTeam->BreakpointAtAddress(address);
	if (breakpoint == NULL)
		return;
 
	// remove the client
	breakpoint->RemoveClient(client);
 
	// check whether the breakpoint needs to be uninstalled
	bool uninstall = !breakpoint->ShouldBeInstalled()
		&& breakpoint->IsInstalled();
 
	// if unused remove it
	BReference<Breakpoint> breakpointReference(breakpoint);
	if (breakpoint->IsUnused())
		fTeam->RemoveBreakpoint(breakpoint);
 
	teamLocker.Unlock();
 
	if (uninstall) {
		fDebuggerInterface->UninstallBreakpoint(address);
		breakpoint->SetInstalled(false);
	}
}
 
 
void
BreakpointManager::UpdateImageBreakpoints(Image* image)
{
	_UpdateImageBreakpoints(image, false);
}
 
 
void
BreakpointManager::RemoveImageBreakpoints(Image* image)
{
	_UpdateImageBreakpoints(image, true);
}
 
 
void
BreakpointManager::_UpdateImageBreakpoints(Image* image, bool removeOnly)
{
	AutoLocker<BLocker> installLocker(fLock);
	AutoLocker<Team> teamLocker(fTeam);
 
	// remove obsolete user breakpoint instances
	BObjectList<Breakpoint> breakpointsToUpdate;
	for (UserBreakpointList::ConstIterator it
			= fTeam->UserBreakpoints().GetIterator();
		UserBreakpoint* userBreakpoint = it.Next();) {
		int32 instanceCount = userBreakpoint->CountInstances();
		for (int32 i = instanceCount - 1; i >= 0; i--) {
			UserBreakpointInstance* instance = userBreakpoint->InstanceAt(i);
			Breakpoint* breakpoint = instance->GetBreakpoint();
			if (breakpoint == NULL || breakpoint->GetImage() != image)
				continue;
 
			userBreakpoint->RemoveInstanceAt(i);
			breakpoint->RemoveUserBreakpoint(instance);
 
			if (!breakpointsToUpdate.AddItem(breakpoint)) {
				_UpdateBreakpointInstallation(breakpoint);
				if (breakpoint->IsUnused())
					fTeam->RemoveBreakpoint(breakpoint);
			}
 
			delete instance;
		}
	}
 
	// update breakpoints
	teamLocker.Unlock();
	for (int32 i = 0; Breakpoint* breakpoint = breakpointsToUpdate.ItemAt(i);
			i++) {
		_UpdateBreakpointInstallation(breakpoint);
	}
 
	teamLocker.Lock();
	for (int32 i = 0; Breakpoint* breakpoint = breakpointsToUpdate.ItemAt(i);
			i++) {
		if (breakpoint->IsUnused())
			fTeam->RemoveBreakpoint(breakpoint);
	}
 
	// add breakpoint instances for function instances in the image (if we have
	// an image debug info)
	BObjectList<UserBreakpointInstance> newInstances;
	ImageDebugInfo* imageDebugInfo = image->GetImageDebugInfo();
	if (imageDebugInfo == NULL)
		return;
 
	for (UserBreakpointList::ConstIterator it
			= fTeam->UserBreakpoints().GetIterator();
		UserBreakpoint* userBreakpoint = it.Next();) {
		// get the function
		Function* function = fTeam->FunctionByID(
			userBreakpoint->Location().GetFunctionID());
		if (function == NULL)
			continue;
 
		const SourceLocation& sourceLocation
			= userBreakpoint->Location().GetSourceLocation();
		target_addr_t relativeAddress
			= userBreakpoint->Location().RelativeAddress();
 
		// iterate through the function instances
		for (FunctionInstanceList::ConstIterator it
				= function->Instances().GetIterator();
			FunctionInstance* functionInstance = it.Next();) {
			if (functionInstance->GetImageDebugInfo() != imageDebugInfo)
				continue;
 
			// get the breakpoint address for the instance
			target_addr_t instanceAddress = 0;
			if (functionInstance->SourceFile() != NULL) {
				// We have a source file, so get the address for the source
				// location.
				Statement* statement = NULL;
				FunctionDebugInfo* functionDebugInfo
					= functionInstance->GetFunctionDebugInfo();
				functionDebugInfo->GetSpecificImageDebugInfo()
					->GetStatementAtSourceLocation(functionDebugInfo,
						sourceLocation, statement);
				if (statement != NULL) {
					instanceAddress = statement->CoveringAddressRange().Start();
						// TODO: What about BreakpointAllowed()?
					statement->ReleaseReference();
					// TODO: Make sure we do hit the function in question!
				}
			}
 
			if (instanceAddress == 0) {
				// No source file (or we failed getting the statement), so try
				// to use the same relative address.
				if (relativeAddress > functionInstance->Size())
					continue;
				instanceAddress = functionInstance->Address() + relativeAddress;
					// TODO: Make sure it does at least hit an instruction!
			}
 
			// create the user breakpoint instance
			UserBreakpointInstance* instance = new(std::nothrow)
				UserBreakpointInstance(userBreakpoint, instanceAddress);
			if (instance == NULL || !newInstances.AddItem(instance)) {
				delete instance;
				continue;
			}
 
			if (!userBreakpoint->AddInstance(instance)) {
				newInstances.RemoveItemAt(newInstances.CountItems() - 1);
				delete instance;
			}
 
			// get/create the breakpoint for the address
			target_addr_t address = instance->Address();
			Breakpoint* breakpoint = fTeam->BreakpointAtAddress(address);
			if (breakpoint == NULL) {
				breakpoint = new(std::nothrow) Breakpoint(image, address);
				if (breakpoint == NULL || !fTeam->AddBreakpoint(breakpoint)) {
					delete breakpoint;
					break;
				}
			}
 
			breakpoint->AddUserBreakpoint(instance);
			instance->SetBreakpoint(breakpoint);
		}
	}
 
	// install the breakpoints for the new user breakpoint instances
	teamLocker.Unlock();
	for (int32 i = 0; UserBreakpointInstance* instance = newInstances.ItemAt(i);
			i++) {
		Breakpoint* breakpoint = instance->GetBreakpoint();
		if (breakpoint == NULL
			|| _UpdateBreakpointInstallation(breakpoint) != B_OK) {
			// something went wrong -- remove the instance
			teamLocker.Lock();
 
			instance->GetUserBreakpoint()->RemoveInstance(instance);
			if (breakpoint != NULL) {
				breakpoint->AddUserBreakpoint(instance);
				if (breakpoint->IsUnused())
					fTeam->RemoveBreakpoint(breakpoint);
			}
 
			teamLocker.Unlock();
		}
	}
}
 
 
status_t
BreakpointManager::_UpdateBreakpointInstallation(Breakpoint* breakpoint)
{
	bool shouldBeInstalled = breakpoint->ShouldBeInstalled();
 
	TRACE_CONTROL("BreakpointManager::_UpdateBreakpointInstallation(%p): "
		"should be installed: %d, is installed: %d\n", breakpoint,
		shouldBeInstalled, breakpoint->IsInstalled());
 
	if (shouldBeInstalled == breakpoint->IsInstalled())
		return B_OK;
 
	if (shouldBeInstalled) {
		// install
		status_t error = B_OK;
		// if we're not actually connected to a team, silently
		// allow setting the breakpoint so it's saved to settings
		// for when we do connect/have the team in the debugger.
		if (fDebuggerInterface->Connected())
			fDebuggerInterface->InstallBreakpoint(breakpoint->Address());
 
		if (error != B_OK)
			return error;
 
		TRACE_CONTROL("BREAKPOINT at %#" B_PRIx64 " installed: %s\n",
			breakpoint->Address(), strerror(error));
 
		breakpoint->SetInstalled(true);
	} else {
		// uninstall
		fDebuggerInterface->UninstallBreakpoint(breakpoint->Address());
 
		TRACE_CONTROL("BREAKPOINT at %#" B_PRIx64 " uninstalled\n",
			breakpoint->Address());
 
		breakpoint->SetInstalled(false);
	}
 
	return B_OK;
}

V547 Expression 'error != ((int) 0)' is always false.