Skip to content

Commit 8790ac3

Browse files
committed
Add owner integrity check
1 parent aecab13 commit 8790ac3

16 files changed

Lines changed: 407 additions & 20 deletions

File tree

.github/workflows/indexing_top_gems.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ jobs:
3131
key: ${{ runner.os }}-rust
3232

3333
- name: Run Ruby tests
34+
env:
35+
RUST_BACKTRACE: full
3436
run: bundle exec rake index_top_gems
3537

3638
- name: Save Rust compile cache

ext/rubydex/graph.c

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "utils.h"
1010

1111
static VALUE cGraph;
12+
static VALUE mRubydex;
1213

1314
// Free function for the custom Graph allocator. We always have to call into Rust to free data allocated by it
1415
static void graph_free(void *ptr) {
@@ -449,6 +450,32 @@ static VALUE rdxr_graph_require_paths(VALUE self, VALUE load_path) {
449450
return array;
450451
}
451452

453+
// Graph#check_integrity: () -> Array[Rubydex::IntegrityFailure]
454+
// Returns an array of IntegrityFailure objects, empty if no issues found
455+
static VALUE rdxr_graph_check_integrity(VALUE self) {
456+
void *graph;
457+
TypedData_Get_Struct(self, void *, &graph_type, graph);
458+
459+
size_t error_count = 0;
460+
const char *const *errors = rdx_check_integrity(graph, &error_count);
461+
462+
if (errors == NULL) {
463+
return rb_ary_new();
464+
}
465+
466+
VALUE cIntegrityError = rb_const_get(mRubydex, rb_intern("IntegrityFailure"));
467+
VALUE array = rb_ary_new_capa((long)error_count);
468+
469+
for (size_t i = 0; i < error_count; i++) {
470+
VALUE argv[] = {rb_utf8_str_new_cstr(errors[i])};
471+
VALUE error = rb_class_new_instance(1, argv, cIntegrityError);
472+
rb_ary_push(array, error);
473+
}
474+
475+
free_c_string_array(errors, error_count);
476+
return array;
477+
}
478+
452479
// Graph#diagnostics -> Array[Rubydex::Diagnostic]
453480
static VALUE rdxr_graph_diagnostics(VALUE self) {
454481
void *graph;
@@ -482,7 +509,8 @@ static VALUE rdxr_graph_diagnostics(VALUE self) {
482509
return diagnostics;
483510
}
484511

485-
void rdxi_initialize_graph(VALUE mRubydex) {
512+
void rdxi_initialize_graph(VALUE moduleRubydex) {
513+
mRubydex = moduleRubydex;
486514
cGraph = rb_define_class_under(mRubydex, "Graph", rb_cObject);
487515
rb_define_alloc_func(cGraph, rdxr_graph_alloc);
488516
rb_define_method(cGraph, "index_all", rdxr_graph_index_all, 1);
@@ -495,6 +523,7 @@ void rdxi_initialize_graph(VALUE mRubydex) {
495523
rb_define_method(cGraph, "constant_references", rdxr_graph_constant_references, 0);
496524
rb_define_method(cGraph, "method_references", rdxr_graph_method_references, 0);
497525
rb_define_method(cGraph, "diagnostics", rdxr_graph_diagnostics, 0);
526+
rb_define_method(cGraph, "check_integrity", rdxr_graph_check_integrity, 0);
498527
rb_define_method(cGraph, "[]", rdxr_graph_aref, 1);
499528
rb_define_method(cGraph, "search", rdxr_graph_search, 1);
500529
rb_define_method(cGraph, "encoding=", rdxr_graph_set_encoding, 1);

lib/rubydex.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@
22

33
require "bundler"
44
require "uri"
5-
6-
module Rubydex
7-
class Error < StandardError; end
8-
end
9-
105
require "rubydex/version"
116

127
begin
@@ -18,6 +13,7 @@ class Error < StandardError; end
1813
require "rubydex/rubydex"
1914
end
2015

16+
require "rubydex/failures"
2117
require "rubydex/location"
2218
require "rubydex/comment"
2319
require "rubydex/diagnostic"

lib/rubydex/failures.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
module Rubydex
4+
class Failure
5+
#: String
6+
attr_reader :message
7+
8+
#: (String) -> void
9+
def initialize(message)
10+
@message = message
11+
end
12+
end
13+
14+
class IntegrityFailure < Failure; end
15+
end

lib/rubydex/location.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ module Rubydex
44
# A zero based internal location. Intended to be used for tool-to-tool communication, such as a language server
55
# communicating with an editor.
66
class Location
7+
class NotFileUriError < StandardError; end
8+
79
include Comparable
810

911
#: String
@@ -22,9 +24,9 @@ def initialize(uri:, start_line:, end_line:, start_column:, end_column:)
2224
end
2325

2426
#: () -> String
25-
def path
27+
def to_file_path
2628
uri = URI(@uri)
27-
raise Rubydex::Error, "URI is not a file:// URI: #{@uri}" unless uri.scheme == "file"
29+
raise NotFileUriError, "URI is not a file:// URI: #{@uri}" unless uri.scheme == "file"
2830

2931
path = uri.path
3032
# TODO: This has to go away once we have a proper URI abstraction
@@ -59,7 +61,7 @@ def to_display
5961

6062
#: -> String
6163
def to_s
62-
"#{path}:#{@start_line + 1}:#{@start_column + 1}-#{@end_line + 1}:#{@end_column + 1}"
64+
"#{to_file_path}:#{@start_line + 1}:#{@start_column + 1}-#{@end_line + 1}:#{@end_column + 1}"
6365
end
6466
end
6567

@@ -82,7 +84,7 @@ def comparable_values
8284

8385
#: -> String
8486
def to_s
85-
"#{path}:#{@start_line}:#{@start_column}-#{@end_line}:#{@end_column}"
87+
"#{to_file_path}:#{@start_line}:#{@start_column}-#{@end_line}:#{@end_column}"
8688
end
8789
end
8890
end

rakelib/index_top_gems.rake

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
desc "Index the top 100 gems from rubygems.org"
4-
task index_top_gems: :compile_release do
4+
task index_top_gems: :compile do
55
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
66
require "net/http"
77
require "rubygems/package"
@@ -51,7 +51,10 @@ task index_top_gems: :compile_release do
5151

5252
# Index the gem's files and yield errors back to the main Ractor
5353
graph = Rubydex::Graph.new
54-
errors = graph.index_all(Dir.glob("#{gem_dir}/**/*.rb"))
54+
indexing_errors = graph.index_all(Dir.glob("#{gem_dir}/**/*.rb"))
55+
graph.resolve
56+
57+
errors = indexing_errors + graph.check_integrity.map(&:message)
5558
next if errors.empty?
5659

5760
@mutex.synchronize { @errors << "#{gem} => #{errors.join(", ")}" }

rbi/rubydex.rbi

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ module Rubydex
204204
sig { returns(T::Array[String]) }
205205
def workspace_paths; end
206206

207+
sig { void }
208+
def check_integrity; end
209+
207210
private
208211

209212
# Gathers the paths we have to index for all workspace dependencies
@@ -235,7 +238,7 @@ module Rubydex
235238
def end_line; end
236239

237240
sig { returns(String) }
238-
def path; end
241+
def to_file_path; end
239242

240243
sig { returns(Integer) }
241244
def start_column; end

rust/rubydex-sys/src/graph_api.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rubydex::model::encoding::Encoding;
1010
use rubydex::model::graph::Graph;
1111
use rubydex::model::ids::DeclarationId;
1212
use rubydex::resolution::Resolver;
13-
use rubydex::{indexing, listing, query};
13+
use rubydex::{indexing, integrity, listing, query};
1414
use std::ffi::CString;
1515
use std::path::PathBuf;
1616
use std::{mem, ptr};
@@ -196,6 +196,42 @@ pub extern "C" fn rdx_graph_resolve(pointer: GraphPointer) {
196196
});
197197
}
198198

199+
/// Checks the integrity of the graph and returns an array of error message strings. Returns NULL if there are no
200+
/// errors. Caller must free with `free_c_string_array`.
201+
///
202+
/// # Safety
203+
///
204+
/// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
205+
/// - `out_error_count` must be a valid, writable pointer.
206+
#[unsafe(no_mangle)]
207+
pub unsafe extern "C" fn rdx_check_integrity(
208+
pointer: GraphPointer,
209+
out_error_count: *mut usize,
210+
) -> *const *const c_char {
211+
with_graph(pointer, |graph| {
212+
let errors = integrity::check_integrity(graph);
213+
214+
if errors.is_empty() {
215+
unsafe { *out_error_count = 0 };
216+
return ptr::null();
217+
}
218+
219+
let c_strings: Vec<*const c_char> = errors
220+
.into_iter()
221+
.filter_map(|error| {
222+
CString::new(error.to_string())
223+
.ok()
224+
.map(|c_string| c_string.into_raw().cast_const())
225+
})
226+
.collect();
227+
228+
unsafe { *out_error_count = c_strings.len() };
229+
230+
let boxed = c_strings.into_boxed_slice();
231+
Box::into_raw(boxed).cast::<*const c_char>()
232+
})
233+
}
234+
199235
/// # Safety
200236
///
201237
/// Expects both the graph pointer and encoding string pointer to be valid

0 commit comments

Comments
 (0)