/* java_thread.cpp
   Copyright (C) 2005 Free Software Foundation, Inc.

This file is part of Mysaifu JVM

Mysaifu JVM 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; version 2 of the License.

Mysaifu JVM 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 GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
*/


#include "StdAfx.h"
#include <Tlhelp32.h>
#include "java_thread.h"
#include "instruction.h"
#include "frame.h"
#include "common_funcs.h"
#include "resource.h"
#include "jni_funcs.h"

/**
 * スレッドの情報
 */
struct thread_info {
	HANDLE hThread;		// スレッドハンドル
	DWORD thread_id;	// スレッドID(Windows CEではスレッドハンドル=スレッドIDとなるため不要？）
	bool daemon;		// デーモンスレッドの場合true
	jobject vmthread;	// java.lang.VMThreadオブジェクトの参照
	jobject thread;		// java.lang.Threadオブジェクトの参照
	HANDLE hInterrupt;	// Thread.interrupt()が使用するイベントオブジェクト
	bool interrupted;	// VMThread.isInterrupted()が返すフラグ

};

/**
 * スレッドバッファのサイズ
 */
static int g_threads_count;

/**
 * スレッド情報バッファ
 */
static thread_info** g_thread_infos;

/**
 * 競合防止用
 */
CRITICAL_SECTION	g_global_critical_section;

/**
 * 実行中のスレッドが、すべてデーモンスレッドになった場合にシグナル状態となる
 * ハンドル。
 */
HANDLE g_hTerminate;

/**
 * スレッドのスタックサイズ
 */
static DWORD g_thread_stack_size;

/**
 * スタティック関数の宣言
 */
static bool add_thread_info(HANDLE hThread, DWORD thread_id, bool daemon, jobject vmthread, jobject thread);
static void remove_current_thread_info();
static bool non_daemon_thread_exists();
static void dump_message(const _TCHAR* filename,
						 void* address,
						 DWORD code,
						 const _TCHAR* detail,
						 CONTEXT* context);

/**
 * コールバック関数
 */
LRESULT CALLBACK ExceptionDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);

/**
 * スレッド関連の設定を初期化する
 */
void init_thread_settings(DWORD thread_stack_size) {
	g_hTerminate = CreateEvent(NULL,
		                        FALSE,
								FALSE,
								NULL);
	
	InitializeCriticalSection(&g_global_critical_section);

	g_thread_stack_size = thread_stack_size;
}

/**
 * 例外ハンドラ
 */
static int filter(LPEXCEPTION_POINTERS pointers) {
	CONTEXT* context = pointers->ContextRecord;
	EXCEPTION_RECORD* record = pointers->ExceptionRecord;

	_TCHAR* detail = NULL;
	int retcode = EXCEPTION_EXECUTE_HANDLER;
	_TCHAR buff[256];

	switch (record->ExceptionCode) {
	case EXCEPTION_ACCESS_VIOLATION:
		_stprintf(buff, _T("Access violation. Attempted to %s address %x"),
						(record->ExceptionInformation[0] == 0 ? _T("read") : _T("write")), 
						record->ExceptionInformation[1]);
		detail = buff;
		break;

	case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
		detail = _T("Array bounds exceeded");
		break;

	case EXCEPTION_DATATYPE_MISALIGNMENT:
		detail = _T("Data misalignment");
		break;

	case EXCEPTION_FLT_DIVIDE_BY_ZERO:
	case EXCEPTION_INT_DIVIDE_BY_ZERO:
		detail = _T("Divide by zero");
		break;

	case EXCEPTION_IN_PAGE_ERROR:
		detail = _T("Page error");
		break;

	case EXCEPTION_STACK_OVERFLOW:
		detail = _T("Stack overflow");
		break;

	default:
		// 上記以外の例外は処理しない
		retcode = EXCEPTION_CONTINUE_SEARCH;
		break;
	}

	if (retcode != EXCEPTION_CONTINUE_SEARCH) {
		// 例外情報を出力するファイル名を決める
		_TCHAR destdir[MAX_PATH + 1];
		GetModuleFileName(GetModuleHandle(NULL), destdir, sizeof(destdir) / sizeof(*destdir));
		int sepcount = 0;
		for (int i = _tcslen(destdir) - 1; i >= 0; --i) {
			if (destdir[i] == _T('\\')) {
				sepcount++;
				if (sepcount >= 3) {
					destdir[i] = _T('\0');
					break;
				}
			}
		}
		_tcscat(destdir, _T("\\errors"));
		CreateDirectory(destdir, NULL);

		_TCHAR reportfile[MAX_PATH + 1];
		GetTempFileName(destdir,
					    _T("err"),
						0,
						reportfile);
		// ファイルにダンプメッセージを出力する
		dump_message(reportfile,
					 record->ExceptionAddress,
					 record->ExceptionCode,
					 detail,
					 context);

		// ダイアログボックスを表示する
		DialogBoxParam(GetModuleHandle(NULL),
					   MAKEINTRESOURCE(IDD_NATIVE_EXCEPTION),
					   NULL,
					   (DLGPROC) ExceptionDialogProc,
					   (LPARAM) &reportfile);


	}

	return retcode;
}

/**
 * 指定されたメソッドを実行する
 * この関数は CreateThread() の引数として渡されることを前提にしている
 */
DWORD WINAPI thread_start(LPVOID pvarg) {
	static method_info* minfo;
	__try {
		thread_start_parameter* param = (thread_start_parameter*) pvarg;
		
		ClassFile* vmthread_cfile = get_ClassFile(param->vmthread_reference);
		frame* newframe = alloc_root_frame(vmthread_cfile);

		// VMThreadオブジェクトをpushする（ガベージコレクトされないように）
		PUSH_OBJECT(newframe, param->vmthread_reference);
		
		// カレントスレッドを設定する
		set_current_thread(newframe, param->thread_reference);

		// VMThread.run()を呼び出す
		if (! minfo) {
			jmethodID mid = get_method_id(vmthread_cfile, intern_utf8("run"), intern_utf8("()V"));
			if (mid) {
				minfo = get_method_info(vmthread_cfile, mid);
			}
		}
		if (minfo) {
			invoke_method(newframe, param->vmthread_reference, vmthread_cfile, minfo);
		}

		// フレームを破棄する
		free_root_frame(newframe);
		
		// パラメータを開放する
		free(param);
		
		// スレッド情報を削除する
		remove_current_thread_info();
	} __except (filter(GetExceptionInformation())) {
		// このスレッドのみが終了する
		// exit(-1);
	}

	return 0;
}

/**
 * Windowsネイティブスレッドを作成する
 */
bool create_native_thread(LPTHREAD_START_ROUTINE func, LPVOID p) {
	thread_start_parameter* param = (thread_start_parameter*) p;
	DWORD thread_id;
	HANDLE hThread = CreateThread(NULL,								// セキュリティ記述子
								  g_thread_stack_size,				// 初期スタックサイズ
								  func,								// 実行関数のポインタ
								  param,							// 実行関数に渡されるパラメータ
								  STACK_SIZE_PARAM_IS_A_RESERVATION,// スレッド作成フラグ
								  &thread_id);						// スレッドID格納ポインタ
	bool result = false;
	if (hThread != NULL) {
		result = add_thread_info(hThread,
								 thread_id,
								 param->daemon,
								 param->vmthread_reference,
								 param->thread_reference);
	}

	return result;
}

/**
 * 指定された情報を登録する
 */
static bool add_thread_info(HANDLE hThread, DWORD thread_id, bool daemon, jobject vmthread, jobject thread) {
	// クリティカルセクションに入る
	EnterCriticalSection(&g_global_critical_section);

	// thread_info構造体を割り当てる。
	thread_info* info = (thread_info*) malloc(sizeof(thread_info));
	if (info == NULL) {
		// クリティカルセクションから抜ける
		LeaveCriticalSection(&g_global_critical_section);
		fatal_error(FATAL_ERROR_NO_MEMORY);
	}

	// thread_info構造体の内容を初期化する
	info->hThread = hThread;
	info->thread_id = thread_id;
	info->daemon = daemon;
	info->vmthread = vmthread;
	info->thread = thread;
	info->hInterrupt = CreateEvent(NULL,	// セキュリティ記述子なし（CEではサポートされていない）
								   FALSE,	// 自動リセット
								   FALSE,	// 初期状態では非シグナル状態
								   NULL);	// 名無し
	info->interrupted = false;

	// 空きスロットに入れる
	bool result = false;
	for (int i = 0;	i < g_threads_count; ++i) {
		if (g_thread_infos[i] == NULL) {
			g_thread_infos[i] = info;
			result = true;
			break;
		}
	}
	if (! result) {
		// 空きスロットが無い場合、バッファを拡張する
		g_threads_count++;
		g_thread_infos = (thread_info**) realloc(g_thread_infos, sizeof(thread_info*) * g_threads_count);
		if (g_thread_infos != NULL) {
			g_thread_infos[g_threads_count - 1] = info;
			result = true;
		} else {
			free(info);
			result = false;
		}
	}

	// クリティカルセクションから抜ける
	LeaveCriticalSection(&g_global_critical_section);
	return result;
}

/**
 * カレントスレッド情報を削除する
 */
static void remove_current_thread_info() {
	EnterCriticalSection(&g_global_critical_section);
	DWORD thread_id = GetCurrentThreadId();
	for (int i = 0;	i < g_threads_count; ++i) {
		thread_info* info = g_thread_infos[i];
		if (info != NULL && info->thread_id == thread_id) {
			CloseHandle(info->hThread);
			CloseHandle(info->hInterrupt);
			free(info);
			g_thread_infos[i] = NULL;
			break;
		}
	}
	if (! non_daemon_thread_exists()) {
		// デーモンスレッド以外のスレッドが存在しない場合、イベントを通知する
		SetEvent(g_hTerminate);
	} else {
		ResetEvent(g_hTerminate);
	}

	LeaveCriticalSection(&g_global_critical_section);
}


/**
 * カレントスレッド以外のスレッドをすべてサスペンドする
 */
bool suspend_all_other_threads() {
	EnterCriticalSection(&g_global_critical_section);
	
	bool result = true;
	DWORD currentThreadId = GetCurrentThreadId();
	for (int i = 0; i < g_threads_count; ++i) {
		thread_info* info = g_thread_infos[i];
		if (info != NULL) {
			DWORD threadId = info->thread_id;
			if (threadId != currentThreadId) {
				if (SuspendThread(info->hThread) == -1) {
					DWORD exitcode;
					if (GetExitCodeThread(info->hThread, &exitcode) == STILL_ACTIVE) {
						int error = GetLastError();
						result = false;
						break;
					}
				}
			}
		}
	}
	LeaveCriticalSection(&g_global_critical_section);

	return result;
}

/**
 * カレントスレッド以外のスレッドをすべて再開する
 */
bool resume_all_other_threads() {
	EnterCriticalSection(&g_global_critical_section);

	bool result = true;
	DWORD currentThreadId = GetCurrentThreadId();
	for (int i = 0; i < g_threads_count; ++i) {
		thread_info* info = g_thread_infos[i];
		if (info != NULL) {
			if (info->thread_id != currentThreadId) {
				if (ResumeThread(info->hThread) == -1) {
					result = false;
					break;
				}
			}
		}
	}
	LeaveCriticalSection(&g_global_critical_section);

	return result;
}

/**
 * デーモン以外のスレッドが存在しているか調べる
 */
static bool non_daemon_thread_exists() {
	EnterCriticalSection(&g_global_critical_section);
	bool result = false;
	for (int i = 0; i < g_threads_count; ++i) {
		thread_info* info = g_thread_infos[i];
		if (info != NULL && ! info->daemon) {
			result = true;
			break;
		}
	}
	LeaveCriticalSection(&g_global_critical_section);
	return result;
}

/**
 * 実行中のスレッドがすべてデーモンスレッドになった場合にシグナル状態になる
 * ハンドルを返す
 */
HANDLE get_terminate_handle() {
	return g_hTerminate;
}

/**
 * カレントスレッドに対応する Thread の参照を返す
 */
jobject current_Thread() {
	DWORD thread_id = GetCurrentThreadId();
	EnterCriticalSection(&g_global_critical_section);
	jobject thread = NULL;
	for (int i = 0; i < g_threads_count; ++i) {
		thread_info* info = g_thread_infos[i];
		if (info != NULL) {
			if (info->thread_id == thread_id) {
				thread = info->thread;
				break;
			}
		}
	}
	LeaveCriticalSection(&g_global_critical_section);
	return thread;
}

/**
 * 指定されたスレッドIDに対応する interrupt() 用イベントオブジェクトを返す。
 */
HANDLE get_interrupt_event(DWORD thread_id) {
	// クリティカルセクションに入る
	EnterCriticalSection(&g_global_critical_section);
	
	HANDLE hEvent = NULL;
	for (int i = 0; i < g_threads_count; ++i) {
		thread_info* info = g_thread_infos[i];
		if (info != NULL) {
			if (info->thread_id == thread_id) {
				hEvent = info->hInterrupt;
				break;
			}
		}
	}
	assert(hEvent != NULL);

	LeaveCriticalSection(&g_global_critical_section);

	return hEvent;
}

/**
 * 指定されたスレッドIDに対応する割り込みフラグを設定する
 */
void set_interrupted(DWORD thread_id, bool flag) {
	// クリティカルセクションに入る
	EnterCriticalSection(&g_global_critical_section);
	
	for (int i = 0; i < g_threads_count; ++i) {
		thread_info* info = g_thread_infos[i];
		if (info != NULL) {
			if (info->thread_id == thread_id) {
				info->interrupted = flag;
				break;
			}
		}
	}
	LeaveCriticalSection(&g_global_critical_section);
}

/**
 * 指定されたスレッドIDに対応する割り込みフラグを返す
 */
bool is_interrupted(DWORD thread_id) {
	// クリティカルセクションに入る
	EnterCriticalSection(&g_global_critical_section);
	
	bool flag = false;
	for (int i = 0; i < g_threads_count; ++i) {
		thread_info* info = g_thread_infos[i];
		if (info != NULL) {
			if (info->thread_id == thread_id) {
				flag = info->interrupted;
				break;
			}
		}
	}

	LeaveCriticalSection(&g_global_critical_section);

	return flag;
}

/**
 * 指定されたスレッドIDに対応するスレッドに割り込む。
 * 割り込みフラグはtrueに設定される。
 */
void interrupt(DWORD thread_id) {
	HANDLE hEvent = get_interrupt_event(thread_id);
	SetEvent(hEvent);

	set_interrupted(thread_id, true);
}

/**
 * 指定されたVMThreadオブジェクトのインスタンスが関連付けられたスレッドの
 * スレッドIDを返す
 */
DWORD get_thread_id(jobject vmthread) {
	// クリティカルセクションに入る
	EnterCriticalSection(&g_global_critical_section);

	DWORD thread_id = (DWORD) -1;
	for (int i = 0; i < g_threads_count; ++i) {
		thread_info* info = g_thread_infos[i];
		if (info != NULL) {
			if (info->vmthread == vmthread) {
				thread_id = info->thread_id;
				break;
			}
		}
	}

	// クリティカルセクションから抜ける
	LeaveCriticalSection(&g_global_critical_section);
	return thread_id;
}

/**
 * 指定されたVMThreadオブジェクトに対応する
 * ネイティブスレッドのハンドルを返す
 */
HANDLE get_thread_handle(jobject vmthread) {
	// クリティカルセクションに入る
	EnterCriticalSection(&g_global_critical_section);

	HANDLE hThread = INVALID_HANDLE_VALUE;
	for (int i = 0; i < g_threads_count; ++i) {
		thread_info* info = g_thread_infos[i];
		if (info != NULL) {
			if (info->vmthread == vmthread) {
				hThread = info->hThread;
				break;
			}
		}
	}

	// クリティカルセクションから抜ける
	LeaveCriticalSection(&g_global_critical_section);
	return hThread;
}

/**
 * クラッシュ時点の情報をファイルに出力する
 * 出力する内容は以下のとおり
 * 1. JVMのバージョン
 * 2. OSのバージョン
 * 3. クラッシュが発生したアドレス
 * 4. エラーメッセージの文字列
 */
static void dump_message(const _TCHAR* filename,
						 void* address,
						 DWORD code,
						 const _TCHAR* detail,
						 CONTEXT* context) {
	FILE* fp = _tfopen(filename, _T("w"));
	if (fp) {
		// JVMの正確なバージョンを取得する
		LARGE_INTEGER jvm_version = get_jvm_version();
		// 16ビットごとに区切って文字列を作成する
		_ftprintf(fp,
				  _T("JVM Version: %d.%d.%d.%d\n"),
				  HIWORD(jvm_version.HighPart),
				  LOWORD(jvm_version.HighPart),
				  HIWORD(jvm_version.LowPart),
				  LOWORD(jvm_version.LowPart));
		// OSのバージョンを取得する
		OSVERSIONINFO info = {0};
		info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
		GetVersionEx(&info);
		_ftprintf(fp,
				   _T("OS version: %d.%d (Build %d): PlatformId %d : %s\n"),
				   info.dwMajorVersion,
				   info.dwMinorVersion,
				   info.dwBuildNumber,
				   info.dwPlatformId,
				   info.szCSDVersion);
		
		// メモリ使用量を出力する
		MEMORYSTATUS memstat = {0};
		memstat.dwLength = sizeof(MEMORYSTATUS);
		GlobalMemoryStatus(&memstat);
		_ftprintf(fp,
				  _T("Memory: %d%% used, physical %d/%d, page file %d/%d, virtual %d/%d\n"),
				  memstat.dwMemoryLoad,
				  memstat.dwAvailPhys,
				  memstat.dwTotalPhys,
				  memstat.dwAvailPageFile,
				  memstat.dwTotalPageFile,
				  memstat.dwAvailVirtual,
				  memstat.dwTotalVirtual);

		STORE_INFORMATION sinfo = {0};
		GetStoreInformation(&sinfo);
		_ftprintf(fp,
				  _T("Store: %d/%d\n"),
				  sinfo.dwFreeSize,
				  sinfo.dwStoreSize);

		// コードページを出力する
		_ftprintf(fp,
				  _T("CodePage: %d\n"),
				  GetOEMCP());
		
		// argumentsを出力する
		_fputts(_T("JVM arguments:"), fp);
		arguments* args = get_current_arguments();
		arguments_store(args, fp);
		_fputts(_T("\n"), fp);

		// 例外が発生したアドレスを出力する
		_ftprintf(fp,
				   _T("ExceptionAddress:0x%x\n"),
				   address);
		
		// 例外コードを出力する
		_ftprintf(fp,
				  _T("ExceptionCode: %x\n"),
				  code);
		
		// 詳細メッセージを出力する
		_fputts(_T("Exception detail:\n"), fp);
		_fputts(detail, fp);
		_fputts(_T("\n\n"), fp);

#if defined(ARM)
		// コンテキストを出力する
		_fputts(_T("Context:\n"), fp);
		_ftprintf(fp,
				  _T("ContextFlags=0x%x\n")
				  _T("R0=0x%08x R1=0x%08x R2=0x%08x R3=0x%08x R4=0x%08x R5=0x%08x R6=0x%08x\n")
				  _T("R7=0x%08x R8=0x%08x R9=0x%08x R10=0x%08x R11=0x%08x R12=0x%08x\n")
				  _T("Sp=0x%08x Lr=0x%08x Pc=0x%08x Psr=0x%08x\n")
				  _T("Fpscr=0x%08x FpExc=0x%08x\n"),
				  context->ContextFlags,
				  context->R0,context->R1,context->R2,context->R3,context->R4,context->R5,context->R6,context->R7,context->R8,context->R9,context->R10,context->R11,context->R12,
				  context->Sp, context->Lr, context->Pc, context->Psr,
				  context->Fpscr, context->FpExc);
		for (int i = 0; i <= NUM_VFP_REGS; ++i) {
			_ftprintf(fp, _T("S[%d]=0x%08x "), i, context->S[i]);
		}
		_ftprintf(fp, _T("\n"));
		for (int i = 0; i < NUM_EXTRA_CONTROL_REGS; ++i) {
			_ftprintf(fp, _T("FpExtra[%d]=0x%08x "), i, context->FpExtra[i]);
		}
		_ftprintf(fp, _T("\n\n"));

#endif
		// DLLモジュール一覧を出力する
		_fputts(_T("Modules:\n"), fp);
		HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
		if (hSnapshot != INVALID_HANDLE_VALUE) {
			MODULEENTRY32 me = {0};
			me.dwSize = sizeof(MODULEENTRY32);
			if (Module32First(hSnapshot, &me)) {
				_fputts(_T("ModuleID   ProcessID  GlblcntUsg ProccntUsg modBaseAdr-modEndAdr  hModule    szModule   szExePath dwFlags \n"), fp);
				do {
					_ftprintf(fp,
							  _T("0x%08x 0x%08x 0x%08x 0x%08x 0x%08x-0x%08x 0x%08x %s %s 0x%08x \n"),
							  me.th32ModuleID,
							  me.th32ProcessID,
							  me.GlblcntUsage,
							  me.ProccntUsage,
							  me.modBaseAddr,
							  (me.modBaseAddr + me.modBaseSize),
							  me.hModule,
							  me.szModule,
							  me.szExePath,
							  me.dwFlags);
				} while (Module32Next(hSnapshot, &me));
			}
			CloseToolhelp32Snapshot(hSnapshot);
		}

		_ftprintf(fp, _T("\n"));
		_fputts(_T("End of dump file\n"), fp);

		fclose(fp);
	}
}

/**
 * Win32例外発生ダイアログのコールバック関数
 */
LRESULT CALLBACK ExceptionDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
	static const _TCHAR* filename;
	switch (message)
	{
		case WM_INITDIALOG:
			{
				// ダイアログボックスを最大化する
				SHINITDLGINFO shidi = {0};
				shidi.dwMask = SHIDIM_FLAGS;
				shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;
				shidi.hDlg = hDlg;
				SHInitDialog(&shidi);
				
				SetWindowText(hDlg, _T("Mysaifu JVM Problem"));
				filename = (const _TCHAR*) lParam;
			
				SetDlgItemText(hDlg, IDC_REPORT_FILE_NAME, filename);
			}			
			return TRUE; 

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
			case IDOK:
				{
					// ユーザの入力内容を取得する
					_TCHAR buff[512];
					GetDlgItemText(hDlg,
								   IDC_EXCEPTION_REPORT,
								   buff,
								   sizeof(buff) / sizeof(buff[0]));

					// ファイルに追加する
					FILE* fp = _tfopen(filename, _T("a"));
					if (fp) {
						_fputts(_T("\nAdditional information:\n"), fp);
						_fputts(buff, fp);
						fclose(fp);
					}
					
					// 保存したファイル名を表示する
					_sntprintf(buff,
							   sizeof(buff) / sizeof(_TCHAR) - 1,
							   _T("The report file has been created. Filename : %s"),
							   filename);
					buff[sizeof(buff) / sizeof(_TCHAR) - 1] = _T('\0');
					MessageBox(hDlg,
							   buff,
							   _T("Notice"),
							   MB_OK | MB_ICONINFORMATION);

					// ダイアログを終了する
					EndDialog(hDlg, LOWORD(wParam));
				}
				return TRUE;
			}
			break;
	}
	return FALSE;
}
