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)
カテゴリー