カテゴリー
投資

制御フローグラフ

import re
import sys
import os
import networkx as nx
import pydot

def remove_comments_and_strings(code):
    """
    Javaコードから、ブロックコメント、行コメント、文字列リテラルを除去して、
    制御構造の誤検出を防ぎます。
    """
    code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL)  # ブロックコメント除去
    code = re.sub(r'//.*', '', code)                        # 行コメント除去
    code = re.sub(r'"(?:\\.|[^"\\])*"', '""', code)         # 文字列リテラル除去
    return code

def extract_methods(code):
    """
    簡易的な正規表現と括弧カウントにより、
    Javaコード内の各メソッドブロックを抽出します。
    """
    method_pattern = re.compile(
        r'(public|protected|private|static|\s)+[\w\<\>\[\]]+\s+\w+\s*\([^)]*\)\s*\{'
    )
    methods = []
    for match in method_pattern.finditer(code):
        start_index = match.end() - 1  # '{' の位置
        brace_count = 1
        end_index = start_index + 1
        while end_index < len(code) and brace_count > 0:
            if code[end_index] == '{':
                brace_count += 1
            elif code[end_index] == '}':
                brace_count -= 1
            end_index += 1
        method_body = code[match.start():end_index]
        methods.append(method_body)
    return methods

def extract_relevant_lines(method_code):
    """
    巨大なメソッド全体ではなく、
    テストケース作成などに必要な箇所(メソッドシグネチャおよび
    if/for/while/case/catch/三項演算子、論理演算子などの決定点を含む行)だけを抽出します。
    """
    lines = method_code.splitlines()
    relevant_lines = []
    # 制御フローに関わるキーワード(最初の非空行=シグネチャは必ず含む)
    decision_keywords = ['if(', 'if (', 'for(', 'for (', 'while(', 'while (',
                         'case ', 'catch(', 'catch (', '?', '&&', '||']
    signature_included = False
    for line in lines:
        stripped = line.strip()
        if not signature_included and stripped:
            relevant_lines.append(stripped)
            signature_included = True
            continue
        for kw in decision_keywords:
            if kw in stripped:
                relevant_lines.append(stripped)
                break
    return relevant_lines

def generate_cfg(method_code):
    """
    抽出されたメソッド内の必要な行から単純な制御フローグラフ(CFG)を作成します。
    ノードは "Entry" → [各抽出行] → "Exit" とし、基本的な線形エッジに加え、
    決定点ノードから 2 つ先のノードへ追加エッジ(True 分岐)を張ります。
    """
    lines = extract_relevant_lines(method_code)
    nodes = ["Entry"] + lines + ["Exit"]
    G = nx.DiGraph()
    for node in nodes:
        G.add_node(node)
    # 基本の線形エッジを追加
    for i in range(len(nodes) - 1):
        G.add_edge(nodes[i], nodes[i+1])
    # 決定点(if, for, while, etc.)から 2 つ先のノードへ追加エッジ(True 分岐)を追加
    decision_keywords = ['if', 'for', 'while', 'case', 'catch', '?', '&&', '||']
    for i, node in enumerate(nodes):
        if any(kw in node for kw in decision_keywords):
            if i + 2 < len(nodes):
                G.add_edge(node, nodes[i+2])
    return G

def generate_dot(G):
    """
    networkx のグラフ G を DOT 言語の文字列に変換して返します。
    """
    dot_str = "digraph CFG {\n"
    for node in G.nodes():
        # ノードラベルはダブルクォートで囲む(エスケープはここでは単純化)
        dot_str += f'    "{node}" [label="{node}"];\n'
    for u, v in G.edges():
        dot_str += f'    "{u}" -> "{v}";\n'
    dot_str += "}\n"
    return dot_str

def validate_dot(dot_code):
    """
    生成された DOT コードが正しくパースできるかを pydot を使って検証します。
    正常なら (True, "OK")、エラーがあれば (False, エラーメッセージ) を返します。
    """
    try:
        graphs = pydot.graph_from_dot_data(dot_code)
        if not graphs or len(graphs) == 0:
            return False, "DOTコードからグラフが生成されませんでした。"
        return True, "DOTコードは正しいです。"
    except Exception as e:
        return False, str(e)

def write_fix_report(report_filename, output_filename, error_message):
    """
    エラー発生時に、修正箇所・修正内容を指摘したレポートファイルを出力します。
    レポートは、対象の出力ファイル名とエラーメッセージ、考えられる原因および修正案を含みます。
    """
    report_text = (
        f"--- 修正レポート ---\n"
        f"対象ファイル: {output_filename}\n\n"
        f"【エラーメッセージ】\n{error_message}\n\n"
        f"【考えられる原因と修正案】\n"
        f"1. generate_dot 関数内で、ノードラベルやエッジの記述に誤りがある可能性があります。\n"
        f"   - 各ノードラベルはダブルクォートで囲む必要があります。\n"
        f"   - エッジの形式は \"source\" -> \"target\"; という形式にする必要があります。\n"
        f"2. ノード名に特殊文字や未エスケープの文字が含まれていないか確認してください。\n"
        f"3. DOT 言語の構文(セミコロンの付与、引用符の対応など)を再確認し、必要に応じて generate_dot 関数の実装を修正してください。\n\n"
        f"【対象箇所】\n"
        f"- generate_dot 関数\n\n"
        f"上記の点を確認・修正してください。"
    )
    with open(report_filename, 'w', encoding='utf-8') as f:
        f.write(report_text)

def main(java_filename):
    """
    指定した Java ファイルから各メソッドを抽出し、
    各メソッドの制御フローグラフ(CFG)を DOT 言語のテキストファイルとして出力し、
    さらに生成された DOT コードが正しいか検証し、エラーがあれば修正内容を指摘したレポートファイルを出力します。
    """
    with open(java_filename, 'r', encoding='utf-8') as f:
        code = f.read()
    code = remove_comments_and_strings(code)
    methods = extract_methods(code)
    if not methods:
        print("Javaファイルからメソッドが見つかりませんでした。")
        return
    base_name = os.path.splitext(os.path.basename(java_filename))[0]
    for idx, method in enumerate(methods, start=1):
        G = generate_cfg(method)
        dot_code = generate_dot(G)
        output_filename = f"{base_name}_method_{idx}.dot"
        with open(output_filename, 'w', encoding='utf-8') as f:
            f.write(dot_code)
        # DOTコードの検証
        valid, message = validate_dot(dot_code)
        if valid:
            print(f"Method {idx}: DOT file generated and validated successfully: {output_filename}")
        else:
            print(f"Method {idx}: DOT file generated but validation failed: {output_filename}")
            print("Error:", message)
            # 修正レポートファイルを出力
            report_filename = f"fix_report_{base_name}_method_{idx}.txt"
            write_fix_report(report_filename, output_filename, message)
            print(f"Fix report generated: {report_filename}")

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python java_cfg_dot.py <JavaFile>")
        sys.exit(1)
    java_filename = sys.argv[1]
    main(java_filename)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です