Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,6 @@ test/Sentry.Unity.Tests/other/**
samples/unity-of-bugs/*IL2CPPCache/
samples/unity-of-bugs/*IL2CPPStats/
*Player.link.log

# Gradle caches generated by the VS Code Gradle extension scanning test fixtures
.gradle/
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- The SDK now provides line number support for managed exceptions for Unity 6.5 and newer ([#2805](https://github.com/getsentry/sentry-unity/pull/2805))
- Added experimental auto game-metrics. When enabled, the SDK periodically collects common performance metrics and sends them to Sentry via the metrics API. ([#2777](https://github.com/getsentry/sentry-unity/pull/2777))

### Dependencies
Expand Down
29 changes: 29 additions & 0 deletions src/Sentry.Unity.Editor/Il2CppBuildPreProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Sentry.Unity.Editor;
internal class Il2CppBuildPreProcess : IPreprocessBuildWithReport
{
internal const string SourceMappingArgument = "--emit-source-mapping";
internal const string LinkSymbolsArgument = "--link-symbols";
private static IDiagnosticLogger? Logger;

public int callbackOrder => 0;
Expand All @@ -33,6 +34,34 @@ public void OnPreprocessBuild(BuildReport report)
SetAdditionalIl2CppArguments(options,
PlayerSettings.GetAdditionalIl2CppArgs,
PlayerSettings.SetAdditionalIl2CppArgs);

SetAdditionalUnityLinkerArguments(options,
() => UnityLinkerDiagnosticSwitch.GetValue(Logger),
arguments => UnityLinkerDiagnosticSwitch.SetValue(arguments, Logger));
}

internal static void SetAdditionalUnityLinkerArguments(SentryUnityOptions options, Func<string?> getArguments, Action<string> setArguments)
{
var arguments = getArguments.Invoke();

if (options.Il2CppLineNumberSupportEnabled)
{
if (arguments?.Contains(LinkSymbolsArgument) == true)
{
Logger?.LogDebug("Additional UnityLinker argument '{0}' already present.", LinkSymbolsArgument);
return;
}

Logger?.LogDebug("IL2CPP line number support enabled - Adding additional UnityLinker argument.");
setArguments.Invoke(string.IsNullOrWhiteSpace(arguments)
? LinkSymbolsArgument
: $"{arguments} {LinkSymbolsArgument}");
}
else if (arguments?.Contains(LinkSymbolsArgument) == true)
{
Logger?.LogDebug("IL2CPP line number support disabled - Removing additional UnityLinker argument.");
setArguments.Invoke(arguments.Replace(LinkSymbolsArgument, "").Trim());
}
}

internal static void SetAdditionalIl2CppArguments(SentryUnityOptions options, Func<string> getArguments, Action<string> setArguments)
Expand Down
90 changes: 90 additions & 0 deletions src/Sentry.Unity.Editor/UnityLinkerDiagnosticSwitch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Reflection;
using Sentry.Extensibility;
using UnityEngine;

namespace Sentry.Unity.Editor;

/// <summary>
/// Provides access to Unity's 'VMUnityLinkerAdditionalArgs' diagnostic switch. Unity feeds its value into the
/// UnityLinker's additional arguments and, unlike the 'UNITYLINKER_ADDITIONAL_ARGS' environment variable, it is part
/// of the Bee build graph's inputs. That means changing it invalidates the cached graph the same way the additional
/// IL2CPP arguments do - without it the linker keeps running with whatever arguments the cached graph was built with.
/// The switch is internal to Unity, so we have to go through reflection to get to it.
/// </summary>
internal static class UnityLinkerDiagnosticSwitch
{
internal const string SwitchName = "VMUnityLinkerAdditionalArgs";

public static string? GetValue(IDiagnosticLogger? logger = null)
{
var diagnosticSwitch = GetSwitch(logger);
if (diagnosticSwitch is null)
{
return null;
}

try
{
return diagnosticSwitch.GetType().GetProperty("value")?.GetValue(diagnosticSwitch) as string;
}
catch (Exception e)
{
logger?.LogWarning("Failed to read the '{0}' diagnostic switch. Reason: {1}", SwitchName, e.Message);
return null;
}
}

public static bool SetValue(string value, IDiagnosticLogger? logger = null)
{
var diagnosticSwitch = GetSwitch(logger);
if (diagnosticSwitch is null)
{
return false;
}

try
{
var property = diagnosticSwitch.GetType().GetProperty("value");
if (property is null)
{
logger?.LogWarning("Failed to resolve the value of the '{0}' diagnostic switch.", SwitchName);
return false;
}

property.SetValue(diagnosticSwitch, value);
return true;
}
catch (Exception e)
{
logger?.LogWarning("Failed to set the '{0}' diagnostic switch. Reason: {1}", SwitchName, e.Message);
return false;
}
}

private static object? GetSwitch(IDiagnosticLogger? logger)
{
try
{
var method = typeof(Debug).GetMethod("GetDiagnosticSwitch", BindingFlags.Static | BindingFlags.NonPublic);
if (method is null)
{
logger?.LogWarning("Failed to resolve 'Debug.GetDiagnosticSwitch'.");
return null;
}

var diagnosticSwitch = method.Invoke(null, new object[] { SwitchName });
if (diagnosticSwitch is null)
{
logger?.LogWarning("The diagnostic switch '{0}' does not exist.", SwitchName);
}

return diagnosticSwitch;
}
catch (Exception e)
{
logger?.LogWarning("Failed to access the '{0}' diagnostic switch. Reason: {1}", SwitchName, e.Message);
return null;
}
}
}
16 changes: 16 additions & 0 deletions test/IntegrationTest/Integration.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,22 @@ Describe "Unity $($env:SENTRY_TEST_PLATFORM) Integration Tests" {
$exception.stacktrace | Should -Not -BeNullOrEmpty
}

It "Resolves the throw frame to its source line" {
if ($script:Platform -in "WebGL") {
Set-ItResult -Skipped -Because "Source-line assertions are unsupported on $script:Platform"
Comment thread
cursor[bot] marked this conversation as resolved.
return
}

$frame = $runEvent.exception.values[0].stacktrace.frames |
Where-Object { $_.module -eq "IntegrationTester" -and $_.function -eq "ThrowException" } |
Select-Object -First 1

$frame | Should -Not -BeNullOrEmpty
$frame.absPath | Should -Match "[\\/]Assets[\\/]Scripts[\\/]IntegrationTester\.cs$"
$frame.lineNo | Should -Be 219
$frame.symbolicatorStatus | Should -Be "symbolicated"
}

It "Has error level" {
($runEvent.tags | Where-Object { $_.key -eq "level" }).value | Should -Be "error"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ private static void DoSomeWork()
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowException()
{
// Integration.Tests.ps1 asserts this throw's line number. Update it when moving this.
throw new InvalidOperationException("Integration test exception");
}

Expand Down
54 changes: 54 additions & 0 deletions test/Sentry.Unity.Editor.Tests/Il2CppBuildPreProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,58 @@ public void SetAdditionalArguments_Il2CppDisabledAndArgumentAlreadyAdded_Removes
Assert.That(resultingArguments, Does.Contain(expectedArgument));
Assert.That(resultingArguments, Does.Not.Contain(Il2CppBuildPreProcess.SourceMappingArgument));
}

[Test]
public void SetAdditionalUnityLinkerArguments_Il2CppEnabled_AddsArgument()
{
var options = new SentryUnityOptions { Il2CppLineNumberSupportEnabled = true };

Il2CppBuildPreProcess.SetAdditionalUnityLinkerArguments(options, () => null, s => resultingArguments = s);

Assert.That(resultingArguments, Is.EqualTo(Il2CppBuildPreProcess.LinkSymbolsArgument));
}

[Test]
public void SetAdditionalUnityLinkerArguments_Il2CppDisabled_DoesNotAddArgument()
{
var options = new SentryUnityOptions { Il2CppLineNumberSupportEnabled = false };

Il2CppBuildPreProcess.SetAdditionalUnityLinkerArguments(options, () => null, s => resultingArguments = s);

Assert.That(resultingArguments, Does.Not.Contain(Il2CppBuildPreProcess.LinkSymbolsArgument));
}

[Test]
public void SetAdditionalUnityLinkerArguments_Il2CppEnabled_ExistingArgumentsDoNotGetOverwritten()
{
var options = new SentryUnityOptions { Il2CppLineNumberSupportEnabled = true };
var expectedArgument = "--MyArgument";

Il2CppBuildPreProcess.SetAdditionalUnityLinkerArguments(options, () => expectedArgument, s => resultingArguments = s);

Assert.That(resultingArguments, Is.EqualTo($"{expectedArgument} {Il2CppBuildPreProcess.LinkSymbolsArgument}"));
}

[Test]
public void SetAdditionalUnityLinkerArguments_Il2CppDisabledAndArgumentAlreadyAdded_RemovesArgument()
{
var options = new SentryUnityOptions { Il2CppLineNumberSupportEnabled = false };
var expectedArgument = "--MyArgument";
arguments = $"{expectedArgument} {Il2CppBuildPreProcess.LinkSymbolsArgument}";

Il2CppBuildPreProcess.SetAdditionalUnityLinkerArguments(options, () => arguments, s => resultingArguments = s);

Assert.That(resultingArguments, Is.EqualTo(expectedArgument));
}

[Test]
public void SetAdditionalUnityLinkerArguments_ArgumentAlreadyAdded_AddsArgumentOnlyOnce()
{
var options = new SentryUnityOptions { Il2CppLineNumberSupportEnabled = true };
arguments = $"--MyArgument {Il2CppBuildPreProcess.LinkSymbolsArgument}";

Il2CppBuildPreProcess.SetAdditionalUnityLinkerArguments(options, () => arguments, s => resultingArguments = s);

Assert.That(resultingArguments, Is.Empty);
}
}
Loading