AssemblyScript / assemblyscript

A TypeScript-like language for WebAssembly.
https://www.assemblyscript.org
Apache License 2.0
16.6k stars 650 forks source link

Incorrect overriden function call #2819

Closed szd12 closed 4 months ago

szd12 commented 4 months ago

Bug description

Incorrect function will be called if a derived class does not have a function override.

Steps to reproduce

Paste this code into the AssemblyScript playground window at www.assemblyscript.org

export function fib(n: i32): i32 {
  return x.f(n);
}

class A {
  f(a:i32):i32 { return a+1; }
}
class B extends A {
  f(a:i32):i32 { return super.f(a)+10; }
}
class C extends B {
  f(a:i32):i32 { return super.f(a)+100; }
}
class D extends C {
}

let x:A = new D();

expected output:

fib(0) = 111
fib(1) = 112
fib(2) = 113
...

actual output:

fib(0) = 11
fib(1) = 12
fib(2) = 13
...

Note: It will work correctly, if you change the variable definition to this:

let x:D = new D();

or this:

let x:A = new C();

AssemblyScript version

v0.27.24

HerrCai0907 commented 4 months ago

Thanks for your report. It is a bug in compiler.

szd12 commented 4 months ago

I think the bogus code is in compiler.ts/finalizeOverrideStub

        // Also alias each extender inheriting this exact overload
        let extenders = classInstance.extenders;
        if (extenders) {
          for (let _values = Set_values(extenders), i = 0, k = _values.length; i < k; ++i) {
            let extender = _values[i];
// bogus vvvvvvvv
            let instanceMembers = extender.prototype.instanceMembers;
            if (instanceMembers && instanceMembers.has(instance.declaration.name.text)) {
              continue; // skip those not inheriting
            }
            builder.addCase(extender.id, stmts);
// bogus ^^^^^^^
          }
        }

I recommend changing it into something like this:

        // Also alias each extender inheriting this exact overload
        let extenders = classInstance.extenders;
        if (extenders) {
          for (let _values = Set_values(extenders), i = 0, k = _values.length; i < k; ++i) {
            let extender = _values[i];
// fix vvvvvvvv
            for(;;) {   // going up the extension hierarchy
                let instanceMembers = extender.prototype.instanceMembers;
                if (instanceMembers && instanceMembers.has(instance.declaration.name.text)) {
                  break; // skip those not inheriting
                }
                if(extender.base == classInstance) {
                    extender = _values[i];      // use original extender
                    builder.addCase(extender.id, stmts);
                    break;
                }
                extender = extender.base as Class;
            }
// fix ^^^^^^^
          }
        }

My project is running correctly after applying this change.