#!/usr/bin/env dub
/+ dub.sdl:
name "fiber_asan"
platforms "linux"
dflags "-g"
dflags "-fsanitize=address" platform="ldc"
targetPath "build"
+/
/**
* AddressSanitizer catches a fiber stack-use-after-return: a `scope` delegate
* whose closure lives in a stack frame is stored for deferred `Fiber` start,
* and the frame dies before the fiber runs — the bug shape ASan found for
* real in this repo (`feat/event-horizon` commit `c9537f96`, "storing scope
* delegates for deferred fiber start is stack-use-after-return").
*
* Two child-process demonstrations (the parent re-execs itself and asserts on
* each child's exit code and stderr report):
*
* 1. With `ASAN_OPTIONS=detect_stack_use_after_return=1` the instrumented
* frame lives on ASan's *fake stack* and is poisoned when the frame
* returns; the fiber's later call through the dead closure dies with
* `stack-use-after-return`, symbolized to the closure body's
* `file:line`, exit 1. The catch works with the stock (uninstrumented,
* no-`SupportSanitizers`) nixpkgs druntime — no fiber annotations are
* required for this particular defect because the faulting read
* happens in instrumented user code.
* 2. With `detect_stack_use_after_return=0` the same child reads garbage
* from the reused frame and exits 0 — the bug silently corrupts. The
* option gate (LDC default `-fsanitize-address-use-after-return=
* runtime`) is what makes leg 1 switchable at run time.
*
* Companion to docs/research/sanitizers/d-toolchain.md
* § "Fibers under ASan: fake stacks and stack-use-after-return".
*
* Run with: dub run --single fiber-asan.d
*
* Environment recorded: Linux 6.18.26, AMD Ryzen 9 7940HX (Zen 4), LDC 1.41.0
* (LLVM 18.1.8). ASan runtime = GCC 15.2.0 `libasan.so.8` via LDC's
* `linker-gcc.cpp` fallback (nixpkgs LDC ships no compiler-rt); druntime
* fiber stacks are `mmap`'d with a guard page
* (`core/thread/fiber/package.d`, `allocStack`).
*
* Portability: builds without instrumentation (DMD has no `-fsanitize`; the
* flag is gated `platform="ldc"`), in which case — detected at compile time
* via the `LDC_AddressSanitizer` predefined version — the probe prints a
* `SKIP:` line and exits 0 so CI stays green on any host.
*/
module (module) fiber_asanAddressSanitizer catches a fiber stack-use-after-return: a scope delegate
whose closure lives in a stack frame is stored for deferred Fiber start,
and the frame dies before the fiber runs — the bug shape ASan found for
real in this repo (feat/event-horizon commit c9537f96, "storing scope
delegates for deferred fiber start is stack-use-after-return").
Two child-process demonstrations (the parent re-execs itself and asserts on
each child's exit code and stderr report):
With ASAN_OPTIONS=detect_stack_use_after_return=1 the instrumented
frame lives on ASan's fake stack and is poisoned when the frame
returns; the fiber's later call through the dead closure dies with
stack-use-after-return, symbolized to the closure body's
file:line, exit 1. The catch works with the stock (uninstrumented,
no-SupportSanitizers) nixpkgs druntime — no fiber annotations are
required for this particular defect because the faulting read
happens in instrumented user code.
With detect_stack_use_after_return=0 the same child reads garbage
from the reused frame and exits 0 — the bug silently corrupts. The
option gate (LDC default -fsanitize-address-use-after-return=
runtime) is what makes leg 1 switchable at run time.
Companion to docs/research/sanitizers/d-toolchain.md
§ "Fibers under ASan: fake stacks and stack-use-after-return".
Run with: dub run --single fiber-asan.d
Environment recorded: Linux 6.18.26, AMD Ryzen 9 7940HX (Zen 4), LDC 1.41.0
(LLVM 18.1.8). ASan runtime = GCC 15.2.0 libasan.so.8 via LDC's
linker-gcc.cpp fallback (nixpkgs LDC ships no compiler-rt); druntime
fiber stacks are mmap'd with a guard page
(core/thread/fiber/package.d, allocStack).
Portability
builds without instrumentation (DMD has no -fsanitize; the
flag is gated platform="ldc"), in which case — detected at compile time
via the LDC_AddressSanitizer predefined version — the probe prints a
SKIP: line and exits 0 so CI stays green on any host.
fiber_asan;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) fiber_asan.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, (alias template) fiber_asan.writeln = std.stdio.writeln(T...)(T args)Equivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln;
version (LDC_AddressSanitizerLDC_AddressSanitizer)
private enum instrumented = true;
else
private enum (constant) bool fiber_asan.instrumented = falseinstrumented = false;
/// Name of the env var that arms the faulty child leg.
private enum (constant) string fiber_asan.childVar = "FIBER_ASAN_CHILD"Name of the env var that arms the faulty child leg.
childVar = "FIBER_ASAN_CHILD";
private (class) core.thread.fiber.FiberThis class provides a cooperative concurrency mechanism integrated with the
threading and garbage collection functionality. Calling a fiber may be
considered a blocking operation that returns when the fiber yields (via
Fiber.yield()). Execution occurs within the context of the calling thread
so synchronization is not necessary to guarantee memory visibility so long
as the same thread calls the fiber each time. Please note that there is no
requirement that a fiber be bound to one specific thread. Rather, fibers
may be freely passed between threads so long as they are not currently
executing. Like threads, a new fiber thread may be created using either
derivation or composition, as in the following example.
Warning
Status registers are not saved by the current implementations. This means
floating point exception status bits (overflow, divide by 0), rounding mode
and similar stuff is set per-thread, not per Fiber!
Warning
On ARM FPU registers are not saved if druntime was compiled as ARM_SoftFloat.
If such a build is used on a ARM_SoftFP system which actually has got a FPU
and other libraries are using the FPU registers (other code is compiled
as ARM_SoftFP) this can cause problems. Druntime must be compiled as
ARM_SoftFP in this case.
Fiber[] (thread local global) core.thread.fiber.Fiber[] fiber_asan.pendingpending;
import (package) corecore.(package) core.threadthread.(module) core.thread.fiberThe fiber module provides lightweight threads aka fibers.
Source
core/thread/fiber/package.d
fiber : (class) core.thread.fiber.FiberThis class provides a cooperative concurrency mechanism integrated with the
threading and garbage collection functionality. Calling a fiber may be
considered a blocking operation that returns when the fiber yields (via
Fiber.yield()). Execution occurs within the context of the calling thread
so synchronization is not necessary to guarantee memory visibility so long
as the same thread calls the fiber each time. Please note that there is no
requirement that a fiber be bound to one specific thread. Rather, fibers
may be freely passed between threads so long as they are not currently
executing. Like threads, a new fiber thread may be created using either
derivation or composition, as in the following example.
Warning
Status registers are not saved by the current implementations. This means
floating point exception status bits (overflow, divide by 0), rounding mode
and similar stuff is set per-thread, not per Fiber!
Warning
On ARM FPU registers are not saved if druntime was compiled as ARM_SoftFloat.
If such a build is used on a ARM_SoftFP system which actually has got a FPU
and other libraries are using the FPU registers (other code is compiled
as ARM_SoftFP) this can cause problems. Druntime must be compiled as
ARM_SoftFP in this case.
Fiber;
/// Stores a `scope` delegate for *deferred* start — the escape the compiler
/// trusted us not to make. With `scope`, the closure may stay in the caller's
/// stack frame instead of being GC-heap-allocated; keeping it past the
/// frame's death is the bug.
private void void fiber_asan.spawnDeferred(scope void delegate() dg) @systemStores a scope delegate for deferred start — the escape the compiler
trusted us not to make. With scope, the closure may stay in the caller's
stack frame instead of being GC-heap-allocated; keeping it past the
frame's death is the bug.
spawnDeferred(scope void delegate() (parameter) void delegate() dgdg) @system
{
(thread local global) core.thread.fiber.Fiber[] fiber_asan.pendingpending ~= new (immutable global) immutable(ulong) core.memory.pageSizeThe size of a system page in bytes.
This value is set at startup time of the application. It's safe to use
early in the start process, like in shared module constructors and
initialization of the D runtime itself.
Examples
ubyte[] buffer = new ubyte[pageSize];
Fiber((parameter) void delegate() dgdg);
}
/// The frame that dies: `local` (captured by the nested function's closure)
/// lives here, and `spawnDeferred` keeps a delegate to it.
private void void fiber_asan.scopeBody() @systemThe frame that dies: local (captured by the nested function's closure)
lives here, and spawnDeferred keeps a delegate to it.
scopeBody() @system
{
int (local variable) int locallocal = 41;
void void fiber_asan.scopeBody.child() @safechild()
{
++(local variable) int locallocal; // read+write through the dead frame once the fiber runs
void std.stdio.writefln!(char, int)(in char[] fmt, int __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("fiber sees local = %s", (local variable) int locallocal);
}
void fiber_asan.spawnDeferred(scope void delegate() dg) @systemStores a scope delegate for deferred start — the escape the compiler
trusted us not to make. With scope, the closure may stay in the caller's
stack frame instead of being GC-heap-allocated; keeping it past the
frame's death is the bug.
spawnDeferred(&void fiber_asan.scopeBody.child() @safechild);
} // scopeBody's frame is gone; pending[0] still points into it
/// Child: run the deferred fiber after the owning frame returned.
private int int fiber_asan.runChildLeg()Child
run the deferred fiber after the owning frame returned.
runChildLeg()
{
void fiber_asan.scopeBody() @systemThe frame that dies: local (captured by the nested function's closure)
lives here, and spawnDeferred keeps a delegate to it.
scopeBody();
foreach ((parameter) core.thread.fiber.Fiber ff; (thread local global) core.thread.fiber.Fiber[] fiber_asan.pendingpending)
(local variable) core.thread.fiber.Fiber ff.object.Throwable core.thread.fiber.base.FiberBase.call(core.thread.fiber.base.FiberBase.Rethrow rethrow = Rethrow.yes)Transfers execution to this fiber object. The calling context will be
suspended until the fiber calls Fiber.yield() or until it terminates
via an unhandled exception.
In
This fiber must be in state HOLD.
call(); // stack-use-after-return happens inside the fiber
void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("child leg: fiber ran to completion (bug undetected)");
return 0;
}
/// Re-execs this binary with the leg armed and `detect_stack_use_after_return`
/// set as given; returns (exitCode, combined output).
private auto std.typecons.Tuple!(int, "status", string, "output") fiber_asan.runChild(bool detectUar) @safeRe-execs this binary with the leg armed and detect_stack_use_after_return
set as given; returns (exitCode, combined output).
runChild(bool (parameter) bool detectUardetectUar)
{
import (package) stdstd.(module) std.fileUtilities for manipulating files and scanning directories. Functions
in this module handle files as a unit, e.g., read or write one file
at a time. For opening files and manipulating them via handles refer
to module std.stdio.
Category Functions General exists isDir isFile isSymlink rename thisExePath Directories chdir dirEntries getcwd mkdir mkdirRecurse rmdir rmdirRecurse tempDir Files append copy read readText remove slurp write Symlinks symlink readLink Attributes attrIsDir attrIsFile attrIsSymlink getAttributes getLinkAttributes getSize setAttributes Timestamp getTimes getTimesWin setTimes timeLastModified timeLastAccessed timeStatusChanged Other DirEntry FileException PreserveAttributes SpanMode getAvailableDiskSpace
Source
std/file.d
file : (alias) thisExePath = string std.file.thisExePath() @trustedReturns the full path of the current executable.
Returns:
The path of the executable as a string.
Throws:
$(REF1 Exception, object)
thisExePath;
import (package) stdstd.(module) std.processFunctions for starting and interacting with other processes, and for
working with the current process' execution environment.
Process handling
`spawnProcess` spawns a new `process`, optionally assigning it an
arbitrary set of standard input, output, and error streams.
The function returns immediately, leaving the child process to execute
in parallel with its parent. All other functions in this module that
spawn processes are built around spawnProcess.
`wait` makes the parent `process` wait for a child `process` to
terminate. In general one should always do this, to avoid
child processes becoming "zombies" when the parent process exits.
Scope guards are perfect for this – see the spawnProcess
documentation for examples. tryWait is similar to wait,
but does not block if the process has not yet terminated.
`pipeProcess` also spawns a child `process` which runs
in parallel with its parent. However, instead of taking
arbitrary streams, it automatically creates a set of
pipes that allow the parent to communicate with the child
through the child's standard input, output, and/or error streams.
This function corresponds roughly to C's popen function.
`execute` starts a new `process` and waits for it
to complete before returning. Additionally, it captures
the process' standard output and error streams and returns
the output of these as a string.
`spawnShell`, `pipeShell` and `executeShell` work like
spawnProcess, pipeProcess and execute, respectively,
except that they take a single command string and run it through
the current user's default command interpreter.
executeShell corresponds roughly to C's system function.
`kill` attempts to terminate a running `process`.
The following table compactly summarises the different process creation
functions and how they relate to each other:
Runs program directly
Runs shell command Low-level process creation spawnProcess spawnShell Automatic input/output redirection using pipes pipeProcess pipeShell Execute and wait for completion, collect output execute executeShell
Other functionality
`pipe` is used to create unidirectional pipes.
`environment` is an interface through which the current `process`'
environment variables can be read and manipulated.
`escapeShellCommand` and `escapeShellFileName` are useful
for constructing shell command lines in a portable way.
Source
std/process.d
Note
Most of the functionality in this module is not available on iOS, tvOS
and watchOS. The only functions available on those platforms are:
environment, thisProcessID and thisThreadID.
process : (alias) escapeShellCommand = string std.process.escapeShellCommand(const(char[])[] args...) pure @safeEscapes an argv-style argument array to be used with $(LREF spawnShell),
$(LREF pipeShell) or $(LREF executeShell).
string url = "http://dlang.org/";
executeShell(escapeShellCommand("wget", url, "-O", "dlang-index.html"));
Concatenate multiple escapeShellCommand and
$(LREF escapeShellFileName) results to use shell redirection or
piping operators.
executeShell(
escapeShellCommand("curl", "http://dlang.org/download.html") ~
"|" ~
escapeShellCommand("grep", "-o", http://\S*\.zip) ~
">" ~
escapeShellFileName("D download links.txt"));
Throws:
$(OBJECTREF Exception) if any part of the command line contains unescapable
characters (NUL on all platforms, as well as CR and LF on Windows).
escapeShellCommand, (alias) executeShell = std.typecons.Tuple!(int, "status", string, "output") std.process.executeShell(scope const(char)[] command, const(string[string]) env = cast(const(string[string]))null, std.process.Config config = Config(Flags.none, null, null), ulong maxOutput = 18446744073709551615LU, scope const(char)[] workDir = null, string shellPath = nativeShell()) @safeexecuteShell;
const (local variable) const(string[string]) envenv = [
(constant) string fiber_asan.childVar = "FIBER_ASAN_CHILD"Name of the env var that arms the faulty child leg.
childVar: "1",
// detect_leaks=0 keeps the asserted exit codes purely about the
// demonstrated defect; the UAR toggle is the experiment's variable.
"ASAN_OPTIONS": "detect_stack_use_after_return="
~ ((parameter) bool detectUardetectUar ? "1" : "0") ~ ":detect_leaks=0",
];
return std.typecons.Tuple!(int, "status", string, "output") std.process.executeShell(scope const(char)[] command, const(string[string]) env = cast(const(string[string]))null, std.process.Config config = Config(Flags.none, null, null), ulong maxOutput = 18446744073709551615LU, scope const(char)[] workDir = null, string shellPath = nativeShell()) @safeexecuteShell(string std.process.escapeShellCommand(const(char[])[] args...) pure @safeEscapes an argv-style argument array to be used with spawnShell,
pipeShell or executeShell.
string url = "http://dlang.org/";
executeShell(escapeShellCommand("wget", url, "-O", "dlang-index.html"));
Concatenate multiple escapeShellCommand and
escapeShellFileName results to use shell redirection or
piping operators.
executeShell(
escapeShellCommand("curl", "http://dlang.org/download.html") ~
"|" ~
escapeShellCommand("grep", "-o", `http://\S*\.zip`) ~
">" ~
escapeShellFileName("D download links.txt"));
escapeShellCommand(string std.file.thisExePath() @trustedReturns the full path of the current executable.
Examples
import std.path : isAbsolute;
auto path = thisExePath();
assert(path.exists);
assert(path.isAbsolute);
assert(path.isFile);
thisExePath) ~ " 2>&1", (local variable) const(string[string]) envenv);
}
version (linuxlinux) private int int fiber_asan.run()run()
{
import (package) stdstd.(package) std.algorithmalgorithm.(module) std.algorithm.searchingThis is a submodule of std.algorithm.
It contains generic searching algorithms.
Function Name Description all all!"a > 0"([1, 2, 3, 4]) returns true because all elements are positive any any!"a > 0"([1, 2, -3, -4]) returns true because at least one element is positive balancedParens balancedParens("((1 + 1) / 2)", '(', ')') returns true because the string has balanced parentheses. boyerMooreFinder find("hello world", boyerMooreFinder("or")) returns "orld" using the Boyer-Moore algorithm. canFind canFind("hello world", "or") returns true. count Counts all elements or elements matching a predicate, specific element or sub-range.
count([1, 2, 1]) returns 3,
count([1, 2, 1], 1) returns 2 and
count!"a < 0"([1, -3, 0]) returns 1. |
| countUntil | countUntil(a, b) returns the number of steps taken in a to reach b; for example, countUntil("hello!", "o") returns 4. |
| commonPrefix | commonPrefix("parakeet", "parachute") returns "para". |
| endsWith | endsWith("rocks", "ks") returns true. |
| extrema | extrema([2, 1, 3, 5, 4]) returns [1, 5]. |
| find | find("hello world", "or") returns "orld" using linear search. (For binary search refer to SortedRange.) |
| findAdjacent | findAdjacent([1, 2, 3, 3, 4]) returns the subrange starting with two equal adjacent elements, i.e. [3, 3, 4]. |
| findAmong | findAmong("abcd", "qcx") returns "cd" because 'c' is among "qcx". |
| findSkip | If a = "abcde", then findSkip(a, "x") returns false and leaves a unchanged, whereas findSkip(a, "c") advances a to "de" and returns true. |
| findSplit | findSplit("abcdefg", "de") returns a tuple of three ranges "abc", "de", and "fg". |
| findSplitAfter | findSplitAfter("abcdefg", "de") returns a tuple of two ranges "abcde" and "fg". |
| findSplitBefore | findSplitBefore("abcdefg", "de") returns a tuple of two ranges "abc" and "defg". |
| minCount | minCount([2, 1, 1, 4, 1]) returns tuple(1, 3). |
| maxCount | maxCount([2, 4, 1, 4, 1]) returns tuple(4, 2). |
| minElement | Selects the minimal element of a range. minElement([3, 4, 1, 2]) returns 1. |
| maxElement | Selects the maximal element of a range. maxElement([3, 4, 1, 2]) returns 4. |
| minIndex | Index of the minimal element of a range. minIndex([3, 4, 1, 2]) returns 2. |
| maxIndex | Index of the maximal element of a range. maxIndex([3, 4, 1, 2]) returns 1. |
| minPos | minPos([2, 3, 1, 3, 4, 1]) returns the subrange [1, 3, 4, 1], i.e., positions the range at the first occurrence of its minimal element. |
| maxPos | maxPos([2, 3, 1, 3, 4, 1]) returns the subrange [4, 1], i.e., positions the range at the first occurrence of its maximal element. |
| skipOver | Assume a = "blah". Then skipOver(a, "bi") leaves a unchanged and returns false, whereas skipOver(a, "bl") advances a to refer to "ah" and returns true. |
| startsWith | startsWith("hello, world", "hello") returns true. |
| until | Lazily iterates a range until a specific value is found. |
Source
std/algorithm/searching.d
searching : (alias template) canFind = std.algorithm.searching.canFind(alias pred = "a == b")Convenience function. Like find, but only returns whether or not the search
was successful.
For more information about pred see $(LREF find).
See_Also:
$(REF among, std,algorithm,comparison) for checking a value against multiple arguments.
canFind;
import (package) stdstd.(module) std.processFunctions for starting and interacting with other processes, and for
working with the current process' execution environment.
Process handling
`spawnProcess` spawns a new `process`, optionally assigning it an
arbitrary set of standard input, output, and error streams.
The function returns immediately, leaving the child process to execute
in parallel with its parent. All other functions in this module that
spawn processes are built around spawnProcess.
`wait` makes the parent `process` wait for a child `process` to
terminate. In general one should always do this, to avoid
child processes becoming "zombies" when the parent process exits.
Scope guards are perfect for this – see the spawnProcess
documentation for examples. tryWait is similar to wait,
but does not block if the process has not yet terminated.
`pipeProcess` also spawns a child `process` which runs
in parallel with its parent. However, instead of taking
arbitrary streams, it automatically creates a set of
pipes that allow the parent to communicate with the child
through the child's standard input, output, and/or error streams.
This function corresponds roughly to C's popen function.
`execute` starts a new `process` and waits for it
to complete before returning. Additionally, it captures
the process' standard output and error streams and returns
the output of these as a string.
`spawnShell`, `pipeShell` and `executeShell` work like
spawnProcess, pipeProcess and execute, respectively,
except that they take a single command string and run it through
the current user's default command interpreter.
executeShell corresponds roughly to C's system function.
`kill` attempts to terminate a running `process`.
The following table compactly summarises the different process creation
functions and how they relate to each other:
Runs program directly
Runs shell command Low-level process creation spawnProcess spawnShell Automatic input/output redirection using pipes pipeProcess pipeShell Execute and wait for completion, collect output execute executeShell
Other functionality
`pipe` is used to create unidirectional pipes.
`environment` is an interface through which the current `process`'
environment variables can be read and manipulated.
`escapeShellCommand` and `escapeShellFileName` are useful
for constructing shell command lines in a portable way.
Source
std/process.d
Note
Most of the functionality in this module is not available on iOS, tvOS
and watchOS. The only functions available on those platforms are:
environment, thisProcessID and thisThreadID.
process : (class) std.process.environmentManipulates environment variables using an associative-array-like
interface.
This class contains only static methods, and cannot be instantiated.
See below for examples of use.
environment;
if ((class) std.process.environmentManipulates environment variables using an associative-array-like
interface.
This class contains only static methods, and cannot be instantiated.
See below for examples of use.
environment.string std.process.environment.get(scope const(char)[] name, string defaultValue = null) @safeRetrieves the value of the environment variable with the given name,
or a default value if the variable doesn't exist.
Unlike environment.opIndex, this function never throws on Posix.
auto sh = environment.get("SHELL", "/bin/sh");
This function is also useful in checking for the existence of an
environment variable.
auto myVar = environment.get("MYVAR");
if (myVar is null)
{
// Environment variable doesn't exist.
// Note that we have to use 'is' for the comparison, since
// myVar == null is also true if the variable exists but is
// empty.
}
get((constant) string fiber_asan.childVar = "FIBER_ASAN_CHILD"Name of the env var that arms the faulty child leg.
childVar) !is null)
return int fiber_asan.runChildLeg()Child
run the deferred fiber after the owning frame returned.
runChildLeg();
static if (!instrumented)
{
void std.stdio.writeln!(string, string)(string __param_0, string __param_1) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("SKIP: built without AddressSanitizer instrumentation ",
"(DMD has no -fsanitize; LDC-only dflags are platform-gated)");
return 0;
}
else
{
const caught = runChild(true);
assert(caught.status != 0,
"expected ASan to abort the deferred-fiber child");
assert(caught.output.canFind("stack-use-after-return"),
"expected a stack-use-after-return report; got:\n" ~ caught.output);
writefln("proved: deferred fiber start through a scope delegate is " ~
"stack-use-after-return (child exit %s)", caught.status);
const silent = runChild(false);
assert(silent.status == 0,
"expected the bug to go undetected with the fake stack disabled");
assert(!silent.output.canFind("AddressSanitizer"),
"expected no ASan report with detect_stack_use_after_return=0");
writeln("proved: detect_stack_use_after_return=0 turns the same run " ~
"into silent corruption");
writeln("OK: ASan's fake stack catches the fiber " ~
"stack-use-after-return that killed event-horizon's M5 gate");
return 0;
}
}
int int D main()main()
{
version (linuxlinux)
return int fiber_asan.run()run();
else
{
writeln("SKIP: this probe records Linux behavior (platforms \"linux\")");
return 0;
}
}