rubycc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +56 -0
  3. data/LICENSE.txt +21 -0
  4. data/NOTICE +52 -0
  5. data/README.md +208 -0
  6. data/data/README.md +117 -0
  7. data/data/r10_corpus_scan.json +4082 -0
  8. data/data/r10_manual_classification.json +5342 -0
  9. data/data/r10_verification_m4a.json +1531 -0
  10. data/data/r10_verification_m4b.json +1818 -0
  11. data/data/r10_verification_m4c.json +1489 -0
  12. data/data/r10_verification_m4d.json +318 -0
  13. data/data/r10_verification_rbs.json +192 -0
  14. data/data/verified_gems.json +397 -0
  15. data/exe/rmake +16 -0
  16. data/exe/rubycc +9 -0
  17. data/exe/rubycc-ar +114 -0
  18. data/exe/rubycc-doctor +14 -0
  19. data/exe/rubycc-pkgconf +8 -0
  20. data/include/float.h +87 -0
  21. data/include/iso646.h +18 -0
  22. data/include/libc/alloca.h +18 -0
  23. data/include/libc/arpa/inet.h +61 -0
  24. data/include/libc/assert.h +43 -0
  25. data/include/libc/dirent.h +72 -0
  26. data/include/libc/dlfcn.h +58 -0
  27. data/include/libc/features.h +205 -0
  28. data/include/libc/glibc/aarch64/ctype.h +108 -0
  29. data/include/libc/glibc/aarch64/endian.h +54 -0
  30. data/include/libc/glibc/aarch64/errno.h +154 -0
  31. data/include/libc/glibc/aarch64/fcntl.h +144 -0
  32. data/include/libc/glibc/aarch64/inttypes.h +182 -0
  33. data/include/libc/glibc/aarch64/limits.h +63 -0
  34. data/include/libc/glibc/aarch64/pthread.h +131 -0
  35. data/include/libc/glibc/aarch64/setjmp.h +84 -0
  36. data/include/libc/glibc/aarch64/stdint.h +174 -0
  37. data/include/libc/glibc/aarch64/sys/epoll.h +88 -0
  38. data/include/libc/glibc/aarch64/sys/fcntl.h +19 -0
  39. data/include/libc/glibc/aarch64/sys/select.h +75 -0
  40. data/include/libc/glibc/aarch64/sys/stat.h +136 -0
  41. data/include/libc/glibc/aarch64/sys/syscall.h +167 -0
  42. data/include/libc/glibc/aarch64/sys/time.h +74 -0
  43. data/include/libc/glibc/aarch64/sys/types.h +127 -0
  44. data/include/libc/glibc/aarch64/time.h +113 -0
  45. data/include/libc/glibc/x86_64/ctype.h +108 -0
  46. data/include/libc/glibc/x86_64/endian.h +54 -0
  47. data/include/libc/glibc/x86_64/errno.h +154 -0
  48. data/include/libc/glibc/x86_64/fcntl.h +134 -0
  49. data/include/libc/glibc/x86_64/inttypes.h +182 -0
  50. data/include/libc/glibc/x86_64/limits.h +61 -0
  51. data/include/libc/glibc/x86_64/pthread.h +111 -0
  52. data/include/libc/glibc/x86_64/setjmp.h +82 -0
  53. data/include/libc/glibc/x86_64/stdint.h +163 -0
  54. data/include/libc/glibc/x86_64/sys/epoll.h +91 -0
  55. data/include/libc/glibc/x86_64/sys/fcntl.h +19 -0
  56. data/include/libc/glibc/x86_64/sys/select.h +75 -0
  57. data/include/libc/glibc/x86_64/sys/stat.h +130 -0
  58. data/include/libc/glibc/x86_64/sys/syscall.h +188 -0
  59. data/include/libc/glibc/x86_64/sys/time.h +74 -0
  60. data/include/libc/glibc/x86_64/sys/types.h +123 -0
  61. data/include/libc/glibc/x86_64/time.h +113 -0
  62. data/include/libc/grp.h +50 -0
  63. data/include/libc/langinfo.h +126 -0
  64. data/include/libc/link.h +18 -0
  65. data/include/libc/locale.h +83 -0
  66. data/include/libc/math.h +191 -0
  67. data/include/libc/netinet/in.h +126 -0
  68. data/include/libc/netinet/tcp.h +44 -0
  69. data/include/libc/poll.h +46 -0
  70. data/include/libc/pwd.h +58 -0
  71. data/include/libc/regex.h +53 -0
  72. data/include/libc/sched.h +35 -0
  73. data/include/libc/signal.h +204 -0
  74. data/include/libc/stdio.h +157 -0
  75. data/include/libc/stdlib.h +92 -0
  76. data/include/libc/string.h +74 -0
  77. data/include/libc/strings.h +25 -0
  78. data/include/libc/sys/cdefs.h +126 -0
  79. data/include/libc/sys/inotify.h +111 -0
  80. data/include/libc/sys/ioctl.h +36 -0
  81. data/include/libc/sys/mman.h +65 -0
  82. data/include/libc/sys/param.h +41 -0
  83. data/include/libc/sys/resource.h +109 -0
  84. data/include/libc/sys/socket.h +167 -0
  85. data/include/libc/sys/statfs.h +82 -0
  86. data/include/libc/sys/timerfd.h +55 -0
  87. data/include/libc/sys/uio.h +40 -0
  88. data/include/libc/sys/un.h +25 -0
  89. data/include/libc/sys/utsname.h +35 -0
  90. data/include/libc/sys/wait.h +135 -0
  91. data/include/libc/termios.h +179 -0
  92. data/include/libc/unistd.h +194 -0
  93. data/include/stdalign.h +16 -0
  94. data/include/stdarg.h +31 -0
  95. data/include/stdatomic.h +158 -0
  96. data/include/stdbool.h +15 -0
  97. data/include/stdckdint.h +28 -0
  98. data/include/stddef.h +60 -0
  99. data/include/stdnoreturn.h +18 -0
  100. data/include/x86intrin.h +16 -0
  101. data/lib/rubycc/backend/aarch64.rb +1724 -0
  102. data/lib/rubycc/backend/x86_64.rb +1369 -0
  103. data/lib/rubycc/compile_error.rb +36 -0
  104. data/lib/rubycc/compiler.rb +305 -0
  105. data/lib/rubycc/doctor/builder.rb +151 -0
  106. data/lib/rubycc/doctor/cli.rb +190 -0
  107. data/lib/rubycc/doctor/fetcher.rb +84 -0
  108. data/lib/rubycc/doctor/gemfile.rb +115 -0
  109. data/lib/rubycc/doctor/verified_gems.rb +114 -0
  110. data/lib/rubycc/doctor.rb +16 -0
  111. data/lib/rubycc/driver.rb +463 -0
  112. data/lib/rubycc/front/ast.rb +528 -0
  113. data/lib/rubycc/front/constant_evaluator.rb +631 -0
  114. data/lib/rubycc/front/initializer_resolver.rb +592 -0
  115. data/lib/rubycc/front/lexeme_reader.rb +460 -0
  116. data/lib/rubycc/front/lexer.rb +232 -0
  117. data/lib/rubycc/front/parser.rb +4122 -0
  118. data/lib/rubycc/front/token.rb +49 -0
  119. data/lib/rubycc/ir/call_convention.rb +486 -0
  120. data/lib/rubycc/ir/generator.rb +6036 -0
  121. data/lib/rubycc/ir/ir.rb +417 -0
  122. data/lib/rubycc/link/compat_runtime.rb +89 -0
  123. data/lib/rubycc/link/errors.rb +13 -0
  124. data/lib/rubycc/link/executable_linker.rb +405 -0
  125. data/lib/rubycc/link/library_resolver.rb +437 -0
  126. data/lib/rubycc/link/partial_linker.rb +546 -0
  127. data/lib/rubycc/link/shared_linker.rb +1732 -0
  128. data/lib/rubycc/mkmf_shim.rb +83 -0
  129. data/lib/rubycc/objfile/ar_archive.rb +437 -0
  130. data/lib/rubycc/objfile/elf_reader.rb +646 -0
  131. data/lib/rubycc/objfile/elf_writer.rb +891 -0
  132. data/lib/rubycc/objfile/relocatable_writer.rb +376 -0
  133. data/lib/rubycc/pkgconf/cli.rb +139 -0
  134. data/lib/rubycc/pkgconf/errors.rb +39 -0
  135. data/lib/rubycc/pkgconf/model.rb +28 -0
  136. data/lib/rubycc/pkgconf/parser.rb +109 -0
  137. data/lib/rubycc/pkgconf/pkgconf.rb +15 -0
  138. data/lib/rubycc/pkgconf/resolver.rb +70 -0
  139. data/lib/rubycc/pkgconf/search_path.rb +38 -0
  140. data/lib/rubycc/pkgconf/system_path_filter.rb +131 -0
  141. data/lib/rubycc/preprocess/constant_expression.rb +219 -0
  142. data/lib/rubycc/preprocess/glibc_version.rb +124 -0
  143. data/lib/rubycc/preprocess/pp_token.rb +62 -0
  144. data/lib/rubycc/preprocess/preprocessor.rb +2020 -0
  145. data/lib/rubycc/preprocess/scanner.rb +290 -0
  146. data/lib/rubycc/preprocess/token_converter.rb +157 -0
  147. data/lib/rubycc/rmake/cli.rb +143 -0
  148. data/lib/rubycc/rmake/errors.rb +71 -0
  149. data/lib/rubycc/rmake/executor.rb +818 -0
  150. data/lib/rubycc/rmake/expander.rb +251 -0
  151. data/lib/rubycc/rmake/makefile.rb +352 -0
  152. data/lib/rubycc/rmake/model.rb +137 -0
  153. data/lib/rubycc/rmake/parser.rb +226 -0
  154. data/lib/rubycc/rmake/rmake.rb +15 -0
  155. data/lib/rubycc/type.rb +1236 -0
  156. data/lib/rubycc/version.rb +5 -0
  157. data/lib/rubycc.rb +36 -0
  158. data/lib/rubygems_plugin.rb +102 -0
  159. metadata +219 -0
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+ require "rubygems/package"
7
+
8
+ module Rubycc
9
+ module Doctor
10
+ # Downloads a gem's package from rubygems.org and reads its real
11
+ # specification. This is the authoritative source of a gem's `extensions`
12
+ # list: neither the versions API nor the "quick" (light) gemspec carries the
13
+ # extensions field, so the only reliable way to know whether a gem has a C
14
+ # extension is to read the full .gem's embedded gemspec. That full package is
15
+ # also exactly what an on-the-fly build needs, so probe and build share it.
16
+ #
17
+ # Everything here touches the network; callers treat any failure (offline,
18
+ # 404, timeout) as "unknown" and degrade gracefully.
19
+ class Fetcher
20
+ HOST = "https://rubygems.org"
21
+ # A .gem is a few hundred KB at most for the extension gems we target; this
22
+ # only guards against a hung connection.
23
+ OPEN_TIMEOUT = 15
24
+ READ_TIMEOUT = 60
25
+
26
+ class FetchError < StandardError; end
27
+
28
+ def initialize(cache_dir)
29
+ @cache_dir = cache_dir
30
+ end
31
+
32
+ # Resolve the latest released (non-prerelease) version of +name+ via the v1
33
+ # API. Returns a version string, or nil when the lookup fails.
34
+ def latest_version(name)
35
+ body = get("#{HOST}/api/v1/gems/#{name}.json")
36
+ JSON.parse(body)["version"]
37
+ rescue StandardError
38
+ nil
39
+ end
40
+
41
+ # Download NAME-VERSION.gem into the cache and return its local path. Raises
42
+ # FetchError on any network failure so the caller can report "unknown".
43
+ def download(name, version)
44
+ dest = File.join(@cache_dir, "#{name}-#{version}.gem")
45
+ return dest if File.file?(dest) && File.size(dest).positive?
46
+
47
+ body = get("#{HOST}/gems/#{name}-#{version}.gem")
48
+ File.binwrite(dest, body)
49
+ dest
50
+ rescue StandardError => e
51
+ raise FetchError, e.message
52
+ end
53
+
54
+ # The Gem::Specification embedded in a downloaded .gem.
55
+ def spec(gem_path)
56
+ Gem::Package.new(gem_path).spec
57
+ end
58
+
59
+ private
60
+
61
+ # A GET that follows rubygems.org's CDN redirects and returns the body,
62
+ # raising on any non-success status.
63
+ def get(url, limit = 5)
64
+ raise FetchError, "too many redirects" if limit.zero?
65
+
66
+ uri = URI(url)
67
+ http = Net::HTTP.new(uri.host, uri.port)
68
+ http.use_ssl = uri.scheme == "https"
69
+ http.open_timeout = OPEN_TIMEOUT
70
+ http.read_timeout = READ_TIMEOUT
71
+
72
+ res = http.get(uri.request_uri)
73
+ case res
74
+ when Net::HTTPSuccess
75
+ res.body
76
+ when Net::HTTPRedirection
77
+ get(URI.join(url, res["location"]).to_s, limit - 1)
78
+ else
79
+ raise FetchError, "#{url} -> HTTP #{res.code}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubycc
4
+ module Doctor
5
+ # Extracts the gems an application uses from its Gemfile.lock (preferred, for
6
+ # exact resolved versions) or, failing that, a bare Gemfile.
7
+ #
8
+ # Bundler's Gemfile is a Ruby DSL; evaluating it safely means sandboxing
9
+ # arbitrary code, which is heavy and a security hazard. The lock file, by
10
+ # contrast, is a fixed indented text format with no code in it, so parsing it
11
+ # is both safe and gives the *resolved* versions and the full transitive
12
+ # dependency closure (its `specs:` section is exactly that closure). rubycc
13
+ # doctor therefore prefers the lock and treats it as authoritative.
14
+ #
15
+ # When only a Gemfile is present it is parsed naively — `gem "name"` lines are
16
+ # read with a regexp, never `eval`'d. That yields direct dependencies only
17
+ # (no closure) and often no version; callers surface that as a limitation.
18
+ module Gemfile
19
+ # One resolved gem: its name, version (nil when unknown), the names of its
20
+ # direct runtime dependencies (from the lock; empty otherwise) and the
21
+ # source section it came from (:gem for rubygems.org, :path/:git for local
22
+ # or VCS gems that cannot be fetched from rubygems.org).
23
+ Entry = Struct.new(:name, :version, :dependencies, :source, keyword_init: true)
24
+
25
+ # The parsed picture the CLI drives off: the list of gem entries, the names
26
+ # of the directly-declared dependencies, and where the data came from
27
+ # (:lock exact versions, or :gemfile direct-only best effort).
28
+ Result = Struct.new(:entries, :direct, :origin, :path, keyword_init: true)
29
+
30
+ module_function
31
+
32
+ # Load gem information from +dir+ (or an explicit --gemfile path). A
33
+ # Gemfile.lock next to the Gemfile is preferred; otherwise the Gemfile
34
+ # itself is parsed. Returns a Result, or nil when neither file exists.
35
+ def load(gemfile_path)
36
+ lock = "#{gemfile_path}.lock"
37
+ if File.file?(lock)
38
+ parse_lock(File.read(lock), path: lock)
39
+ elsif File.file?(gemfile_path)
40
+ parse_gemfile(File.read(gemfile_path), path: gemfile_path)
41
+ end
42
+ end
43
+
44
+ # Parse the text of a Gemfile.lock. The format is a sequence of sections
45
+ # introduced by an unindented header (GEM, PATH, GIT, DEPENDENCIES, ...).
46
+ # Inside GEM/PATH/GIT a `specs:` line is followed by the resolved gems at
47
+ # 4-space indent (`name (version)`), each optionally trailed by its own
48
+ # dependencies at 6-space indent (`dep (constraint)`). DEPENDENCIES lists
49
+ # the directly-declared gems at 2-space indent.
50
+ def parse_lock(text, path: nil)
51
+ entries = []
52
+ direct = []
53
+ section = nil # current top-level section header
54
+ source = nil # :gem / :path / :git for the current specs block
55
+ in_specs = false
56
+ current = nil # the Entry whose dependency lines we are reading
57
+
58
+ text.each_line do |raw|
59
+ line = raw.chomp
60
+ next if line.strip.empty?
61
+
62
+ if line == line.lstrip # unindented -> a section header
63
+ section = line
64
+ source = { "GEM" => :gem, "PATH" => :path, "GIT" => :git }[section]
65
+ in_specs = false
66
+ current = nil
67
+ next
68
+ end
69
+
70
+ if section == "DEPENDENCIES"
71
+ direct << line.strip.sub(/[!<>=~ ].*\z/, "").strip
72
+ next
73
+ end
74
+
75
+ if source
76
+ if line.strip == "specs:"
77
+ in_specs = true
78
+ next
79
+ end
80
+ next unless in_specs
81
+
82
+ indent = line[/\A */].length
83
+ if indent == 4 && (m = line.strip.match(/\A(\S+) \(([^)]+)\)\z/))
84
+ current = Entry.new(name: m[1], version: m[2].split.first, dependencies: [], source: source)
85
+ entries << current
86
+ elsif indent >= 6 && current && (m = line.strip.match(/\A(\S+)/))
87
+ current.dependencies << m[1]
88
+ end
89
+ end
90
+ end
91
+
92
+ Result.new(entries: entries, direct: direct.uniq, origin: :lock, path: path)
93
+ end
94
+
95
+ # Parse a bare Gemfile naively: pick up every `gem "name"` (or 'name')
96
+ # declaration and an optional literal version argument. This never evaluates
97
+ # the file, so conditionals, variables and computed names are invisible —
98
+ # the caller reports the reduced fidelity. Group/platform blocks are not
99
+ # tracked; every declared gem is returned.
100
+ def parse_gemfile(text, path: nil)
101
+ entries = []
102
+ text.each_line do |raw|
103
+ line = raw.sub(/#.*/, "")
104
+ m = line.match(/\bgem\s*\(?\s*["']([^"']+)["']\s*(?:,\s*["']([^"']+)["'])?/)
105
+ next unless m
106
+
107
+ version = m[2]&.sub(/\A[~><=!\s]+/, "")&.strip
108
+ version = nil if version && version.empty?
109
+ entries << Entry.new(name: m[1], version: version, dependencies: [], source: :gem)
110
+ end
111
+ Result.new(entries: entries.uniq(&:name), direct: entries.map(&:name).uniq, origin: :gemfile, path: path)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Rubycc
6
+ module Doctor
7
+ # The build-verified gem database (data/verified_gems.json), the primary
8
+ # reference rubycc doctor consults before ever attempting a build. A gem whose
9
+ # resolved version satisfies one of the recorded `versions` entries is reported
10
+ # as verified with no network access and no build.
11
+ #
12
+ # The schema is documented in data/README.md. One gem is one entry, and an
13
+ # entry holds a list of verifications -- one per environment it was confirmed
14
+ # in, in insertion (oldest-first) order:
15
+ #
16
+ # { "json": { "verifications": [ { "versions": ["2.21.1"],
17
+ # "environment": "glibc x86_64 / ruby 3.4.5",
18
+ # "verified_at": "2026-07-17",
19
+ # "evidence": "..." } ],
20
+ # "notes": "..." } }
21
+ #
22
+ # `versions` sits inside each record rather than once at the entry level
23
+ # because the versions actually exercised may differ per environment; hoisting
24
+ # them would claim more than was measured. The absence of a record is itself
25
+ # the statement that the gem is unverified in that environment.
26
+ #
27
+ # Version entries are matched with Gem::Requirement, so both exact pins
28
+ # ("2.21.1") and ranges (">= 1.8, < 2") work.
29
+ class VerifiedGems
30
+ # data/verified_gems.json relative to this file (lib/rubycc/doctor/).
31
+ DEFAULT_PATH = File.expand_path("../../../data/verified_gems.json", __dir__)
32
+
33
+ # One environment's verification of a gem, mirroring the JSON schema.
34
+ Verification = Struct.new(:versions, :environment, :verified_at, :evidence, keyword_init: true)
35
+
36
+ # A verified-gem entry: the gem's name, every verification recorded for it,
37
+ # and the human-owned notes.
38
+ Record = Struct.new(:name, :verifications, :notes, keyword_init: true) do
39
+ # Every version any environment verified, de-duplicated and in the order
40
+ # the records list them. Callers that only ask "which versions are known
41
+ # good at all?" (the schema test, listings) want this rather than a
42
+ # per-environment breakdown.
43
+ def versions
44
+ verifications.flat_map(&:versions).uniq
45
+ end
46
+ end
47
+
48
+ # Load the database from +path+ (defaults to the shipped file).
49
+ def self.load(path = DEFAULT_PATH)
50
+ new(JSON.parse(File.read(path)))
51
+ end
52
+
53
+ def initialize(raw)
54
+ @records = {}
55
+ raw.each do |name, attrs|
56
+ verifications = Array(attrs["verifications"]).map do |v|
57
+ Verification.new(
58
+ versions: Array(v["versions"]),
59
+ environment: v["environment"],
60
+ verified_at: v["verified_at"],
61
+ evidence: v["evidence"]
62
+ )
63
+ end
64
+ @records[name] = Record.new(name: name, verifications: verifications, notes: attrs["notes"])
65
+ end
66
+ end
67
+
68
+ # All records (used by the schema test and for listing).
69
+ attr_reader :records
70
+
71
+ # The record for +name+, or nil.
72
+ def [](name)
73
+ @records[name]
74
+ end
75
+
76
+ # Whether +name+ at +version+ is verified. A nil version can never match an
77
+ # exact pin, so an unknown version is treated as not verified.
78
+ def verified?(name, version)
79
+ !match(name, version).nil?
80
+ end
81
+
82
+ # The Record that verifies +name+ at +version+, or nil. A version satisfies
83
+ # an entry when at least one of its verifications covers it -- being
84
+ # verified in any one environment is what "verified" has always meant here,
85
+ # and splitting the schema per environment must not narrow it.
86
+ def match(name, version)
87
+ record = @records[name]
88
+ return nil if record.nil? || matching_verifications(name, version).empty?
89
+
90
+ record
91
+ end
92
+
93
+ # Every Verification of +name+ whose `versions` cover +version+, in the
94
+ # order they are recorded; empty when nothing matches. This is what a caller
95
+ # asks when it needs to say *where* the version was verified, not just
96
+ # whether it was.
97
+ def matching_verifications(name, version)
98
+ record = @records[name]
99
+ return [] unless record && version
100
+
101
+ gem_version = Gem::Version.new(version)
102
+ record.verifications.select do |verification|
103
+ verification.versions.any? do |req|
104
+ # A range entry may be comma-joined (">= 1.8, < 2"); split it into the
105
+ # individual constraints Gem::Requirement expects as separate arguments.
106
+ Gem::Requirement.new(*req.split(",").map(&:strip)).satisfied_by?(gem_version)
107
+ rescue ArgumentError
108
+ false
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "doctor/gemfile"
4
+ require_relative "doctor/verified_gems"
5
+ require_relative "doctor/fetcher"
6
+ require_relative "doctor/builder"
7
+ require_relative "doctor/cli"
8
+
9
+ module Rubycc
10
+ # rubycc doctor: the adoption-check command (ROADMAP §6 "M3 完了後のツール").
11
+ # Given an application's Gemfile.lock/Gemfile it reports, per gem, whether
12
+ # rubycc can build its C extension — first from the shipped build-verified
13
+ # database, then by an on-the-spot build for anything unverified.
14
+ module Doctor
15
+ end
16
+ end