Skip to content

Instantly share code, notes, and snippets.

@ksss
Last active January 2, 2024 23:17
Show Gist options
  • Save ksss/300f95e68bfc6da47f776702be4c750c to your computer and use it in GitHub Desktop.
Save ksss/300f95e68bfc6da47f776702be4c750c to your computer and use it in GitHub Desktop.
#! /usr/bin/env ruby
# $ rbs-fuzzing.rb [TypeName] [method_name]
# $ rbs-fuzzing.rb Integer
# $ rbs-fuzzing.rb Integer pow
require 'rbs'
require 'rbs/test'
class Fuzzing
class Interface < BasicObject
def initialize(f, type_name)
@f = f
@type_name = type_name
@definition = @f.builder.build_interface(type_name)
@obj = ::Object.new
@definition.methods.each do |name, method|
return_type = method.defs.sample.member.overloads.sample.method_type.type.return_type
ret = f.generate_by(return_type)
@obj.define_singleton_method(name) do |*r|
ret
end
end
end
def method_missing(name, *args, **kwargs, &block)
if @definition.methods.key?(name)
@obj.send(name, *args, **kwargs, &block)
else
super
end
end
def inspect
"#<Fuzzing::Interface @type_name=#{@type_name} @methods=#{@definition.methods.keys}>"
end
end
attr_reader :count, :builder
def initialize(count:, builder:)
@count = count
@builder = builder
end
def each(method_type)
return enum_for(__method__) unless block_given?
@count.times do |i|
# print '.'
args = generate_args(method_type)
kwargs = generate_kwargs(method_type)
block = generate_block(method_type)
yield args, kwargs, block
end
end
def generate_receiver(type_name)
if @builder.env.module_decl?(type_name)
Class.new.include(eval(type_name.to_s)).new
else
generate_by_type_name(type_name)
end
end
def generate_args(method_type)
fun = method_type.type
[].tap do |args|
fun.required_positionals&.each do |param|
args << generate_by(param.type)
end
fun.optional_positionals.each do |param|
if [true, false].sample
args << generate_by(param.type)
end
end
fun.trailing_positionals&.each do |param|
args << generate_by(param.type)
end
if fun.rest_positionals
3.times do
args << generate_by(fun.rest_positionals.type)
end
end
end
end
def generate_kwargs(method_type)
fun = method_type.type
{}.tap do |kw|
fun.required_keywords.each do |key, param|
kw[key] = generate_by(param.type)
end
fun.optional_keywords.each do |key, param|
if [true, false].sample
kw[key] = generate_by(param.type)
end
end
if fun.rest_keywords
3.times do
key = generate_symbol
kw[key] = generate_by(fun.rest_keywords.type)
end
end
end
end
def generate_block(method_type)
return nil unless method_type.block
return nil unless method_type.block.required || [true, false].sample
generate_proc(method_type.type)
end
def generate_by(type)
case type
when RBS::Types::Bases::Any
generate_untyped
when RBS::Types::Variable
type
when RBS::Types::Tuple
type.types.map { |t| generate_by(t) }
when RBS::Types::Union
type.types.sample.then { |t| generate_by(t) }
when RBS::Types::Optional
[true, false].sample ? generate_by(type.type) : nil
when RBS::Types::Alias
generate_by(@builder.expand_alias2(type.name, type.args))
when RBS::Types::ClassInstance
if type.args.empty?
generate_by_type_name(type.name)
else
case type.name.to_s
when "::Range"
generate_range(type)
when "::Array"
generate_array(type)
else
pp type
raise
end
end
when RBS::Types::Interface
Interface.new(self, type.name)
when RBS::Types::Literal
type.literal
when RBS::Types::Bases::Bool
generate_bool
when RBS::Types::Bases::Nil
nil
else
pp type
raise
end
end
def generate_by_type_name(type_name)
case type_name.to_s
when "::Encoding"
Encoding::UTF_8
when "::String"
generate_string
when "::Integer"
generate_integer
when "::Float"
generate_float
when "::Rational"
generate_rational
when "::Complex"
generate_complex
when "::Numeric"
case rand(4)
when 0
generate_integer
when 1
generate_float
when 2
generate_rational
when 3
generate_complex
end
when "::Symbol"
generate_symbol
when "::Regexp"
generate_regexp
when "::Object"
generate_object
when "::Binding"
generate_binding
when "::Time"
generate_time
else
pp type_name
raise
end
end
SIMPLE_SOURCE = ('a'..'z').to_a << '_'
def generate_symbol(length: 10)
generate_string(length: length).to_sym
end
def generate_string(length: 10)
length.times.map { SIMPLE_SOURCE.sample }.join
end
def generate_regexp(length: 3)
Regexp.new(generate_string(length: length))
end
def generate_integer
rand(200) - 100
end
def generate_nonzero_integer
loop do
n = generate_integer
return n if n != 0
end
end
def generate_rational
Rational(generate_nonzero_integer, generate_nonzero_integer)
end
def generate_float
rand * 100 - 50
end
def generate_untyped
case rand(6)
when 0
nil
when 1
true
when 2
false
when 3
generate_integer
when 4
generate_string
when 5
generate_symbol
end
end
def generate_complex
Complex(generate_nonzero_integer, generate_nonzero_integer)
end
def generate_range(type)
a = generate_by(type.args.first)
Range.new(a, a)
end
def generate_array(type)
Array.new(3) { generate_by(type.args.first) }
end
def generate_proc(_type)
Proc.new {}
end
def generate_bool
[true, false].sample
end
def generate_object
Object.new
end
def generate_binding
binding
end
def generate_time
Time.now
end
end
target_type_name = TypeName(ARGV[0]).absolute!
target_method_name = ARGV[1]&.to_sym
env = RBS::Environment.from_loader(RBS::EnvironmentLoader.new).resolve_type_names
builder = RBS::DefinitionBuilder.new(env: env)
typecheck = RBS::Test::TypeCheck.new(
self_class: eval(target_type_name.to_s),
builder: builder,
sample_size: 100,
unchecked_classes: []
)
f = Fuzzing.new(count: 100, builder: builder)
methods = builder.build_instance(target_type_name).methods
methods.each do |name, method|
next if method.defined_in != target_type_name
next if name == :fork || name == :spawn || name == :step
next if target_method_name && name != target_method_name
method.defs.each do |type_def|
type_def.member.overloads.each do |overload|
print "[#{method.defined_in}] def #{type_def.member.name}: #{overload.method_type}"
begin
f.each(overload.method_type) do |args, kwargs, block|
receiver = f.generate_receiver(target_type_name)
return_value =
begin
if kwargs.empty?
receiver.send(type_def.member.name, *args, &block)
else
receiver.send(type_def.member.name, *args, **kwargs, &block)
end
rescue => e
# p e
# p [:args, receiver, type_def.member.name, args, kwargs, block]
next
end
ok =
begin
typecheck.value(return_value, overload.method_type.type.return_type)
rescue => e
# p e
# p [:args, receiver, type_def.member.name, args, kwargs, block]
next
end
unless ok
p [:return, { receiver: receiver, args: args, args_class: args.map(&:class), return_value: return_value, return_value_class: return_value.class, return_type: overload.method_type.type.return_type.to_s }]
end
end
rescue => e
p e
end
puts
end
break
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment