2011年10月1日土曜日

CLangでクラスの中身を調べる(4)

前回の続き。アサーションではなくエラーメッセージにする方法の検討。

どうもclang::DiagnosticのメンバにClientというのがあり、それがnullptrであるときにアサーションされるようだ。

ってことで、改めてマジメにclang::Diagnosticsについて調べてみる。.hのドキュメントを見ると、clang::DiagnosticClientを出力先に指定しなければならないようだ。もし自分が診断結果の出力先のクラスを作るなら、きっと出力先を切り替えられるよう抽象化するだろうと思ってpublic DiagnosticClientで検索すると、以下のようなものがヒットした:
  • ChainedDiagnosticClient
  • TextDiagnosticBuffer
  • TextDiagnosticPrinter
  • VerifyDiagnosticsClient
  • StoredDiagnosticClient
  • FixItRewriter
  • IgnoringDiagClient
  • PathDiagnosticClient
最もそれっぽいclang::TextDiagnosticPrinterを使ってみる。

clanng::TextDiagnosticPrinterのコンストラクタはllvm::raw_ostreamとclang::DiagnosticOptionsを要求する。
前者は出力先ストリームであるからさらに抽象化されているのではないかと思って検索すると、やはり派生クラスがいくつか出てくる。ひとまずstd::ostreamに出力すると書いてあるllvm::raw_os_ostreamを使ってみる。
後者は色々設定できるようだが、まずはデフォルト設定で使ってみる。
インクルードに
#include <iostream>
#include "llvm/Support/raw_os_ostream.h"
#include "clang/Frontend/DiagnosticOptions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
を追加して、以下のコードを記述。
	llvm::raw_os_ostream diagOStream(std::cout);
	clang::DiagnosticOptions diagOpts;
	clang::TextDiagnosticPrinter diagPrinter(diagOStream, diagOpts);

	llvm::IntrusiveRefCntPtr<clang::diagnosticids>
		diagIds(new clang::DiagnosticIDs());
	clang::Diagnostic diag(diagIds, &diagPrinter);

ビルドするとclang::TextDiagnosticPrinterがリンクエラーになるので、clangFrontendプロジェクトを依存関係に追加。

で、実行すると・・・今度はTextDiagnosticPrinterでアサーションされるorz
メンバにLangOptionsがいて、そいつがnullptrであるとのこと。そのメンバ変数を設定するAPIはTextDiagnosticPrinter::BeginSourceFile()のみであるようだ。親クラスであるclang::DiagnosticClientの.hに記載のドキュメントによると、ソースファイルの処理が開始されたこと通知するためのコールバックであると書かれている。

そこでBeginSourceFile()の呼び出しルートを調べる。

■clang::DiagnosticClient::BeginSourceFile()の呼び出し元
  • clang::ASTMergeAction::ExecuteAction()
    ※ASTMergeActionはclang::FrontendActionの派生クラス。
  • clang::FrontendAction::BeginSourceFile()
■clang::FrontendAction::ExecuteAction()の呼び出し元
  • clang::FrontendAction::Execute()
■clang::FrontendAction::BeginSourceFile()の呼び出し元
  • clang::ASTUnit::Parse()
  • clang::ASTUnit::getMainBufferWithPrecompiledPreamble()
  • clang::ASTUnit::CodeComplete()
  • clang::CompilerInstance::ExecuteAction()
■clang::FrontendAction::Execute()の呼び出し元
  • clang::ASTUnit::Parse()
  • clang::ASTUnit::getMainBufferWithPrecompiledPreamble()
  • clang::ASTUnit::CodeComplete()
  • clang::CompilerInstance::ExecuteAction()
■clang::CompilerInstance::ExecuteAction()の呼び出し元
  • clang::ExecuteCompilerInvocation()

うーん、CompilerInstanceやASTUnitはプリプロセッサより後段であると思う。では自前でコールするのがいいのか?
正しいやり方かどうか怪しくなってきたが、試してみると・・・アサーションされなくなり、プリプロセッサのエラーが確認できるようになった。
#include <iostream>
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Support/Host.h"
#include "clang/Frontend/DiagnosticOptions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/Preprocessor.h"

int main(int, char*[])
{
	// Preprocessor をセットアップする。

	llvm::raw_os_ostream diagOStream(std::cout);
	clang::DiagnosticOptions diagOpts;
	clang::TextDiagnosticPrinter diagPrinter(diagOStream, diagOpts);

	llvm::IntrusiveRefCntPtr<clang::diagnosticids>
		diagIds(new clang::DiagnosticIDs());
	clang::Diagnostic diag(diagIds, &diagPrinter, false);

	clang::LangOptions langOpts;
	diagPrinter.BeginSourceFile(langOpts, NULL);

	clang::TargetOptions targetOpts;
	targetOpts.Triple = llvm::sys::getHostTriple();

	clang::TargetInfo* targetInfo
		= clang::TargetInfo::CreateTargetInfo(diag, targetOpts);

	clang::FileSystemOptions fileSysOpts;
	clang::FileManager fileMgr(fileSysOpts);
	clang::SourceManager srcMgr(diag, fileMgr);

	clang::HeaderSearch headerSearch(fileMgr);

	clang::Preprocessor pp(diag, langOpts, *targetInfo, srcMgr, headerSearch);


	// 標準インクルードパスを設定する。

	llvm::StringRef stdHeaderPath1("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\INCLUDE");
	llvm::StringRef stdHeaderPath2("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\PlatformSDK\\include");
	std::vector<clang::directorylookup> stdHeaderPaths;
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath1);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, false);
		stdHeaderPaths.push_back(d);
	}
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath2);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, false);
		stdHeaderPaths.push_back(d);
	}
	headerSearch.SetSearchPaths(stdHeaderPaths, 0, false);


	// コンパイル対象のファイルを指定する。

	llvm::StringRef cppPath("D:\\develop\\Clang\\temp.cpp");
	const clang::FileEntry* cppEntry = fileMgr.getFile(cppPath);
	clang::FileID fileId = srcMgr.createMainFileID(cppEntry);
	pp.EnterMainSourceFile();

	clang::Token token;
	pp.Lex(token);

	diagPrinter.EndSourceFile();

	getchar();
	return 0;
}

しばらくこれで進めてみよう・・・。

0 件のコメント:

コメントを投稿