1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
#!/usr/bin/env ruby
# frozen_string_literal: true
# Copyright (C) 2021 Dante Catalfamo
# SPDX-License-Identifier: MIT
require 'digest'
require 'set'
SOURCE_DIR = File.join Dir.home, 'src', 'github.com', 'openbsd', 'src', 'lib', 'libc', 'gen'
FILENAMES = %w[authenticate.c auth_subr.c login_cap.c].freeze
FUNCTION_REGEX = /^\w.*?$\n(?!DEF)(\w*)\(.*?\)\n\{(.*?)^\}/m.freeze
ONELINE_FUNCTION_REFGEX = /^\w.*?(\w*)\(.*?\).*?\{(.*?)\}/.freeze
CALL_REGEX = /[^\n](\w+)\(.*?\)/.freeze
class FunctionDigraph
attr_accessor :pairs, :subgraphs
class Subgraph
attr_accessor :name, :label, :functions
def initialize(name, label)
@name = name
@label = label
@functions = []
end
def emit
puts "subgraph cluster_#{name} {"
puts "label = \"#{label}\""
functions.each { |f| puts f unless f == 'DEF_WEAK' }
puts '}'
end
end
class Pair
attr_accessor :to, :from
def initialize(from, to)
@from = from
@to = to
end
def emit
puts "#{from} -> #{to} [color = \"##{color}\"]"
end
def color
Digest::MD5.hexdigest(from)[..5]
end
end
def initialize
@pairs = []
@subgraphs = []
end
def emit
puts 'digraph G {'
puts 'rankdir=LR'
puts 'splines=ortho'
puts 'graph [pad="0.5", nodesep="0.5", ranksep="1.5"]'
all_functions = Set.new
@subgraphs.each { |s| all_functions.merge(s.functions) }
@subgraphs.each(&:emit)
@pairs.uniq { |p| [p.to, p.from] }.each do |p|
p.emit if all_functions.include?(p.to)
end
puts '}'
end
def parse_files(filenames)
filenames.each do |filename|
contents = File.read(filename)
basename = File.basename filename
subgraph = Subgraph.new(basename.gsub(/\..*/, ''), basename)
functions = contents.scan(FUNCTION_REGEX)
oneliners = contents.scan(ONELINE_FUNCTION_REFGEX)
functions.concat(oneliners) unless oneliners.empty?
functions.each do |function|
function_name = function[0]
function_body = function[1]
subgraph.functions << function_name
function_body.scan(CALL_REGEX) do |call|
@pairs << Pair.new(function_name, call[0])
end
end
@subgraphs << subgraph
end
end
end
fg = FunctionDigraph.new
files = FILENAMES.map { |f| File.join(SOURCE_DIR, f) }
fg.parse_files files
fg.emit
|