Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
18 changes: 18 additions & 0 deletions Content.Tests/DMProject/Tests/Builtins/caller.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#define COMPARE(a, b) if(a != b) {CRASH("Assertion failed: expected [b] got [a]")}

/datum/meep/proc/bar()
COMPARE("[caller.caller.caller.name]", nameof(/proc/RunTest))

/datum/proc/foo()
var/datum/meep/M = new
COMPARE("[caller.name]", nameof(/proc/ihateithere))
M.bar()

/proc/ihateithere()
var/datum/D = new
COMPARE("[caller.name]", nameof(/proc/RunTest))
D.foo()

/proc/RunTest()
// RunTest()'s caller is null due to how unit tests are invoked
ihateithere()
2 changes: 1 addition & 1 deletion DMCompiler/DMStandard/Types/Callee.dm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/callee
var/proc
var/args
var/caller as /callee|null
var/callee/caller as /callee|null

var/name as text|null
var/desc as text|null
Expand Down
17 changes: 17 additions & 0 deletions OpenDreamRuntime/DreamThread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using DMCompiler.DM;
using JetBrains.Annotations;
using OpenDreamRuntime.Objects;
using OpenDreamRuntime.Objects.Types;
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
using OpenDreamRuntime.Procs;
using OpenDreamRuntime.Procs.DebugAdapter;
using OpenDreamShared.Dream;
Expand Down Expand Up @@ -156,6 +157,12 @@ public abstract class ProcState : IDisposable {
public int ArgumentCount;
public abstract DreamProc? Proc { get; }

[Access(typeof(DreamThread), Other = AccessPermissions.Read)]
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
public int Depth;

protected DreamObjectCallee? Callee { get; set; }
protected DreamObjectCallee? Caller { get; set; }

protected void Initialize(DreamThread thread, bool waitFor) {
Thread = thread;
WaitFor = waitFor;
Expand Down Expand Up @@ -205,6 +212,7 @@ public sealed class DreamThread(string name) {

private ProcState? _current;
private readonly Stack<ProcState> _stack = new();
public int StackDepth => _current is null ? 0 : (_stack.Count + 1); // including _current

// The amount of stack frames containing `WaitFor = false`
private int _syncCount;
Expand Down Expand Up @@ -394,6 +402,7 @@ public void PushProcState(ProcState state) {
}

_current = state;
_current.Depth = StackDepth;
}

public void PopProcState(bool dispose = true) {
Expand All @@ -418,6 +427,14 @@ public void PopProcState(bool dispose = true) {
}
}

/// <remarks><c>index == 0</c> will return the current proc. Greater values returns the callers.</remarks>
/// <returns>ProcState in the call stack offset by index, or null if the index held nothing.</returns>
public ProcState? PeekStack(int index) {
if (StackDepth == 0) return null;
if (index == 0) return _current;
return index == 1 ? _stack.Peek() : _stack.ElementAtOrDefault(index - 1);
}

// Used by implementations of DreamProc::InternalContinue to defer execution to be resumed later.
// This function may mutate `ProcState.Thread` on any of the states within this DreamThread's call stack
public ProcStatus HandleDefer() {
Expand Down
59 changes: 50 additions & 9 deletions OpenDreamRuntime/Objects/Types/DreamObjectCallee.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,59 @@
namespace OpenDreamRuntime.Objects.Types;

public sealed class DreamObjectCallee(DreamObjectDefinition objectDefinition) : DreamObject(objectDefinition) {
public DMProcState? ProcState;
public ProcState? ProcState;
public long ProcStateId; // Used to ensure the proc state hasn't been reused for another proc
private DreamObjectCallee? _caller; // cached, we'll build the call stack only if we actually need to

public static DreamObjectCallee FromDMProcState(DMProcState procState) {
var proc = procState.Proc;
var callee = proc.ObjectTree.CreateObject<DreamObjectCallee>(proc.ObjectTree.Callee);
callee.ProcState = procState;
callee.ProcStateId = procState.Id;
return callee;
}

protected override void HandleDeletion() {
_caller?.DecRef();
_caller = null;
base.HandleDeletion();
}

protected override bool TryGetVar(string varName, out DreamValue value) {
// TODO: This ProcState check doesn't match byond behavior?
if (ProcState == null || ProcState.Id != ProcStateId)
throw new Exception("This callee has expired");

switch (varName) {
case "proc":
value = new(ProcState.Proc);
value = ProcState.Proc != null ? new(ProcState.Proc) : DreamValue.Null;
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return true;
case "args":
value = new(new ProcArgsList(ObjectTree.List.ObjectDefinition, ProcState));
return true;
case "caller":
// TODO
value = DreamValue.Null;
if(_caller is null)
SetCaller();
value = new DreamValue(_caller);
return true;
case "name":
value = new(ProcState.Proc.VerbName);
value = ProcState.Proc?.VerbName != null ? new(ProcState.Proc.VerbName) : DreamValue.Null;
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return true;
case "desc":
value = ProcState.Proc.VerbDesc != null ? new(ProcState.Proc.VerbDesc) : DreamValue.Null;
value = ProcState.Proc?.VerbDesc != null ? new(ProcState.Proc.VerbDesc) : DreamValue.Null;
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return true;
case "category":
value = ProcState.Proc.VerbCategory != null ? new(ProcState.Proc.VerbCategory) : DreamValue.Null;
value = ProcState.Proc?.VerbCategory != null ? new(ProcState.Proc.VerbCategory) : DreamValue.Null;
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return true;
case "file":
value = new(ProcState.Proc.GetSourceAtOffset(0).Source);
value = ProcState.Proc is DMProc procFile
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
? new DreamValue(procFile.GetSourceAtOffset(0).Source)
: DreamValue.Null;
return true;
case "line":
value = new(ProcState.Proc.GetSourceAtOffset(0).Line);
value = ProcState.Proc is DMProc procLine
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
? new DreamValue(procLine.GetSourceAtOffset(0).Line)
: DreamValue.Null;
return true;
case "src":
ProcState.Instance?.IncRef();
Expand All @@ -56,6 +77,26 @@
}
}

/// <summary>
/// Sets <see cref="_caller"/> if it hasn't been already and <see cref="ProcState"/> is not null
/// </summary>
private void SetCaller() {
if (ProcState is null || _caller is not null) return;
int ourIndex = ProcState.Thread.StackDepth - ProcState.Depth;
ProcState? callerProcState = ProcState.Thread.PeekStack(ourIndex + 1);
if(callerProcState is not DMProcState dmProcState) { // avert your eyes
if(callerProcState is not InitDreamObjectState) return;
callerProcState = ProcState.Thread.PeekStack(ourIndex + 2);
if(callerProcState is not DMProcState realDmProcState) return;
dmProcState = realDmProcState;
}


_caller = FromDMProcState(dmProcState);

Check warning

Code scanning / InspectCode

Incorrect blank lines: Blank lines are redundant elsewhere Warning

Blank lines are redundant, expected maximum 1 instead of 2
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
_caller.IncRef();
_caller.SetCaller();
}

protected override void SetVar(string varName, DreamValue value) {
throw new Exception($"Cannot set var {varName} on /callee");
}
Expand Down
28 changes: 20 additions & 8 deletions OpenDreamRuntime/Procs/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -932,16 +932,28 @@
DreamManager.WorldInstance.IncRef();
return new(DreamManager.WorldInstance);
case DMReference.Type.Callee: {
// TODO: BYOND seems to reuse the same object. At least, callee == callee
var callee = Proc.ObjectTree.CreateObject<DreamObjectCallee>(Proc.ObjectTree.Callee);

callee.ProcState = this;
callee.ProcStateId = Id;
return new(callee);
// BYOND seems to reuse the same object. At least, callee == callee
Callee ??= DreamObjectCallee.FromDMProcState(this);
Callee.IncRef();
return new(Callee);
}
case DMReference.Type.Caller: {
// TODO
return DreamValue.Null;
// Note that the ref says that caller still returns a "/callee" object, just with the caller's info
if(Caller is null) {
var peekState = Thread.PeekStack(1);
if(peekState is not DMProcState dmProcState)

Check warning

Code scanning / InspectCode

Assignment is not used Warning

Value assigned is not used in any execution path
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
// THIS IS A HACK!!!!! But it works
if(peekState is not InitDreamObjectState)
return DreamValue.Null;
if(Thread.PeekStack(2) is not DMProcState realDmProcState)

Check warning

Code scanning / InspectCode

Incorrect indent: Around child statement Warning

Line indent is not restored to the previous level around child statement
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return DreamValue.Null;
dmProcState = realDmProcState;

Caller = DreamObjectCallee.FromDMProcState(dmProcState);
}

Caller.IncRef();
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return new(Caller);
}
case DMReference.Type.Field: {
var owner = peek ? Peek() : Pop();
Expand Down
Loading