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

C#: Improve GD.PushError and GD.PushWarning #79280

Merged
merged 1 commit into from
Aug 3, 2023

Conversation

raulsntos
Copy link
Member

@raulsntos raulsntos commented Jul 10, 2023

Users that use these methods would expect the reported location printed by these methods to correspond to a location in their project source files. Specifically, they'd expect to see the file path and line number at which they call these methods, and not the location of the C++ code (which is always the same). Now, these methods are a lot more useful since users can know which line in their source code printed the error/warning.

4.1.stable This PR
error4.1 error4.2

@raulsntos
Copy link
Member Author

Removed the breaks compat label, since I changed this to avoid breaking compatibility.

- Use the name, file path and line number of the caller that invokes
`GD.PushError` and `GD.PushWarning` instead of the location in the C++
`runtime_interop.cpp` file.
- Improvements to getting the C# stack trace.
  - Use C# type keywords for built-in types in method declarations.
  - Remove extra space before each parameter in method declarations.
  - Skip one more frame to avoid `NativeInterop.NativeFuncs`.
  - Skip methods annotated with the `[StackTraceHidden]` attribute.
- Improvements to `ScriptEditorDebugger` when source is in project.
  - Avoid overriding error metadata when the source is inside the
project file.
  - Use the source function in the title when the source is inside
the project file.

Users that use these methods would expect the reported location printed
by these methods to correspond to a location in their project source files.
Specifically, they'd expect to see the file path and line number at which
they call these methods, and not the location of the C++ code (which is
always the same). Now, these methods are a lot more useful since users
can know which line in their source code printed the error/warning.
Copy link
Member

@RedworkDE RedworkDE left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the a stack from is from source generated code attempting to open its source will fail because the source doesn't exist:

ERROR: Cannot open file 'C:/dir/Net7/Godot.SourceGenerators/Godot.SourceGenerators.ScriptMethodsGenerator/Test_Node_ScriptMethods.generated.cs'.
   at: (modules\mono\utils\string_utils.cpp:152)
ERROR: Failed to read file: 'C:/dir/Net7/Godot.SourceGenerators/Godot.SourceGenerators.ScriptMethodsGenerator/Test_Node_ScriptMethods.generated.cs'.
   at: (modules\mono\csharp_script.cpp:2754)
ERROR: Cannot load C# script file 'C:/dir/Net7/Godot.SourceGenerators/Godot.SourceGenerators.ScriptMethodsGenerator/Test_Node_ScriptMethods.generated.cs'.
   at: (modules\mono\csharp_script.cpp:2820)
ERROR: Failed loading resource: C:/dir/Net7/Godot.SourceGenerators/Godot.SourceGenerators.ScriptMethodsGenerator/Test_Node_ScriptMethods.generated.cs. Make sure resources have been imported by opening the project in the editor at least once.
   at: (C:\dir\godot_4.0.1\core/io/resource_loader.cpp:274)

(Same probably happens for libraries where the sources are not available, but paths are embedded in the pdb)
Can we pretend that we don't know about the source file if it doesn't exist and avoid these errors?
Also If you open a frame from a file that does exist but is not part of the project, the editor tries to convince you to save it as part of the project, if you save any script.

Also about the formatting of method signatures: if we want to format them in C# style, reference parameters are formatted as Type& instead of ref Type and inner types as Outer+Inner instead of Outer.Inner

internal static unsafe StackFrame? GetCurrentStackFrame(int skipFrames = 0)
{
// We skip 2 frames:
// The first skipped frame is the current method.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this always reliably work or can inlining mean that these frames can potentially not exist? (Idk if such frames are available in C#)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a really good point, I don't know.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still a concern or is this not a problem since it's only going to execute in Debug builds which hopefully disable inlining optimizations?

@raulsntos
Copy link
Member Author

If the a stack from is from source generated code attempting to open its source will fail because the source doesn't exist

There is a check to avoid the source function if it's not part of the project. It only checks the path, is that check not enough?

Can we pretend that we don't know about the source file if it doesn't exist and avoid these errors?

That sounds reasonable, I'm a little worried about how this would affect the script debugger since I think it assumes the entire stack trace contains methods defined in the user project. Maybe we should filter everything that is not part of the user project from the stack trace, but that feels wrong since it may be useful information.

@RedworkDE
Copy link
Member

There is a check to avoid the source function if it's not part of the project. It only checks the path, is that check not enough?

What check do you mean?

#ifdef TOOLS_ENABLED
Ref<FileAccess> file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES);
ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), Ref<Resource>(), "Resource file not found: " + p_path + ".");
#endif

Could only trigger if the previous errors didn't happen.

I would add a single error to this function: (tho I suppose we could do that in a different PR as well)

void EditorDebuggerNode::_error_selected(const String &p_file, int p_line, int p_debugger) {
Ref<Script> s = ResourceLoader::load(p_file);
emit_signal(SNAME("goto_script_line"), s, p_line - 1);
}

@YuriSizov YuriSizov modified the milestones: 4.x, 4.2 Jul 26, 2023
@raulsntos
Copy link
Member Author

What check do you mean?

This one:

bool source_is_project_file = oe.source_file.begins_with("res://");

if (!source_is_project_file) {
// Only override metadata if the source is not inside the project.
error->set_metadata(0, meta);
}

The metadata is then used to determine which file to open:

Array meta = selected->get_metadata(0);
if (meta.size() == 0) {
return;
}
emit_signal(SNAME("error_selected"), String(meta[0]), int(meta[1]));

I would add a single error to this function: (tho I suppose we could do that in a different PR as well)

I'd prefer to do it in a different PR.

@YuriSizov YuriSizov requested a review from RedworkDE August 2, 2023 19:28
Copy link
Member

@RedworkDE RedworkDE left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs #79342 to work correctly on windows, but is still a significant improvement without.

@akien-mga akien-mga merged commit 8b6c867 into godotengine:master Aug 3, 2023
@akien-mga
Copy link
Member

Thanks!

@raulsntos raulsntos deleted the dotnet/better-push-error branch August 3, 2023 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment