Skip to content

Ability to catch AppDomain.UnhandledException in a not-hosted script (cscs) ? #257

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

Closed
MarLupin opened this issue Oct 5, 2021 · 4 comments

Comments

@MarLupin
Copy link

MarLupin commented Oct 5, 2021

I'd like to be able to catch unhandled exceptions in a script, just like in a regular console application (where try-catch was missing for some reason). For example (run by cscs.exe):

using System;
class Script {
	static void Main()
	{
		AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(ExceptionEventHandler);
		AppDomain.CurrentDomain.ProcessExit += new EventHandler(ProcessExitEventHandler);
		throw new Exception("I'd like to intercept it without try-catch.");
	}
	static void ProcessExitEventHandler(object sender, EventArgs args)
	{
		Console.WriteLine("ProcessExit intercepted");
	}
	static void ExceptionEventHandler(object sender, UnhandledExceptionEventArgs args)
	{
		Console.WriteLine("UnhandledException intercepted");
		Console.WriteLine("Message: "+((Exception)args.ExceptionObject).Message);
		Environment.Exit(0);
	}
}

The result of running script is:

Error: Specified file could not be executed.
I'd like to intercept it without try-catch.
ProcessExit intercepted

I would rather get something like this:

Error: Specified file could not be executed.
UnhandledException intercepted
Message: I'd like to intercept it without try-catch.
ProcessExit intercepted

I think I understand why this is happening. When the script is run through cscs there are really no unhandled exceptions because they are all caught on the way:

try {
... executor.Execute ...
catch
{
	if (!CSSUtils.IsRuntimeErrorReportingSuppressed)
		print("Error: Specified file could not be executed." + NewLine);
	throw;
} ...

So, I would like to ask, if it would be possible for cscs to check, after catching an unhandled exception:
If there is a delegate in CurrentDomain.UnhandledException, then pack the exception into UnhandledExceptionEventArgs and call that delegate ?
Or maybe there is some other way to achieve this?

@oleg-shilo
Copy link
Owner

I think it is possible.
Marking it as "enhancement"

@oleg-shilo
Copy link
Owner

Unfortunately AppDomain.UnhandledException is not as simple as any other events. It's not accessible as let's say AppDomain.DomainUnload:

var fields = AppDomain.CurrentDomain
    .GetType()
    .GetFields(BindingFlags.NonPublic |
                BindingFlags.Static |
                BindingFlags.Instance |
                BindingFlags.FlattenHierarchy |
                BindingFlags.IgnoreCase);

image

Thus despite all my effort, I do not see how this enhancement can be implemented. :(

@MarLupin
Copy link
Author

Unfortunately, it looks like you are right. I haven't checked this before, but seems there's no way to get into the UnhandledException delegates in a domain except += and -=.

But, if you still have a bit of spare time, I probably have an idea for a simple workaround for this limitation. It's not very elegant, but it works.

dbg.cs

public class dbg
{
	public static UnhandledExceptionEventHandler UnhandledException = null; // <-- instead CurrentDomain for scripts
	...
}

Utils.cs

internal static string DbgInjectionCodeInterface = @"// Auto-generated file
...
partial class dbg
{
	public static UnhandledExceptionEventHandler UnhandledException = null; // <-- expose to script
	...
}";

csscript.cs

try {
... executor.Execute ...
catch (Exception e)
{
	dbg.UnhandledException?.Invoke(null, new UnhandledExceptionEventArgs(e, true)); // <-- callback to script if exists

	if (!CSSUtils.IsRuntimeErrorReportingSuppressed)
		print("Error: Specified file could not be executed." + NewLine);
	throw;
}

The sample script I posted at the beginning only needs a slight modification and works as expected.

#if CS_SCRIPT
	dbg.UnhandledException = new UnhandledExceptionEventHandler(ExceptionEventHandler);
#else	
	AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(ExceptionEventHandler);
#endif

I'm not sure putting this delegate in the dbg class is a good idea, but it was on hand. Besides, exception handling seems logically related to debugging, so maybe this is the right place. If this would be acceptable, then in my opinion it may solve the problem of the script ability to catching its own unhandled exceptions.
It may not be a commonly used feature, but it can be convenient in some situations, such as custom logging, etc.

@oleg-shilo
Copy link
Owner

oleg-shilo commented Oct 18, 2021

Yeah, I thought about this too. I mean using a global delegate.
But stopped short since the effort of assigning delegate in the script is arguably the same as wrapping the script in the try...catch

But your use of partial dbg that can be combined with makes it rather nice as this is now possible:

using static dbg;

. . .

static void Main()
{
    UnhandledException += (sender, args) =>
    {
        Console.WriteLine("UnhandledException intercepted");
	Console.WriteLine("Message: "+((Exception)args.ExceptionObject).Message);
	Environment.Exit(0);
    };
    . . .

And you can even use your conditional compilation:

#if CS_SCRIPT
	using static dbg;
#else	
	using static AppDomain.CurrentDomain;
#endif

So... I am not giving up just yet :)

oleg-shilo pushed a commit that referenced this issue Nov 3, 2021
oleg-shilo pushed a commit that referenced this issue Nov 4, 2021
- Updated `-speed` and `-code` with the complete support `-ng:*` switches
- Added `IEvaluator.IsCachingEnabled`. Ite as always available from the conctrete types implementing `IEvaluator` and now it is moved directly to the interface.
- Added `-servers:start` and `-servers:stop` command to control both Roslyn and csc build servers at the same time
- CSScriptLib: Native API `CacheEnabled` marked as obsolete
- Issue #258: Can not run scripts after installing VS2022
- Issue #257: Ability to catch AppDomain.UnhandledException in a not-hosted script (cscs)
- Issue #255: Relative path for cscs.exe -out option results in wrong output folder
- Issue #254: Script merger for hosted scripts
- Issue #253: Supports both .Net Framework and .Net 5
- Issue #252: System.NullReferenceException: Object reference not set to an instance of an object. (updated API doc)
- Added auto-generation of the CLI MD documentation with `-help cli:md`. To be used to generate GitHub wiki page during the build
- Fixed Debian packaging problem (`/n/r` needed replacement with `\n`)
oleg-shilo added a commit that referenced this issue Nov 14, 2021
### Misc

- Added auto-generation of the CLI MD documentation with -help cli:md. To be used to generate GitHub wiki page during the build
- Fixed Debian packaging problem (/n/r needed replacement with \n)
- Issue #253: Supports both .Net Framework and .Net 5

### CLI

- Updated -speed and -code with the complete support -ng:* switches
- Added -servers:start and -servers:stop command to control both Roslyn and csc build servers at the same time
- Issue #258: Can not run scripts after installing VS2022
- Issue #257: Ability to catch AppDomain.UnhandledException in a not-hosted script (cscs)
- Issue #255: Relative path for cscs.exe -out option results in wrong output folder
- Issue #254: Script merger for hosted scripts
- Issue #252: System.NullReferenceException: Object reference not set to an instance of an object. (updated API doc)

### CSScriptLib

- Native API CacheEnabled marked as obsolete
- Added IEvaluator.IsCachingEnabled. It is always available from the concrete types implementing IEvaluator and now it is moved directly to the interface.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants