ソースコードからドキュメント生成するツールとしていにしえの時代から有名な doxygen には xml 出力があるので、静的解析ツールの補助として いろいろと活用ができそう。

使い方

$ doxygen -g

で生成された Doxyfile ないしすでにある Doxyfile において GENERATE_XMLYES にして、

# 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"}]}}]}

参考文献