Last active
January 2, 2024 23:17
-
-
Save ksss/300f95e68bfc6da47f776702be4c750c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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