Skip to content

Instantly share code, notes, and snippets.

@hexagonrecursion
Last active May 13, 2024 23:39
Show Gist options
  • Save hexagonrecursion/c3036f79d1a04892336073344b4bf8ac to your computer and use it in GitHub Desktop.
Save hexagonrecursion/c3036f79d1a04892336073344b4bf8ac to your computer and use it in GitHub Desktop.
A trip down to edge case city

Functions

An external function replaces the runnig public function

  • Can crash the game: ❓ unknown
  • Bypasses typechecking: ✅ no
  1. Program A defines a public function
  2. Program B uses the function
  3. Program B is in the middle of running the function or in the middle of executing CBotInstrCall before calling the function
  4. The game is saved
  5. A new version of the game introduces a built-in with the same name
  6. The game is loaded
  7. Program A fails to compile bedcause it tries to overload an external function
  8. Program B compiles succesfully if the signature of the new external function happens to allow this
  9. Program B continues running.

An external function replaces a public function - causes a different overload to be selected

  • Can crash the game: ❌ yes because this allows arbitrary user-supplied code to be replaced with different user-supplied code in a runnig program
  • Bypasses typechecking: ✅ no
  1. Program A defines a public function foo()
  2. Program B defines a function bar() with two overloads
  3. Program B calls bar(..., foo(...), ...)
  4. The interpreter picks the first overload
  5. Program B is in the middle of running bar()
  6. The game is saved
  7. A new version of the game introduces a built-in named foo()
  8. The game is loaded
  9. Program A fails to compile - same as above
  10. Program B happens to compile successfully, but the game picks the second overload of bar()
  11. Program B continues running

A built-in constant, external function, field of a built-in class or method of a built-in class changes return type - causes a different overload to be selected

  • Can crash the game: ❌ yes because this allows arbitrary user-supplied code to be replaced with different user-supplied code in a runnig program
  • Bypasses typechecking: ✅ no

Similar to above

The return type of a public function changes - breaks save file

  • Can crash the game: ❌ yes
  • Bypasses typechecking: ✅ no
  1. Program A defines a public function fooA()
  2. Program B defines a public function foo() with a different return type than fooA()
  3. fooA() is renamed to foo() and the editor is closed by pressing "Cancel". Program A now has a compilation error because foo() is already defined
  4. Program C defines a function bar() with two overloads
  5. Program C calls bar(..., foo(...), ...)
  6. The interpreter picks the first overload
  7. Program C is in the middle of running bar()
  8. The game is saved
  9. The game is loaded
  10. Now program A compiles fine and program B has a compilation error because foo() is already defined
  11. Program C picks a different overoad of bar() because the return type of foo() is now different

The return type of a public function changes at run time

  • Can crash the game: ❓ unknown
  • Bypasses typechecking: ❌ yes - may cause an expression to evaluate to a different type than the one it had at compile time
  1. Bot A defines a public function foo()
  2. Bot B is running a program that uses foo()
  3. The player edits foo() and changes its return type
  4. The next time bot B calls foo() it will return an unexpected type

The body of a public function (or a local function it depends on) is edited while it is running

  • Can crash the game: ❌ yes
  • Bypasses typechecking: ❓ unknown

See colobot/colobot#1628

A public function is recompiled without changes while it is running

  • Can crash the game: ❌ yes
  • Bypasses typechecking: ✅ no

See colobot/colobot#1494

Classes

Where do I even begin? Here are a few fun observations:

  1. A reference to a class is implicitly convertible to a reference to its base class. We check this when assigning to a variable. This also affects overload resolution. Seems quite sensible, except the inheritance hierarchy can change at any time Foo extends Bar can become Foo extends Moo or even Bar extends Foo.
  2. Most issues related to public function signature or body changing hat I have described above probably apply to methods too plus classes have inheritance plus methods can be overridden, which probably adds more tricky edge cases, especially since the inheritance hierarchy is changeable.
  3. One file can add a method to a class defined in a different file void Foo::Bar() { message("a new method"); }. How? Why? At least the new method is only visible from the file that adds it and does not appear to affect other files.

A note to myself

0 or more required parameters + 0 or more overridden defaults + 0 or more defaults

  1. Public function

    1. Parameter added
    2. Parameter removed
    3. Default parameter type changes
    4. Parameter default removed
    5. Parameter default added
    6. Overload added
    7. Overload removed
  2. Public class

    1. Class of instance removed
    2. Class of instance added
    3. TODO: Base class of instance removed
    4. TODO: Base class of instance added
    5. TODO: Base class of instance is replaced with a different class
    6. TODO: Public field of instance removed
    7. TODO: Public field of instance added
    8. TODO: class hierarchy changes - changes overload resolution
    9. TODO

Functions can be:

  1. Local
  2. Public
  3. External

When compiling

  • External call shadows all user-defined functions with the same name - overloads are not checked Irrelevant: we can not define an overload for a built-in
  • Public and local functions participate in overload resolution on equal terms
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment