Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Null-related issues with bounded polymorphism #5726

Closed
RustanLeino opened this issue Aug 27, 2024 · 0 comments · Fixed by #5738
Closed

Null-related issues with bounded polymorphism #5726

RustanLeino opened this issue Aug 27, 2024 · 0 comments · Fixed by #5738
Assignees
Labels
during 3: execution of incorrect program An bug in the verifier that allows Dafny to run a program that does not correctly implement its spec kind: bug Crashes, unsoundness, incorrect output, etc. If possible, add a `part:` label part: resolver Resolution and typechecking part: verifier Translation from Dafny to Boogie (translator)

Comments

@RustanLeino
Copy link
Collaborator

Dafny version

4.7.0

Code to produce this issue

// Here are four examples that exhibit (the same or related) null problems

// Example 0

trait G {
  const Three := 3
  const Six := Three + Three
}

class E extends G { }

method DoIt<G1 extends G>(g1: G1) {
  var g := g1 as G;
  print g.Six, "\n";
}

method Main() {
  DoIt<E?>(null); // should be an error: E? does not extend G
}

// Example 1

trait G extends object {
  const Three := 3
  const Six := Three + Three
}

class E extends G { }

method DoIt<G1 extends G?>(g1: G1) {
  var g := g1 as G; // should be an error: g1 is a G?, but it might not be a G (because it might be null)
  print g.Six, "\n";
}

method Main() {
  DoIt<E?>(null);
}

// Example 2

trait G extends object {
  const Three := 3
  const Six := Three + Three
}

class E extends G { }

method DoIt<G1 extends G?>(g1: G1) {
  var g := g1 as G?;
  print g.Six, "\n"; // should be an error: this is trying to deference a G?, but it is not known whether or not g is null
}

method Main() {
  DoIt<E?>(null);
}

// Example 3

trait G {
  const Three := 3
  const Six := Three + Three
}

class E extends G { }

trait Thing<G1 extends G> {
  function GetG(): (g: G1)

  method PrintGSix() {
    var g1 := GetG();
    var g := g1 as G;
    print g.Six, "\n";
  }
}

class MyThing extends Thing<E?> { // should be an error: E? does not extend G
  function GetG(): (g: E?) {
    null
  }
}

method Main() {
  var thing := new MyThing;
  thing.PrintGSix();
}

Command to run and resulting output

Each of the programs above passes resolution and verification, but the compiled program causes a crash. For example:

% dafny run test.dfy --type-system-refresh --general-traits=datatype

Dafny program verifier finished with 2 verified, 0 errors
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at _module.__default.DoIt[__G1](__G1 g1) in test.cs:line 5707
   at _module.__default._Main(ISequence`1 __noArgsParameter) in test.cs:line 5712
   at __CallToMain.<>c__DisplayClass0_0.<Main>b__0() in test.cs:line 5742
   at Dafny.Helpers.WithHaltHandling(Action action) in test.cs:line 1673
   at __CallToMain.Main(String[] args) in test.cs:line 5742

What happened?

The compiled program crashes.

What type of operating system are you experiencing the problem on?

Mac

@RustanLeino RustanLeino added kind: bug Crashes, unsoundness, incorrect output, etc. If possible, add a `part:` label part: resolver Resolution and typechecking part: verifier Translation from Dafny to Boogie (translator) during 3: execution of incorrect program An bug in the verifier that allows Dafny to run a program that does not correctly implement its spec labels Aug 27, 2024
@RustanLeino RustanLeino self-assigned this Aug 29, 2024
RustanLeino added a commit to RustanLeino/dafny that referenced this issue Aug 30, 2024
RustanLeino added a commit that referenced this issue Sep 5, 2024
)

Fixes #5719
Fixes #5726

## Description

This PR solves both soundness and incompleteness problems related to
when a nullable type extends a trait.

To fix the soundness issue (#5726), the resolver was changed to check
type bounds after type inference. For example, the resolver now
generates an error if a nullable reference type `E?` is used as a type
argument with a bound `B` that is not a nullable reference type. Note
that this check cannot be done completely during pre-type inference,
because pre-types don't distinguish between `E?` and `E`; therefore, the
definitive check for this is done in the same place where type arguments
are checked to have the specified type characteristics (`(==)`, `(0)`,
...).

To fix the incompleteness issues (#5719), changes were made in the
generated axioms. To describe them, consider the following types:

``` dafny
trait RefTrait extends object { }
trait GeneralTrait { }  // does not extend object, and thus is not a reference type
class /* or trait */ E extends RefTrait, GeneralTrait { }
```

* Previously, this generated the following _type axioms_ (and analogous
_allocation axioms_ for `$IsAlloc`):

    ``` boogie
    // type axiom for trait parent: E extends RefTrait
    axiom (forall $o: ref ::
      { $Is($o, Tclass.E?()) }
$o != null && $Is($o, Tclass.E?()) // CHANGE: remove o != null
==> $Is($o, Tclass.RefTrait())); // CHANGE: RefTrait --> RefTrait?
    
    // type axiom for trait parent: E extends GeneralTrait
    axiom (forall $o: ref ::
{ $Is($o, Tclass.E?()) } // CHANGE: replace E? with E
$o != null && $Is($o, Tclass.E?()) // CHANGE: remove o != null, and
replace E? with E
         ==> $IsBox($Box($o), Tclass.GeneralTrait()));
    ```

    The new axioms say:

    ``` boogie
    // type axiom for trait parent: E extends RefTrait
    axiom (forall $o: ref ::
      { $Is($o, Tclass.E?()) }
      $Is($o, Tclass.E?()) ==> $Is($o, Tclass.RefTrait?()));
    
    // type axiom for trait parent: E extends GeneralTrait
    axiom (forall $o: ref ::
       { $Is($o, Tclass.E()) }
      $Is($o, Tclass.E()) ==> $IsBox($Box($o), Tclass.GeneralTrait()));
    ```

* To make the first of these axioms still be able to connect `E?` with
`RefTrait`, the axiom that connects a nullable type `X?` with its
non-null reference type `X` was extended with an additional matching
pattern:

    ``` boogie
    // $Is axiom for non-null type X
    axiom (forall c#0: ref :: 
      { $Is(c#0, Tclass.X()) }
{ $Is(c#0, Tclass.X?()) } // CHANGE: this matching pattern was added
      $Is(c#0, Tclass.X()) <==> $Is(c#0, Tclass.X?()) && c#0 != null);
    ```

* The property that a type parameter `T` with a bound `B` has is
(elsewhere) given as a quantification over boxes. To make this property
known of a type that extends a trait, the following axioms were added:

    ``` boogie
    axiom (forall bx: Box :: 
      { $IsBox(bx, Tclass.E?()) } 
      $IsBox(bx, Tclass.E?()) ==> $IsBox(bx, Tclass.RefTrait?()));
    
    axiom (forall bx: Box :: 
      { $IsBox(bx, Tclass.E()) } 
      $IsBox(bx, Tclass.E()) ==> $IsBox(bx, Tclass.GeneralTrait()));
    ```

    These are essentially box versions of the type axioms above.



<small>By submitting this pull request, I confirm that my contribution
is made under the terms of the [MIT
license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt).</small>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
during 3: execution of incorrect program An bug in the verifier that allows Dafny to run a program that does not correctly implement its spec kind: bug Crashes, unsoundness, incorrect output, etc. If possible, add a `part:` label part: resolver Resolution and typechecking part: verifier Translation from Dafny to Boogie (translator)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant