guardinari 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 695c5943a6900a961d484e6f85093a42a5ce1dd9c3c5922135694158eba58de4
4
+ data.tar.gz: fa9dbf6e5fed9ca33c5c898db23144107d8dc295750a0fd9bea86d37caf4c5ac
5
+ SHA512:
6
+ metadata.gz: c1f4601be00b785494de79248df3c3453843682e2b2128b835bfc870b011637c9e61bca692b310285804ad70dabe5b5b0523b0b6f6ed787c0d8f460b551fc96f
7
+ data.tar.gz: 2ef0c623f59bdaeb23e614f90bde9e66206f67b2a08a83860c7cf7b6738faa3e4907930a39401a50c01be1a68d6ba22cf4c0c78037075f60f869b39e7b864513
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ NewCops: enable
4
+
5
+ Style/StringLiterals:
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ EnforcedStyle: double_quotes
10
+
11
+ Style/Documentation:
12
+ Enabled: false
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Matthew Eagar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # Guardinari
2
+
3
+ Guardinari finds [Kaminari](https://github.com/kaminari/kaminari) queries containing broken pagination, that is, queries that have a `LIMIT` and `OFFSET` but no `ORDER BY`.
4
+
5
+ Kaminari mentions this in its README:
6
+
7
+ > Kaminari does not add an order to queries. To avoid surprises, you should generally include an order in paginated queries.
8
+
9
+ However, it's pretty easy in a non-trivial Rails app to wind up with a paginated query that does _not_ have an order-by clause. When this happens, the ordering of results is non-deterministic, and causes surprising behavior.
10
+
11
+ See, for example:
12
+
13
+ * https://github.com/kaminari/kaminari/issues/1040
14
+ * https://github.com/kaminari/kaminari/pull/1054
15
+
16
+ Guardinari implements a simple Flex-based lexer to find broken queries without brittle regular expressions that would be prone to false-positives and false-negatives.
17
+
18
+ It's written in C because it's ~70x faster than the equivalent `StringScanner`-based parser, written in pure Ruby - fast enough to run in dev, test, _and_ prod if you so desire.
19
+
20
+ > [!WARNING]
21
+ >
22
+ > Guardinari was written largely as a counter point to Aaron Patterson's [Ruby Outperforms C: Breaking the Catch-22](https://railsatscale.com/2023-08-29-ruby-outperforms-c/) and his [Rails World 2024 Keynote](https://www.youtube.com/watch?v=ZE6F3drGhA8) where he shows that pure Ruby parsers can outperform a C extension.
23
+ >
24
+ > Guardinari _massively_ outperforms the equivalent pure Ruby scanner because it eliminates back-and-forth - The C extension is called _once_, with the entire SQL statement in question; the Flex-based tokenizer performs zero allocations, processes the entire query, and returns a boolean `true`/`false` back to Ruby.
25
+ >
26
+ > This project has never run in a production environment, use with caution.
27
+
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ bundle add guardinari
33
+ ```
34
+
35
+ With Guardinari installed, you can check whether a given query is broken:
36
+
37
+ ```ruby
38
+ # Second argument are bound parametesr
39
+ Guardinari.find_broken_pagination('select * from users limit 10 offset 0', []) # => true
40
+ # or
41
+ Guardinari.find_broken_pagination('select * from users limit ? offset ?', [10, 0]) # => true
42
+ ```
43
+
44
+ To make it useful, you probably want to run _all_ your queries through Guardinari; in Rails, you can do this with an `ActiveSupport::Notifications`:
45
+
46
+
47
+ ```ruby
48
+ ActiveSupport::Notifications.subscribe('sql.active_record') do |_name, _start, _finish, _id, payload|
49
+ # "SCHEMA" queries are created by Rails, we can trust that they are correct
50
+ next if payload[:name] == 'SCHEMA'
51
+
52
+ sql = payload[:sql]
53
+ binds = payload[:binds]
54
+
55
+ # Raises a Guardinari::BrokenPagination exception
56
+ # This exception contains the raw SQL, don't render it to the user in production
57
+ raise "Broken pagination" if Guardinari.find_broken_pagination(sql, binds)
58
+
59
+ # OR, custom handling
60
+ Rails.logger.error("Broken pagination: #{sql}") if Guardinari.find_broken_pagination?(sql, binds)
61
+ end
62
+ ```
63
+
64
+ ## What constitutes "Broken"
65
+
66
+ A query is considered broken if:
67
+
68
+ - It is a `SELECT` query
69
+ - It has an explicit `LIMIT` _and_ `OFFSET`...
70
+ - but _not_ `LIMIT 1 OFFSET 0`*
71
+ - It does _not_ have an `ORDER BY`.
72
+
73
+ The `LIMIT 1 OFFSET 0` is ignored because sometimes Rails seems to generate these in certain circumstances where there is assumed to be exactly 0 or 1 result.
74
+
75
+ ## Implementation
76
+
77
+ Guardinari is a C-extension, implementing a (very) simple Flex lexer that supports a very small subset of SQL - enough to prevent false positives that would occur with a pure regex-based solution.
78
+
79
+ What we want is effectively this:
80
+
81
+ ```
82
+ broken_pagination = query.match?(/select/) && query.match?(/limit/) && query.match?(/offset/) && !query.match?(/order by/)
83
+ ```
84
+
85
+ That is, `SELECT` queries with a limit/offset, but no order.
86
+
87
+ The above regex produces false positives and false negatives on _many_ types of input:
88
+
89
+ ```sql
90
+ -- It cannot detect broken inner queries
91
+ select * from posts where user_id in (
92
+ -- this inner query is non-deterministically paginated
93
+ select id from users limit 10 offset 20
94
+ ) order by created_at;
95
+
96
+
97
+ -- It matches inside strings
98
+ update posts set body = "select * from users limit 10 offset 0";
99
+
100
+ -- It cannot handle comments
101
+ /* limit 10 offset 0 */ select * from posts;
102
+
103
+ ```
104
+
105
+ By implementing a simple lexer for the subset of SQL that we care about, these false-positives and false-negatives are avoided.
106
+
107
+ Of the _hundreds_ of SQL keywords, Guardinari's lexer is really only aware of the `select`, `limit`, `offset` `order` and `by`, while understanding how to skip comments, single- and double-quoted strings, and how to handle sub-queries.
108
+
109
+ ## Benchmarks
110
+
111
+ An equivalent pure-Ruby `StringScanner`, with and without YJIT, along side benchmarks for Guardinari's C extension, running against a 500 character long SQL `SELECT` query:
112
+
113
+
114
+
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ require "rake/extensiontask"
17
+
18
+ file "ext/guardinari/guardinari.c" => "ext/guardinari/guardinari.l" do |t|
19
+ sh "flex -o #{t.name} #{t.source}"
20
+ end
21
+
22
+ task compile: "ext/guardinari/guardinari.c"
23
+
24
+ task build: :compile
25
+
26
+ GEMSPEC = Gem::Specification.load("guardinari.gemspec")
27
+
28
+ Rake::ExtensionTask.new("guardinari", GEMSPEC) do |ext|
29
+ ext.lib_dir = "lib/guardinari"
30
+ end
31
+
32
+ task default: %i[clobber compile test rubocop]
33
+
34
+ desc "Run tests with ASan"
35
+ task test_asan: :clobber do
36
+ ENV["SANITIZE"] = "1"
37
+ Rake::Task["compile"].invoke
38
+
39
+ asan_lib = `clang -print-file-name=libclang_rt.asan_osx_dynamic.dylib`.strip
40
+ env = { "DYLD_INSERT_LIBRARIES" => asan_lib }
41
+ system(env, "ruby", "-Ilib:test", *FileList["test/**/*_test.rb"]) || exit(1)
42
+ end
@@ -0,0 +1,277 @@
1
+
2
+ SHELL = /bin/sh
3
+
4
+ # V=0 quiet, V=1 verbose. other values don't work.
5
+ V = 0
6
+ V0 = $(V:0=)
7
+ Q1 = $(V:1=)
8
+ Q = $(Q1:0=@)
9
+ ECHO1 = $(V:1=@ :)
10
+ ECHO = $(ECHO1:0=@ echo)
11
+ NULLCMD = :
12
+
13
+ #### Start of system configuration section. ####
14
+
15
+ srcdir = .
16
+ topdir = /Users/matteagar/.asdf/installs/ruby/3.4.7/include/ruby-3.4.0
17
+ hdrdir = $(topdir)
18
+ arch_hdrdir = /Users/matteagar/.asdf/installs/ruby/3.4.7/include/ruby-3.4.0/arm64-darwin25
19
+ PATH_SEPARATOR = :
20
+ VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby
21
+ prefix = $(DESTDIR)/Users/matteagar/.asdf/installs/ruby/3.4.7
22
+ rubysitearchprefix = $(rubylibprefix)/$(sitearch)
23
+ rubyarchprefix = $(rubylibprefix)/$(arch)
24
+ rubylibprefix = $(libdir)/$(RUBY_BASE_NAME)
25
+ exec_prefix = $(prefix)
26
+ vendorarchhdrdir = $(vendorhdrdir)/$(sitearch)
27
+ sitearchhdrdir = $(sitehdrdir)/$(sitearch)
28
+ rubyarchhdrdir = $(rubyhdrdir)/$(arch)
29
+ vendorhdrdir = $(rubyhdrdir)/vendor_ruby
30
+ sitehdrdir = $(rubyhdrdir)/site_ruby
31
+ rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME)
32
+ vendorarchdir = $(vendorlibdir)/$(sitearch)
33
+ vendorlibdir = $(vendordir)/$(ruby_version)
34
+ vendordir = $(rubylibprefix)/vendor_ruby
35
+ sitearchdir = $(sitelibdir)/$(sitearch)
36
+ sitelibdir = $(sitedir)/$(ruby_version)
37
+ sitedir = $(rubylibprefix)/site_ruby
38
+ rubyarchdir = $(rubylibdir)/$(arch)
39
+ rubylibdir = $(rubylibprefix)/$(ruby_version)
40
+ sitearchincludedir = $(includedir)/$(sitearch)
41
+ archincludedir = $(includedir)/$(arch)
42
+ sitearchlibdir = $(libdir)/$(sitearch)
43
+ archlibdir = $(libdir)/$(arch)
44
+ ridir = $(datarootdir)/$(RI_BASE_NAME)
45
+ modular_gc_dir = $(DESTDIR)
46
+ mandir = $(datarootdir)/man
47
+ localedir = $(datarootdir)/locale
48
+ libdir = $(exec_prefix)/lib
49
+ psdir = $(docdir)
50
+ pdfdir = $(docdir)
51
+ dvidir = $(docdir)
52
+ htmldir = $(docdir)
53
+ infodir = $(datarootdir)/info
54
+ docdir = $(datarootdir)/doc/$(PACKAGE)
55
+ oldincludedir = $(DESTDIR)/usr/include
56
+ includedir = $(SDKROOT)$(prefix)/include
57
+ runstatedir = $(localstatedir)/run
58
+ localstatedir = $(prefix)/var
59
+ sharedstatedir = $(prefix)/com
60
+ sysconfdir = $(prefix)/etc
61
+ datadir = $(datarootdir)
62
+ datarootdir = $(prefix)/share
63
+ libexecdir = $(exec_prefix)/libexec
64
+ sbindir = $(exec_prefix)/sbin
65
+ bindir = $(exec_prefix)/bin
66
+ archdir = $(rubyarchdir)
67
+
68
+
69
+ CC_WRAPPER =
70
+ CC = clang
71
+ CXX = clang++
72
+ LIBRUBY = $(LIBRUBY_SO)
73
+ LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
74
+ LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
75
+ LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework CoreFoundation $(MAINLIBS)
76
+ empty =
77
+ OUTFLAG = -o $(empty)
78
+ COUTFLAG = -o $(empty)
79
+ CSRCFLAG = $(empty)
80
+
81
+ RUBY_EXTCONF_H =
82
+ cflags = $(hardenflags) -fdeclspec $(optflags) $(debugflags) $(warnflags)
83
+ cxxflags =
84
+ optflags = -O3 -fno-fast-math
85
+ debugflags = -ggdb3
86
+ warnflags = -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wmisleading-indentation -Wundef
87
+ cppflags =
88
+ CCDLFLAGS = -fno-common
89
+ CFLAGS = $(CCDLFLAGS) $(cflags) -fno-common -pipe -fvisibility=hidden $(ARCH_FLAG)
90
+ INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
91
+ DEFS =
92
+ CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags)
93
+ CXXFLAGS = $(CCDLFLAGS) -fdeclspec $(ARCH_FLAG)
94
+ ldflags = -L. -fstack-protector-strong -L/opt/homebrew/Cellar/gmp/6.3.0/lib
95
+ dldflags = -L/opt/homebrew/Cellar/gmp/6.3.0/lib -Wl,-undefined,dynamic_lookup
96
+ ARCH_FLAG = -arch arm64
97
+ DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG)
98
+ LDSHARED = $(CC) -dynamic -bundle
99
+ LDSHAREDXX = $(CXX) -dynamic -bundle
100
+ POSTLINK = dsymutil $@ 2>/dev/null; { test -z '$(RUBY_CODESIGN)' || codesign -s '$(RUBY_CODESIGN)' $@; }
101
+ AR = ar
102
+ LD = ld
103
+ EXEEXT =
104
+
105
+ RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)
106
+ RUBY_SO_NAME = ruby.3.4
107
+ RUBYW_INSTALL_NAME =
108
+ RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version)
109
+ RUBYW_BASE_NAME = rubyw
110
+ RUBY_BASE_NAME = ruby
111
+
112
+ arch = arm64-darwin25
113
+ sitearch = $(arch)
114
+ ruby_version = 3.4.0
115
+ ruby = $(bindir)/$(RUBY_BASE_NAME)
116
+ RUBY = $(ruby)
117
+ BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)
118
+ ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h
119
+
120
+ RM = rm -f
121
+ RM_RF = rm -fr
122
+ RMDIRS = rmdir -p
123
+ MAKEDIRS = /opt/homebrew/bin/gmkdir -p
124
+ INSTALL = /opt/homebrew/bin/ginstall -c
125
+ INSTALL_PROG = $(INSTALL) -m 0755
126
+ INSTALL_DATA = $(INSTALL) -m 644
127
+ COPY = cp
128
+ TOUCH = exit >
129
+
130
+ #### End of system configuration section. ####
131
+
132
+ preload =
133
+ libpath = . $(libdir)
134
+ LIBPATH = -L. -L$(libdir)
135
+ DEFFILE =
136
+
137
+ CLEANFILES = mkmf.log
138
+ DISTCLEANFILES =
139
+ DISTCLEANDIRS =
140
+
141
+ extout =
142
+ extout_prefix =
143
+ target_prefix = /guardinari
144
+ LOCAL_LIBS =
145
+ LIBS = $(LIBRUBYARG_SHARED) -lpthread
146
+ ORIG_SRCS = guardinari.c
147
+ SRCS = $(ORIG_SRCS)
148
+ OBJS = guardinari.o
149
+ HDRS = $(srcdir)/scanner.h
150
+ LOCAL_HDRS =
151
+ TARGET = guardinari
152
+ TARGET_NAME = guardinari
153
+ TARGET_ENTRY = Init_$(TARGET_NAME)
154
+ DLLIB = $(TARGET).bundle
155
+ EXTSTATIC =
156
+ STATIC_LIB =
157
+
158
+ TIMESTAMP_DIR = .
159
+ BINDIR = $(bindir)
160
+ RUBYCOMMONDIR = $(sitedir)$(target_prefix)
161
+ RUBYLIBDIR = $(sitelibdir)$(target_prefix)
162
+ RUBYARCHDIR = $(sitearchdir)$(target_prefix)
163
+ HDRDIR = $(sitehdrdir)$(target_prefix)
164
+ ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix)
165
+ TARGET_SO_DIR =
166
+ TARGET_SO = $(TARGET_SO_DIR)$(DLLIB)
167
+ CLEANLIBS = $(TARGET_SO) $(TARGET_SO:=.dSYM)
168
+ CLEANOBJS = $(OBJS) *.bak
169
+ TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.-.guardinari.time
170
+
171
+ all: $(DLLIB)
172
+ static: $(STATIC_LIB)
173
+ .PHONY: all install static install-so install-rb
174
+ .PHONY: clean clean-so clean-static clean-rb
175
+
176
+ clean-static::
177
+ clean-rb-default::
178
+ clean-rb::
179
+ clean-so::
180
+ clean: clean-so clean-static clean-rb-default clean-rb
181
+ -$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time
182
+
183
+ distclean-rb-default::
184
+ distclean-rb::
185
+ distclean-so::
186
+ distclean-static::
187
+ distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb
188
+ -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
189
+ -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
190
+ -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true
191
+
192
+ realclean: distclean
193
+ install: install-so install-rb
194
+
195
+ install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP)
196
+ $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
197
+ clean-static::
198
+ -$(Q)$(RM) $(STATIC_LIB)
199
+ install-rb: pre-install-rb do-install-rb install-rb-default
200
+ install-rb-default: pre-install-rb-default do-install-rb-default
201
+ pre-install-rb: Makefile
202
+ pre-install-rb-default: Makefile
203
+ do-install-rb:
204
+ do-install-rb-default:
205
+ pre-install-rb-default:
206
+ @$(NULLCMD)
207
+ $(TARGET_SO_DIR_TIMESTAMP):
208
+ $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR)
209
+ $(Q) $(TOUCH) $@
210
+
211
+ site-install: site-install-so site-install-rb
212
+ site-install-so: install-so
213
+ site-install-rb: install-rb
214
+
215
+ .SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S
216
+
217
+ .cc.o:
218
+ $(ECHO) compiling $(<)
219
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
220
+
221
+ .cc.S:
222
+ $(ECHO) translating $(<)
223
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
224
+
225
+ .mm.o:
226
+ $(ECHO) compiling $(<)
227
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
228
+
229
+ .mm.S:
230
+ $(ECHO) translating $(<)
231
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
232
+
233
+ .cxx.o:
234
+ $(ECHO) compiling $(<)
235
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
236
+
237
+ .cxx.S:
238
+ $(ECHO) translating $(<)
239
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
240
+
241
+ .cpp.o:
242
+ $(ECHO) compiling $(<)
243
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
244
+
245
+ .cpp.S:
246
+ $(ECHO) translating $(<)
247
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
248
+
249
+ .c.o:
250
+ $(ECHO) compiling $(<)
251
+ $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
252
+
253
+ .c.S:
254
+ $(ECHO) translating $(<)
255
+ $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
256
+
257
+ .m.o:
258
+ $(ECHO) compiling $(<)
259
+ $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
260
+
261
+ .m.S:
262
+ $(ECHO) translating $(<)
263
+ $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
264
+
265
+ $(TARGET_SO): $(OBJS) Makefile
266
+ $(ECHO) linking shared-object guardinari/$(DLLIB)
267
+ -$(Q)$(RM) $(@)
268
+ $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
269
+ $(Q) $(POSTLINK)
270
+
271
+
272
+
273
+ ###
274
+ all: guardinari.c
275
+
276
+ guardinari.c: guardinari.l
277
+ flex -o guardinari.c guardinari.l
@@ -0,0 +1,4 @@
1
+ all: guardinari.c
2
+
3
+ guardinari.c: guardinari.l
4
+ flex -o guardinari.c guardinari.l
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+
5
+ # Makes all symbols private by default to avoid unintended conflict
6
+ # with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED
7
+ # selectively, or entirely remove this flag.
8
+ append_cflags("-fvisibility=hidden")
9
+ append_cflags("-O3")
10
+
11
+ if ENV["SANITIZE"]
12
+ $CFLAGS << " -fsanitize=address,undefined -fno-omit-frame-pointer" # rubocop:disable Style/GlobalVars
13
+ $LDFLAGS << " -fsanitize=address,undefined" # rubocop:disable Style/GlobalVars
14
+ end
15
+
16
+ create_makefile("guardinari/guardinari")