houndstooth 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 +7 -0
 - data/.gitignore +5 -0
 - data/.rspec +1 -0
 - data/.ruby-version +1 -0
 - data/Gemfile +11 -0
 - data/Gemfile.lock +49 -0
 - data/README.md +99 -0
 - data/bin/houndstooth.rb +183 -0
 - data/fuzz/cases/x.rb +8 -0
 - data/fuzz/cases/y.rb +8 -0
 - data/fuzz/cases/z.rb +22 -0
 - data/fuzz/ruby.dict +64 -0
 - data/fuzz/run +21 -0
 - data/lib/houndstooth/environment/builder.rb +260 -0
 - data/lib/houndstooth/environment/type_parser.rb +149 -0
 - data/lib/houndstooth/environment/types/basic/type.rb +85 -0
 - data/lib/houndstooth/environment/types/basic/type_instance.rb +54 -0
 - data/lib/houndstooth/environment/types/compound/union_type.rb +72 -0
 - data/lib/houndstooth/environment/types/defined/base_defined_type.rb +23 -0
 - data/lib/houndstooth/environment/types/defined/defined_type.rb +137 -0
 - data/lib/houndstooth/environment/types/defined/pending_defined_type.rb +14 -0
 - data/lib/houndstooth/environment/types/method/method.rb +79 -0
 - data/lib/houndstooth/environment/types/method/method_type.rb +144 -0
 - data/lib/houndstooth/environment/types/method/parameters.rb +53 -0
 - data/lib/houndstooth/environment/types/method/special_constructor_method.rb +15 -0
 - data/lib/houndstooth/environment/types/special/instance_type.rb +9 -0
 - data/lib/houndstooth/environment/types/special/self_type.rb +9 -0
 - data/lib/houndstooth/environment/types/special/type_parameter_placeholder.rb +38 -0
 - data/lib/houndstooth/environment/types/special/untyped_type.rb +11 -0
 - data/lib/houndstooth/environment/types/special/void_type.rb +12 -0
 - data/lib/houndstooth/environment/types.rb +3 -0
 - data/lib/houndstooth/environment.rb +74 -0
 - data/lib/houndstooth/errors.rb +53 -0
 - data/lib/houndstooth/instructions.rb +698 -0
 - data/lib/houndstooth/interpreter/const_internal.rb +148 -0
 - data/lib/houndstooth/interpreter/objects.rb +142 -0
 - data/lib/houndstooth/interpreter/runtime.rb +309 -0
 - data/lib/houndstooth/interpreter.rb +7 -0
 - data/lib/houndstooth/semantic_node/control_flow.rb +218 -0
 - data/lib/houndstooth/semantic_node/definitions.rb +253 -0
 - data/lib/houndstooth/semantic_node/identifiers.rb +308 -0
 - data/lib/houndstooth/semantic_node/keywords.rb +45 -0
 - data/lib/houndstooth/semantic_node/literals.rb +226 -0
 - data/lib/houndstooth/semantic_node/operators.rb +126 -0
 - data/lib/houndstooth/semantic_node/parameters.rb +108 -0
 - data/lib/houndstooth/semantic_node/send.rb +349 -0
 - data/lib/houndstooth/semantic_node/super.rb +12 -0
 - data/lib/houndstooth/semantic_node.rb +119 -0
 - data/lib/houndstooth/stdlib.rb +6 -0
 - data/lib/houndstooth/type_checker.rb +462 -0
 - data/lib/houndstooth.rb +53 -0
 - data/spec/ast_to_node_spec.rb +889 -0
 - data/spec/environment_spec.rb +323 -0
 - data/spec/instructions_spec.rb +291 -0
 - data/spec/integration_spec.rb +785 -0
 - data/spec/interpreter_spec.rb +170 -0
 - data/spec/self_spec.rb +7 -0
 - data/spec/spec_helper.rb +50 -0
 - data/test/ruby_interpreter_test.rb +162 -0
 - data/types/stdlib.htt +170 -0
 - metadata +110 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA256:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 0febf254b01185f48cc75c1fc717a524e90dedaebbcbf156b4db2da8f2c6cc4d
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 7d553f1ad85674e0fd5286a9c0414c5c0dfd72ba8e6ae7a648ace0f2fe566222
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: bfd945c4d810464e80e389711cc7e8eb367688a7a76c6fd5db1da45e5a509163d3d7dbe1f54d97339913620205134848522304a8c21a19fe1c03dcc41f284cc0
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: abfb4d5da0b0232d2279b47d1be19dd4b4935a0f6949679e345f27eb06420eecbb1c26bf1af52ff58c9c8ff8d3254e99544d252ed4febc728d4bf25fc32ffa24
         
     | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.rspec
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --require spec_helper
         
     | 
    
        data/.ruby-version
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            3.0.3
         
     | 
    
        data/Gemfile
    ADDED
    
    
    
        data/Gemfile.lock
    ADDED
    
    | 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            GIT
         
     | 
| 
      
 2 
     | 
    
         
            +
              remote: https://github.com/richo/afl-ruby.git
         
     | 
| 
      
 3 
     | 
    
         
            +
              revision: cbaad7a5fadca7b1617a211d571f7badc80fec82
         
     | 
| 
      
 4 
     | 
    
         
            +
              specs:
         
     | 
| 
      
 5 
     | 
    
         
            +
                afl (0.0.3)
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            GEM
         
     | 
| 
      
 8 
     | 
    
         
            +
              remote: https://rubygems.org/
         
     | 
| 
      
 9 
     | 
    
         
            +
              specs:
         
     | 
| 
      
 10 
     | 
    
         
            +
                ast (2.4.2)
         
     | 
| 
      
 11 
     | 
    
         
            +
                diff-lcs (1.5.0)
         
     | 
| 
      
 12 
     | 
    
         
            +
                docile (1.4.0)
         
     | 
| 
      
 13 
     | 
    
         
            +
                optimist (3.0.1)
         
     | 
| 
      
 14 
     | 
    
         
            +
                parser (3.1.0.0)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  ast (~> 2.4.1)
         
     | 
| 
      
 16 
     | 
    
         
            +
                rbs (2.1.0)
         
     | 
| 
      
 17 
     | 
    
         
            +
                rspec (3.10.0)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  rspec-core (~> 3.10.0)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  rspec-expectations (~> 3.10.0)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  rspec-mocks (~> 3.10.0)
         
     | 
| 
      
 21 
     | 
    
         
            +
                rspec-core (3.10.2)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  rspec-support (~> 3.10.0)
         
     | 
| 
      
 23 
     | 
    
         
            +
                rspec-expectations (3.10.2)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  diff-lcs (>= 1.2.0, < 2.0)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  rspec-support (~> 3.10.0)
         
     | 
| 
      
 26 
     | 
    
         
            +
                rspec-mocks (3.10.3)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  diff-lcs (>= 1.2.0, < 2.0)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  rspec-support (~> 3.10.0)
         
     | 
| 
      
 29 
     | 
    
         
            +
                rspec-support (3.10.3)
         
     | 
| 
      
 30 
     | 
    
         
            +
                simplecov (0.21.2)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  docile (~> 1.1)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  simplecov-html (~> 0.11)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  simplecov_json_formatter (~> 0.1)
         
     | 
| 
      
 34 
     | 
    
         
            +
                simplecov-html (0.12.3)
         
     | 
| 
      
 35 
     | 
    
         
            +
                simplecov_json_formatter (0.1.3)
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            PLATFORMS
         
     | 
| 
      
 38 
     | 
    
         
            +
              arm64-darwin-21
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            DEPENDENCIES
         
     | 
| 
      
 41 
     | 
    
         
            +
              afl!
         
     | 
| 
      
 42 
     | 
    
         
            +
              optimist
         
     | 
| 
      
 43 
     | 
    
         
            +
              parser
         
     | 
| 
      
 44 
     | 
    
         
            +
              rbs
         
     | 
| 
      
 45 
     | 
    
         
            +
              rspec
         
     | 
| 
      
 46 
     | 
    
         
            +
              simplecov
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            BUNDLED WITH
         
     | 
| 
      
 49 
     | 
    
         
            +
               2.2.22
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,99 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Houndstooth
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Houndstooth is a **highly-experimental Ruby static type checker**, which is uniquely
         
     | 
| 
      
 4 
     | 
    
         
            +
            **metaprogramming-aware**.
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Houndstooth was created for my final-year project at the University of York. It is far from
         
     | 
| 
      
 7 
     | 
    
         
            +
            production-ready, and should be treated here as a proof-of-concept!
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            Here's an annotated example of what this enables you to do:
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 12 
     | 
    
         
            +
            #!var @name String
         
     | 
| 
      
 13 
     | 
    
         
            +
            #!var @graduate Boolean
         
     | 
| 
      
 14 
     | 
    
         
            +
            class Student
         
     | 
| 
      
 15 
     | 
    
         
            +
                #!arg String
         
     | 
| 
      
 16 
     | 
    
         
            +
                attr_reader :name
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # Now we'd like to define an accessor for our boolean variable, @graduate. But we usually like
         
     | 
| 
      
 19 
     | 
    
         
            +
                # methods returning a boolean to end in ?, so we can't use `attr_accessor`.
         
     | 
| 
      
 20 
     | 
    
         
            +
                # Instead, let's define our own helper, `bool_accessor`
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                #: (Symbol) -> void
         
     | 
| 
      
 23 
     | 
    
         
            +
                #!const required
         
     | 
| 
      
 24 
     | 
    
         
            +
                #  ^ This special annotation means, "hey, type checker - you need to check out what this does!"
         
     | 
| 
      
 25 
     | 
    
         
            +
                def self.bool_accessor(name)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    # Define our method #<name>?
         
     | 
| 
      
 27 
     | 
    
         
            +
                    #!arg Boolean
         
     | 
| 
      
 28 
     | 
    
         
            +
                    attr_reader "#{name}?".to_sym
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    # ...and also define a normal writer, #<name>=
         
     | 
| 
      
 31 
     | 
    
         
            +
                    #!arg Boolean
         
     | 
| 
      
 32 
     | 
    
         
            +
                    attr_writer name
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                # Now use our neat new helper
         
     | 
| 
      
 36 
     | 
    
         
            +
                bool_accessor :graduate
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                #: (String) -> void
         
     | 
| 
      
 39 
     | 
    
         
            +
                def initialize(name)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @name = name
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @graduate = false
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            # The type checker sees those `graduate?` and `graduate=` definitions, even though they were
         
     | 
| 
      
 46 
     | 
    
         
            +
            # dynamic!
         
     | 
| 
      
 47 
     | 
    
         
            +
            s = Student.new("Aaron")
         
     | 
| 
      
 48 
     | 
    
         
            +
            s.graduate? # => false
         
     | 
| 
      
 49 
     | 
    
         
            +
            s.graduate = true
         
     | 
| 
      
 50 
     | 
    
         
            +
            s.graduate? # => true
         
     | 
| 
      
 51 
     | 
    
         
            +
            ```
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            It even understands control flow such as loops:
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 56 
     | 
    
         
            +
            class Adder
         
     | 
| 
      
 57 
     | 
    
         
            +
                1000.times do |i,|
         
     | 
| 
      
 58 
     | 
    
         
            +
                    #!arg Integer
         
     | 
| 
      
 59 
     | 
    
         
            +
                    #!arg Integer
         
     | 
| 
      
 60 
     | 
    
         
            +
                    #  ^ These annotations are the parameter type (first one) and return type (second one)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    define_method :"add_#{i}" do |input,|
         
     | 
| 
      
 62 
     | 
    
         
            +
                        i + input
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
            end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            # Now we can add to our heart's content
         
     | 
| 
      
 68 
     | 
    
         
            +
            a = Adder.new
         
     | 
| 
      
 69 
     | 
    
         
            +
            x = a.add_123(a.add_5(3))
         
     | 
| 
      
 70 
     | 
    
         
            +
            ```
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
            Houndstooth includes a minimal Ruby interpreter capable of evaluating a pure and deterministic
         
     | 
| 
      
 73 
     | 
    
         
            +
            subset of the language. Using this, it executes portions of your codebase to discover methods which
         
     | 
| 
      
 74 
     | 
    
         
            +
            will be dynamically defined at runtime.
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
            All methods can optionally be tagged, either as:
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
            - _const_, which means they _can_ be executed by the interpreter. Such methods include `Integer#+`,
         
     | 
| 
      
 79 
     | 
    
         
            +
              `Array#each`, and `String#length`.
         
     | 
| 
      
 80 
     | 
    
         
            +
            - _const-internal_, which means they **must** be executed by the interpreter wherever they appear
         
     | 
| 
      
 81 
     | 
    
         
            +
              in your codebase. These are your metaprogramming methods, like `define_method` and `attr_reader`.
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
            The strict requirements of const-internal mean that Houndstooth's interpreter is guaranteed to
         
     | 
| 
      
 84 
     | 
    
         
            +
            discover any invocations of metaprogramming, and therefore knows about the entire environment of
         
     | 
| 
      
 85 
     | 
    
         
            +
            your program. 
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
            Thanks to this tagging mechanism, it becomes a type error to write definitions which are not
         
     | 
| 
      
 88 
     | 
    
         
            +
            guaranteed to exist at runtime, or depend on non-deterministic data:
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 91 
     | 
    
         
            +
            class A
         
     | 
| 
      
 92 
     | 
    
         
            +
                # This is a type error!
         
     | 
| 
      
 93 
     | 
    
         
            +
                # Cannot call non-const method `rand` on `#<interpreter object: <Eigen:Kernel>>` from const context
         
     | 
| 
      
 94 
     | 
    
         
            +
                if Kernel.rand > 0.5
         
     | 
| 
      
 95 
     | 
    
         
            +
                    #: () -> void
         
     | 
| 
      
 96 
     | 
    
         
            +
                    def x; end
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
            end
         
     | 
| 
      
 99 
     | 
    
         
            +
            ```
         
     | 
    
        data/bin/houndstooth.rb
    ADDED
    
    | 
         @@ -0,0 +1,183 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'optimist'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'afl'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative '../lib/houndstooth'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            options = Optimist::options do
         
     | 
| 
      
 6 
     | 
    
         
            +
                banner "Houndstooth: A Ruby type checker"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                opt :file, "file to type check", type: :string, short: :f
         
     | 
| 
      
 9 
     | 
    
         
            +
                opt :code, "code string to type check", type: :string, short: :e
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                opt :no_stdlib, "don't load stdlib types (for debugging - almost guaranteed to cause weird problems!)", short: :s
         
     | 
| 
      
 12 
     | 
    
         
            +
                opt :fatal_interpreter, "exit on first interpreter error, and print internal backtrace", short: :x
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                opt :debug_nodes, "print parsed node tree", short: :none
         
     | 
| 
      
 15 
     | 
    
         
            +
                opt :debug_environment, "print known types and methods", short: :none
         
     | 
| 
      
 16 
     | 
    
         
            +
                opt :debug_instructions, "print generated instructions", short: :none
         
     | 
| 
      
 17 
     | 
    
         
            +
                opt :debug_type_changes, "print instructions after type changes", short: :none
         
     | 
| 
      
 18 
     | 
    
         
            +
                opt :verbose_instructions, "show more detail in instruction debug views", short: :none
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                opt :instrument, "AFL instrumentation", short: :none
         
     | 
| 
      
 21 
     | 
    
         
            +
            end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            def main(options)
         
     | 
| 
      
 24 
     | 
    
         
            +
                $cli_options = options
         
     | 
| 
      
 25 
     | 
    
         
            +
                # Checks if there are any errors. If so, prints them and aborts.
         
     | 
| 
      
 26 
     | 
    
         
            +
                def abort_on_error!
         
     | 
| 
      
 27 
     | 
    
         
            +
                    if Houndstooth::Errors.errors.any?
         
     | 
| 
      
 28 
     | 
    
         
            +
                        Houndstooth::Errors.errors.each do |error|
         
     | 
| 
      
 29 
     | 
    
         
            +
                            puts error.format
         
     | 
| 
      
 30 
     | 
    
         
            +
                            puts
         
     | 
| 
      
 31 
     | 
    
         
            +
                        end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                        # The fuzzer will view an abort as a crash, so if we're running under instrumentation,
         
     | 
| 
      
 34 
     | 
    
         
            +
                        # exit gracefully here
         
     | 
| 
      
 35 
     | 
    
         
            +
                        l = Houndstooth::Errors.errors.length
         
     | 
| 
      
 36 
     | 
    
         
            +
                        if $cli_options[:instrument]
         
     | 
| 
      
 37 
     | 
    
         
            +
                            puts "Exiting with #{l} error#{l == 1 ? '' : 's'}."
         
     | 
| 
      
 38 
     | 
    
         
            +
                            puts "Running with instrumentation, so exit is just a jump."
         
     | 
| 
      
 39 
     | 
    
         
            +
                            puts "THIS WILL NOT RESULT IN AN ERROR EXIT CODE."
         
     | 
| 
      
 40 
     | 
    
         
            +
                            throw :afl_exit
         
     | 
| 
      
 41 
     | 
    
         
            +
                        else
         
     | 
| 
      
 42 
     | 
    
         
            +
                            abort "Exiting with #{l} error#{l == 1 ? '' : 's'}."
         
     | 
| 
      
 43 
     | 
    
         
            +
                        end
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end 
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                # Create an environment with stdlib types
         
     | 
| 
      
 48 
     | 
    
         
            +
                env = Houndstooth::Environment.new
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                # Load and parse code from file
         
     | 
| 
      
 51 
     | 
    
         
            +
                if options[:file]
         
     | 
| 
      
 52 
     | 
    
         
            +
                    unless File.exist?(options[:file])
         
     | 
| 
      
 53 
     | 
    
         
            +
                        Houndstooth::Errors::Error.new("File '#{options[:file]}' does not exist", []).push
         
     | 
| 
      
 54 
     | 
    
         
            +
                        abort_on_error!
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 58 
     | 
    
         
            +
                        code = File.read(options[:file])
         
     | 
| 
      
 59 
     | 
    
         
            +
                    rescue => e
         
     | 
| 
      
 60 
     | 
    
         
            +
                        Houndstooth::Errors::Error.new("Error reading file: #{e}", []).push
         
     | 
| 
      
 61 
     | 
    
         
            +
                        abort_on_error!
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
                elsif options[:code]
         
     | 
| 
      
 64 
     | 
    
         
            +
                    code = options[:code]
         
     | 
| 
      
 65 
     | 
    
         
            +
                else
         
     | 
| 
      
 66 
     | 
    
         
            +
                    puts "
         
     | 
| 
      
 67 
     | 
    
         
            +
            ███████▖  ▀██ 
         
     | 
| 
      
 68 
     | 
    
         
            +
            ████████▙▖  ▜ 
         
     | 
| 
      
 69 
     | 
    
         
            +
            ██████▌▜██▄           HOUNDSTOOTH
         
     | 
| 
      
 70 
     | 
    
         
            +
            ██████▌ ▝▜██▖     A Ruby type checker
         
     | 
| 
      
 71 
     | 
    
         
            +
            ▝██▙▖         
         
     | 
| 
      
 72 
     | 
    
         
            +
              ▀██▙           -f/--file: Check file
         
     | 
| 
      
 73 
     | 
    
         
            +
            ▙  ▝▜█▌         -e/--code: Check string
         
     | 
| 
      
 74 
     | 
    
         
            +
            ██▄  ▝▌       
         
     | 
| 
      
 75 
     | 
    
         
            +
            "
         
     | 
| 
      
 76 
     | 
    
         
            +
                    exit 1
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                if options[:no_stdlib]
         
     | 
| 
      
 80 
     | 
    
         
            +
                    htt_files = []
         
     | 
| 
      
 81 
     | 
    
         
            +
                else
         
     | 
| 
      
 82 
     | 
    
         
            +
                    htt_files = [["stdlib.htt", File.read(File.join(__dir__, '..', 'types', 'stdlib.htt'))]]
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                # Parse and run builder over all files
         
     | 
| 
      
 86 
     | 
    
         
            +
                all_nodes = [[options[:file] || 'inline code', code], *htt_files].map do |name, contents|
         
     | 
| 
      
 87 
     | 
    
         
            +
                    Houndstooth.process_file(name, contents, env) 
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
                node = all_nodes[0]
         
     | 
| 
      
 90 
     | 
    
         
            +
                abort_on_error!
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                if options[:debug_nodes]
         
     | 
| 
      
 93 
     | 
    
         
            +
                    puts "------ Nodes ------"
         
     | 
| 
      
 94 
     | 
    
         
            +
                    pp node
         
     | 
| 
      
 95 
     | 
    
         
            +
                    puts "-------------------"
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                # Resolve environment
         
     | 
| 
      
 99 
     | 
    
         
            +
                env.resolve_all_pending_types
         
     | 
| 
      
 100 
     | 
    
         
            +
                abort_on_error!
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                if options[:debug_environment]
         
     | 
| 
      
 103 
     | 
    
         
            +
                    puts "--- Environment ---"
         
     | 
| 
      
 104 
     | 
    
         
            +
                    env.types.each do |_, t|
         
     | 
| 
      
 105 
     | 
    
         
            +
                        puts t.path
         
     | 
| 
      
 106 
     | 
    
         
            +
                        if t.type_instance_variables.any?
         
     | 
| 
      
 107 
     | 
    
         
            +
                            puts "  Vars:"
         
     | 
| 
      
 108 
     | 
    
         
            +
                            t.type_instance_variables.each do |k, v|
         
     | 
| 
      
 109 
     | 
    
         
            +
                                puts "    #{k}: #{v.rbs}"
         
     | 
| 
      
 110 
     | 
    
         
            +
                            end
         
     | 
| 
      
 111 
     | 
    
         
            +
                        end
         
     | 
| 
      
 112 
     | 
    
         
            +
                        t.instance_methods.each do |m|
         
     | 
| 
      
 113 
     | 
    
         
            +
                            puts "  #{m.name}"
         
     | 
| 
      
 114 
     | 
    
         
            +
                            # Don't try to print special `new`
         
     | 
| 
      
 115 
     | 
    
         
            +
                            if m.is_a?(Houndstooth::Environment::SpecialConstructorMethod)
         
     | 
| 
      
 116 
     | 
    
         
            +
                                puts "    <special constructor>"
         
     | 
| 
      
 117 
     | 
    
         
            +
                            else
         
     | 
| 
      
 118 
     | 
    
         
            +
                                m.signatures.each do |s|
         
     | 
| 
      
 119 
     | 
    
         
            +
                                    puts "    #{s.rbs}"
         
     | 
| 
      
 120 
     | 
    
         
            +
                                end
         
     | 
| 
      
 121 
     | 
    
         
            +
                            end
         
     | 
| 
      
 122 
     | 
    
         
            +
                        end
         
     | 
| 
      
 123 
     | 
    
         
            +
                        puts
         
     | 
| 
      
 124 
     | 
    
         
            +
                    end
         
     | 
| 
      
 125 
     | 
    
         
            +
                    puts "-------------------"
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                # Create a new instruction block and populate it
         
     | 
| 
      
 129 
     | 
    
         
            +
                # TODO: this is probably not how it'll be done in the final thing - we need to do this to individual
         
     | 
| 
      
 130 
     | 
    
         
            +
                # methods, probably, or just ignore definitions? Don't know!
         
     | 
| 
      
 131 
     | 
    
         
            +
                block = Houndstooth::Instructions::InstructionBlock.new(has_scope: true, parent: nil)
         
     | 
| 
      
 132 
     | 
    
         
            +
                node.to_instructions(block)
         
     | 
| 
      
 133 
     | 
    
         
            +
                env.types["__HoundstoothMain"] = Houndstooth::Environment::DefinedType.new(path: "__HoundstoothMain")
         
     | 
| 
      
 134 
     | 
    
         
            +
                abort_on_error!
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                if options[:debug_instructions]
         
     | 
| 
      
 137 
     | 
    
         
            +
                    puts "-- Instructions ---"
         
     | 
| 
      
 138 
     | 
    
         
            +
                    puts block.to_assembly
         
     | 
| 
      
 139 
     | 
    
         
            +
                    puts "-------------------"
         
     | 
| 
      
 140 
     | 
    
         
            +
                end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                # Run the interpreter
         
     | 
| 
      
 143 
     | 
    
         
            +
                runtime = Houndstooth::Interpreter::Runtime.new(env: env)
         
     | 
| 
      
 144 
     | 
    
         
            +
                runtime.execute_from_top_level(block)
         
     | 
| 
      
 145 
     | 
    
         
            +
                abort_on_error!
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                # That could have printed due to const-internal `puts` etc - if it did, print a divider
         
     | 
| 
      
 148 
     | 
    
         
            +
                puts "--- end of const stdout ---" if $const_printed
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
                # Type check the instruction block
         
     | 
| 
      
 151 
     | 
    
         
            +
                checker = Houndstooth::TypeChecker.new(env)
         
     | 
| 
      
 152 
     | 
    
         
            +
                checker.process_block(
         
     | 
| 
      
 153 
     | 
    
         
            +
                    block,
         
     | 
| 
      
 154 
     | 
    
         
            +
                    lexical_context: Houndstooth::Environment::BaseDefinedType.new,
         
     | 
| 
      
 155 
     | 
    
         
            +
                    self_type: env.types["__HoundstoothMain"],
         
     | 
| 
      
 156 
     | 
    
         
            +
                    const_context: false,
         
     | 
| 
      
 157 
     | 
    
         
            +
                    type_parameters: [],
         
     | 
| 
      
 158 
     | 
    
         
            +
                )
         
     | 
| 
      
 159 
     | 
    
         
            +
                
         
     | 
| 
      
 160 
     | 
    
         
            +
                if options[:debug_type_changes]
         
     | 
| 
      
 161 
     | 
    
         
            +
                    puts "--- Inst. Types ---"
         
     | 
| 
      
 162 
     | 
    
         
            +
                    puts block.to_assembly
         
     | 
| 
      
 163 
     | 
    
         
            +
                    puts "-------------------"
         
     | 
| 
      
 164 
     | 
    
         
            +
                end
         
     | 
| 
      
 165 
     | 
    
         
            +
                abort_on_error!
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                # Yay!
         
     | 
| 
      
 168 
     | 
    
         
            +
                puts "All good!"
         
     | 
| 
      
 169 
     | 
    
         
            +
            end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
            if options[:instrument]
         
     | 
| 
      
 172 
     | 
    
         
            +
                puts "== Instrumentation enabled =="
         
     | 
| 
      
 173 
     | 
    
         
            +
                AFL.init
         
     | 
| 
      
 174 
     | 
    
         
            +
                AFL.with_logging_to_file("/tmp/houndstooth-afl") do
         
     | 
| 
      
 175 
     | 
    
         
            +
                    catch :afl_exit do
         
     | 
| 
      
 176 
     | 
    
         
            +
                        AFL.with_exceptions_as_crashes do
         
     | 
| 
      
 177 
     | 
    
         
            +
                            main(options)
         
     | 
| 
      
 178 
     | 
    
         
            +
                        end
         
     | 
| 
      
 179 
     | 
    
         
            +
                    end
         
     | 
| 
      
 180 
     | 
    
         
            +
                end
         
     | 
| 
      
 181 
     | 
    
         
            +
            else
         
     | 
| 
      
 182 
     | 
    
         
            +
                main(options)
         
     | 
| 
      
 183 
     | 
    
         
            +
            end
         
     | 
    
        data/fuzz/cases/x.rb
    ADDED
    
    
    
        data/fuzz/cases/y.rb
    ADDED
    
    
    
        data/fuzz/cases/z.rb
    ADDED
    
    | 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            class Translator
         
     | 
| 
      
 3 
     | 
    
         
            +
                #: (String, String) -> String
         
     | 
| 
      
 4 
     | 
    
         
            +
                def translate(text, language)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    "#{text} in #{language} is..."
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                #!arg String
         
     | 
| 
      
 9 
     | 
    
         
            +
                [
         
     | 
| 
      
 10 
     | 
    
         
            +
                    "english", "french", "german", "japanese",
         
     | 
| 
      
 11 
     | 
    
         
            +
                    "spanish", "urdu", "korean", "hungarian",
         
     | 
| 
      
 12 
     | 
    
         
            +
                ].each do |lang,|
         
     | 
| 
      
 13 
     | 
    
         
            +
                    #!arg String
         
     | 
| 
      
 14 
     | 
    
         
            +
                    #!arg String
         
     | 
| 
      
 15 
     | 
    
         
            +
                    define_method(:"to_#{lang}") do |s,|
         
     | 
| 
      
 16 
     | 
    
         
            +
                        translate(s, lang)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            t = Translator.new
         
     | 
| 
      
 22 
     | 
    
         
            +
            x = t.to_german("Hello")
         
     | 
    
        data/fuzz/ruby.dict
    ADDED
    
    | 
         @@ -0,0 +1,64 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Derived from the Sorbet fuzzer dictionary, with modifications to use Houndstooth type syntax
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # keywords
         
     | 
| 
      
 4 
     | 
    
         
            +
            "begin"
         
     | 
| 
      
 5 
     | 
    
         
            +
            "end"
         
     | 
| 
      
 6 
     | 
    
         
            +
            "def"
         
     | 
| 
      
 7 
     | 
    
         
            +
            "class"
         
     | 
| 
      
 8 
     | 
    
         
            +
            "module"
         
     | 
| 
      
 9 
     | 
    
         
            +
            "do"
         
     | 
| 
      
 10 
     | 
    
         
            +
            "if"
         
     | 
| 
      
 11 
     | 
    
         
            +
            "else"
         
     | 
| 
      
 12 
     | 
    
         
            +
            "elsif"
         
     | 
| 
      
 13 
     | 
    
         
            +
            "until"
         
     | 
| 
      
 14 
     | 
    
         
            +
            "unless"
         
     | 
| 
      
 15 
     | 
    
         
            +
            "for"
         
     | 
| 
      
 16 
     | 
    
         
            +
            "while"
         
     | 
| 
      
 17 
     | 
    
         
            +
            "rescue"
         
     | 
| 
      
 18 
     | 
    
         
            +
            "case"
         
     | 
| 
      
 19 
     | 
    
         
            +
            "when"
         
     | 
| 
      
 20 
     | 
    
         
            +
            "yield"
         
     | 
| 
      
 21 
     | 
    
         
            +
            "next"
         
     | 
| 
      
 22 
     | 
    
         
            +
            "break"
         
     | 
| 
      
 23 
     | 
    
         
            +
            "return"
         
     | 
| 
      
 24 
     | 
    
         
            +
            "super"
         
     | 
| 
      
 25 
     | 
    
         
            +
            "ensure"
         
     | 
| 
      
 26 
     | 
    
         
            +
            "in"
         
     | 
| 
      
 27 
     | 
    
         
            +
            "redo"
         
     | 
| 
      
 28 
     | 
    
         
            +
            "retry"
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            # common values
         
     | 
| 
      
 32 
     | 
    
         
            +
            "true"
         
     | 
| 
      
 33 
     | 
    
         
            +
            "false"
         
     | 
| 
      
 34 
     | 
    
         
            +
            "Integer"
         
     | 
| 
      
 35 
     | 
    
         
            +
            "String"
         
     | 
| 
      
 36 
     | 
    
         
            +
            "Array"
         
     | 
| 
      
 37 
     | 
    
         
            +
            "Numeric"
         
     | 
| 
      
 38 
     | 
    
         
            +
            "Object"
         
     | 
| 
      
 39 
     | 
    
         
            +
            "BasicObject"
         
     | 
| 
      
 40 
     | 
    
         
            +
            "nil"
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            # types
         
     | 
| 
      
 44 
     | 
    
         
            +
            "self"
         
     | 
| 
      
 45 
     | 
    
         
            +
            "instance"
         
     | 
| 
      
 46 
     | 
    
         
            +
            "void"
         
     | 
| 
      
 47 
     | 
    
         
            +
            "untyped"
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            # interesting stuff
         
     | 
| 
      
 51 
     | 
    
         
            +
            "new"
         
     | 
| 
      
 52 
     | 
    
         
            +
            "extend"
         
     | 
| 
      
 53 
     | 
    
         
            +
            "include"
         
     | 
| 
      
 54 
     | 
    
         
            +
            "is_a?"
         
     | 
| 
      
 55 
     | 
    
         
            +
            "self"
         
     | 
| 
      
 56 
     | 
    
         
            +
            "raise"
         
     | 
| 
      
 57 
     | 
    
         
            +
            "initialize"
         
     | 
| 
      
 58 
     | 
    
         
            +
            "&&"
         
     | 
| 
      
 59 
     | 
    
         
            +
            "||"
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
            # Metaprogramming
         
     | 
| 
      
 63 
     | 
    
         
            +
            "define_method"
         
     | 
| 
      
 64 
     | 
    
         
            +
            "attr_accessor"
         
     | 
    
        data/fuzz/run
    ADDED
    
    | 
         @@ -0,0 +1,21 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/bin/bash
         
     | 
| 
      
 2 
     | 
    
         
            +
            set -e
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
         
     | 
| 
      
 5 
     | 
    
         
            +
            if [ -d "$SCRIPTPATH/out/latest" ]
         
     | 
| 
      
 6 
     | 
    
         
            +
            then
         
     | 
| 
      
 7 
     | 
    
         
            +
                echo "out/latest directory already exists!"
         
     | 
| 
      
 8 
     | 
    
         
            +
                echo "Delete it to perform another fuzzer run."
         
     | 
| 
      
 9 
     | 
    
         
            +
                exit 1
         
     | 
| 
      
 10 
     | 
    
         
            +
            fi
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            # gitignored, so a fresh clone won't have this
         
     | 
| 
      
 13 
     | 
    
         
            +
            mkdir -p "$SCRIPTPATH/out"
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            AFL_NO_FORKSRV=1 AFL_SKIP_BIN_CHECK=1 \
         
     | 
| 
      
 16 
     | 
    
         
            +
                bundle exec afl-fuzz \
         
     | 
| 
      
 17 
     | 
    
         
            +
                -i "$SCRIPTPATH/cases" \
         
     | 
| 
      
 18 
     | 
    
         
            +
                -o "$SCRIPTPATH/out/latest" \
         
     | 
| 
      
 19 
     | 
    
         
            +
                -m 5000 \
         
     | 
| 
      
 20 
     | 
    
         
            +
                -x "$SCRIPTPATH/ruby.dict" \
         
     | 
| 
      
 21 
     | 
    
         
            +
                -- ruby bin/houndstooth.rb --instrument -f @@
         
     |