summaryrefslogtreecommitdiffstats
path: root/content/posts/WIP-how-bsd-authentication-works/gen_dot.rb
blob: 9f71876c0bff161131ddc67917c6491bf2344dbd (plain) (blame)
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