C言語の構造体を構造分析したい〜静的解析としての doxygen
ソースコードからドキュメント生成するツールとしていにしえの時代から有名な doxygen には xml 出力があるので、静的解析ツールの補助として いろいろと活用ができそう。
使い方
$ doxygen -g
で生成された Doxyfile ないしすでにある Doxyfile において
GENERATE_XML
を YES
にして、
# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
# captures the structure of the code including all documentation.
# The default value is: NO.
GENERATE_XML = YES
ドキュメントをつくるように doxygen を実行すると xml が出力される。 html 出力などほかの出力も有効にしているならば、一緒に出力される。
$ doxygen Doxyfile
具体例
構造体 struct _Foo
にどんなメンバがあるかを構造分析したい:
// foobar.h
typedef enum _BarType {
BAR_TYPE_A,
BAR_TYPE_B
} BarType;
#define NUM_B 3
typedef union _Bar {
uint a;
uint b[NUM_B];
} Bar;
typedef struct _Foo {
BarType bartype;
Bar bar;
} Foo;
doxygen の実行
$ ls
foobar.h
$ doxygen -g
Configuration file 'Doxyfile' created.
Now edit the configuration file and enter
doxygen Doxyfile
to generate the documentation for your project
$ vi Doxyfile
$ doxygen Doxyfile
(略)
$ ls -F
Doxyfile foobar.h html/ latex/ xml/
$ ls -F xml
combine.xslt foobar_8h.xml index.xsd union__Bar.xml
compound.xsd index.xml struct__Foo.xml
構造体 struct _Foo
の構造を見るには struct__Foo.xml
を見るという風な単純な構造となっている:
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<doxygen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="compound.xsd" version="1.8.6">
<compounddef id="struct__Foo" kind="struct" prot="public">
<compoundname>_Foo</compoundname>
<sectiondef kind="public-attrib">
<memberdef kind="variable" id="struct__Foo_1a8f8833abcfaa7b946be2b13d9e4ef73d" prot="public" static="no" mutable="no">
<type>BarType</type>
<definition>BarType _Foo::bartype</definition>
<argsstring></argsstring>
<name>bartype</name>
<briefdescription>
</briefdescription>
<detaileddescription>
</detaileddescription>
<inbodydescription>
</inbodydescription>
<location file="/tmp/doxytest/foobar.h" line="12" column="1" bodyfile="/tmp/doxytest/foobar.h" bodystart="12" bodyend="-1"/>
</memberdef>
<memberdef kind="variable" id="struct__Foo_1a21f3b7dab2816e7ea7938baf94e643c8" prot="public" static="no" mutable="no">
<type><ref refid="union__Bar" kindref="compound">Bar</ref></type>
<definition>Bar _Foo::bar</definition>
<argsstring></argsstring>
<name>bar</name>
<briefdescription>
</briefdescription>
<detaileddescription>
</detaileddescription>
<inbodydescription>
</inbodydescription>
<location file="/tmp/doxytest/foobar.h" line="13" column="1" bodyfile="/tmp/doxytest/foobar.h" bodystart="13" bodyend="-1"/>
</memberdef>
</sectiondef>
<briefdescription>
</briefdescription>
<detaileddescription>
</detaileddescription>
<collaborationgraph>
<node id="4">
<label>_Bar</label>
<link refid="union__Bar"/>
</node>
<node id="3">
<label>_Foo</label>
<link refid="struct__Foo"/>
<childnode refid="4" relation="usage">
<edgelabel>bar</edgelabel>
</childnode>
</node>
</collaborationgraph>
<location file="/tmp/doxytest/foobar.h" line="11" column="1" bodyfile="/tmp/doxytest/foobar.h" bodystart="11" bodyend="14"/>
<listofallmembers>
<member refid="struct__Foo_1a21f3b7dab2816e7ea7938baf94e643c8" prot="public" virt="non-virtual"><scope>_Foo</scope><name>bar</name></member>
<member refid="struct__Foo_1a8f8833abcfaa7b946be2b13d9e4ef73d" prot="public" virt="non-virtual"><scope>_Foo</scope><name>bartype</name></member>
</listofallmembers>
</compounddef>
</doxygen>
memberdef
要素の中身を探していけば良い。他のファイルにその定義がある場合は ref
要素
があるようなので再帰的に処理を行えば探せる。
というわけで、さくっと doxygen の xml 出力を利用して、C言語構造体の構造を JSON で吐き出すスクリプトを書いてみた。 100 行未満で書けるのだから doxygen の xml 出力は強力である。
#!/usr/bin/ruby
# vim: filetype=ruby fileencoding=UTF-8 shiftwidth=2 tabstop=2 autoindent expandtab
#
# MIT License
require 'rexml/document'
require 'json'
class DoxyStructXml
def initialize
@structs = {}
end
attr_reader :structs
def load_doxy_struct_xml_file(file)
File.open(file, 'r') do |f|
doc = REXML::Document.new(f)
id = doc.elements['doxygen/compounddef'].attributes['id']
members = []
doc.elements.each('doxygen/compounddef//memberdef') do |elem|
member = {}
if elem.elements['type/ref']
member[:typeref] = elem.elements['type/ref'].attributes['refid']
member[:type] = elem.elements['type/ref'].text
else
member[:type] = elem.elements['type'].text
end
member[:argsstring] = elem.elements['argsstring'].text
member[:name] = elem.elements['name'].text
members << member
end
@structs[id] = {
:name => doc.elements['doxygen/compounddef/compoundname'].text,
:members => members
}
end
end
def data(struct_id)
d = {}
d[:id] = struct_id
d[:name] = @structs[struct_id][:name]
d[:members] = []
@structs[struct_id][:members].each do |member|
m = {}
m[:name] = member[:name]
m[:type] = member[:type]
m[:name] << member[:argsstring] if member[:argsstring]
if member[:typeref]
m[:type_data] = data(member[:typeref])
end
d[:members] << m
end
d
end
end
if $0 == __FILE__
doxy_struct_xml = DoxyStructXml.new
Dir.glob('{struct,union}_*.xml') do |file|
doxy_struct_xml.load_doxy_struct_xml_file(file)
end
struct_id = ARGV[0]
unless struct_id
$stderr << "Usage: ruby #{$0} struct_id\n"
$stderr << "struct_id: "
doxy_struct_xml.structs.each do |key,value|
$stderr << key << " "
end
$stderr << "\n\n"
exit 1
end
if doxy_struct_xml.structs[struct_id]
$stdout << JSON.dump(doxy_struct_xml.data(struct_id))
$stdout << "\n"
else
$stderr << "#{struct_id} does not found"
exit 1
end
end
列挙体が扱えないことが残念なことこの上ない。
$ ruby doxy_struct_xml_to_json.rb
Usage: ruby doxy_struct_xml_to_json.rb struct_id
struct_id: struct__Foo union__Bar
$ ruby doxy_struct_xml_to_json.rb struct__Foo
{"id":"struct__Foo","name":"_Foo","members":[{"name":"bartype","type":"BarType"},{"name":"bar","type":"Bar","type_data":{"id":"union__Bar","name":"_Bar","members":[{"name":"a","type":"uint"},{"name":"b[NUM_B]","type":"uint"}]}}]}