ethanblake4 / dart_eval

Extensible Dart interpreter for Dart with full interop
https://pub.dev/packages/dart_eval
BSD 3-Clause "New" or "Revised" License
334 stars 40 forks source link

Error propagation doesn't work correctly in combination with async/await #156

Open Noobware1 opened 11 months ago

Noobware1 commented 11 months ago

here I am trying to catch the error thrown by loadMediaDetails but I am unable to do so.

import 'dart:io';
import 'package:meiyou_extenstions/meiyou_extenstions.dart';

import 'build.dart';

void main(List<String> args) async {
  final file = File(args[0]).readAsStringSync();
  final query = args[1];

  final packages = {
    'meiyou': {'main.dart': fixImports(file), ...getAllExtractors(file, {})}
  };

  final compiled = ExtenstionComplier().compilePackages(packages);

  final pluginApi = ExtenstionLoader()
      .runtimeEval(compiled)
      .executeLib('package:meiyou/main.dart', 'main') as $BasePluginApi;

  print('Starting loadMediaDetails for $query');
  try {
    final media = await pluginApi.loadMediaDetails(
        SearchResponse(title: '', url: '', poster: '', type: ShowType.Movie));
    print(media);
  } catch (e) {
    print('Caught error');
  }

}

I mean it should print Caught error but instead it prints this

Starting loadMediaDetails for oppenheimer
Unhandled exception:
dart_eval runtime exception: type 'Null' is not a subtype of type 'String'
#0      $AppUtils.$parseHtml (package:meiyou_extenstions/src/bridge_models/utils_models/app_utils.dart:327:56)
#1      InvokeExternal.run (package:dart_eval/src/eval/runtime/ops/bridge.dart:107:44)
#2      Runtime.bridgeCall (package:dart_eval/src/eval/runtime/runtime.dart:847:12)
at <asynchronous gap>

RUNTIME STATE
=============
Program offset: 1090
Stack sample: [L0: Instance of '$BasePluginApi', L1: $"", L2: Instance of '$Completer<dynamic>', L3: Instance of '$JsonCodec', L4: $"", L5: $"https://aniwatch.to", L6: $"https://aniwatch.to", L7: $"/ajax/v2/episode/list/", L8: $"https://aniwatch.to/ajax/v2/episode/list/", L9: $"https://aniwatch.to/ajax/v2/episode/list/"]
Args sample: []
Call stack: [0, -1]
TRACE:
1084: PushConstant (C78)
1085: BoxString (L19)
1086: PushArg (L19)
1087: InvokeDynamic (L18.C79)
1088: PushReturnValue ()
1089: PushArg (L20)
1090: InvokeExternal (Ex#159)  <<< EXCEPTION
1091: PushReturnValue ()
1092: PushConstant (C97)
1093: BoxString (L22)

#0      Runtime.bridgeCall (package:dart_eval/src/eval/runtime/runtime.dart:857:7)
#1      Await._suspend (package:dart_eval/src/eval/runtime/ops/bridge.dart:169:13)
<asynchronous suspension>
ethanblake4 commented 11 months ago

I think this is primarily caused because error handling isn't really implemented yet in combination with async/await, so I'll rename this issue to reflect that. In the meantime you might be able to make this work by replacing async/await stuff with Future.then() etc in the affected codepath.

Noobware1 commented 11 months ago

It's ok I can wait that'll be more easier.

Noobware1 commented 11 months ago

But I am confused about one thing this code is not eval code this is real dart code so error handling is done in actually dart, so it should fine right?

ethanblake4 commented 11 months ago

Well it appears that the loadMediaDetails() eval code implementation contains an await in it or calls a function that uses await.

Noobware1 commented 11 months ago

So the error thrown in loadMediaDetails should catched by above catch block but It doesn't?

ethanblake4 commented 11 months ago

Yeah. Because loadMediaDetails has an await somewhere in it, that currently breaks its ability to throw exceptions across the bridge.

Noobware1 commented 11 months ago

Ic, Thanks for the explanation.

Noobware1 commented 11 months ago

If this issue gets resolved will I be able to bridge these type of functions and not get any error?

T? trySync<T>(T Function() fun) {
  try {
    return fun();
  } catch (e) {
    return null;
  }
}

Future<T?> tryAsync<T>(Future<T> Function() fun) async {
  try {
    return await fun();
  } catch (e) {
    return null;
  }
}
ethanblake4 commented 11 months ago

The first function trySync should already work, no? But yeah once I get around to fixing this you'd probably be able to use tryAsync too either as a bridge or eval function.

Noobware1 commented 11 months ago

yeah, it works.

test('trySync', () {
      final value = compiler.compileWriteAndLoad({
        'example': {
          'main.dart': '''
          import 'package:meiyou_extensions_lib/meiyou_extensions_lib.dart';

          int? main() {
            return AppUtils.trySync<int>(() => StringUtils.toInt('this is not a number'));
          }
          '''
        }
      }).executeLib('package:example/main.dart', 'main');
      expect((value as $Value).$value, null);
    });
Noobware1 commented 11 months ago

Any update on this?

ethanblake4 commented 11 months ago

This is a complicated issue to solve with a lot of edge cases, it's not going to be done that soon unfortunately.

Noobware1 commented 11 months ago

Is there any way I can help cause I need this fix bad but I also understand where you coming from so it's ok if it can't be done right now

ethanblake4 commented 11 months ago

Alright, I figured out a temporary change I could do to allow errors to propagate through an await to the top-level. This isn't really correct since try/catch blocks inside dart_eval still won't work at all, but at least it should let a try/catch in real Dart that surrounds it function for the most part. Released in v0.7.3, don't close this though since it's not actually fixed in a correct way

Noobware1 commented 11 months ago

Thanks a lot man

Noobware1 commented 8 months ago

Alright, I figured out a temporary change I could do to allow errors to propagate through an await to the top-level. This isn't really correct since try/catch blocks inside dart_eval still won't work at all, but at least it should let a try/catch in real Dart that surrounds it function for the most part. Released in v0.7.3, don't close this though since it's not actually fixed in a correct way

try catch in real dart still isn't able to catch it sadly.

ethanblake4 commented 8 months ago

Can you give more info on the error you get? I have tested it and this test passes successfully:

test('Exception bubbles through asynchronous gap', () {
      final runtime = compiler.compileWriteAndLoad({
        'example': {
          'main.dart': '''
            import 'dart:async';

            void main() async {
              await Future.delayed(const Duration(milliseconds: 10));
              throw 'error';
            }
          '''
        }
      });
      expect(() => runtime.executeLib('package:example/main.dart', 'main'),
          throwsA($String('error')));
    });