Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Commit

Permalink
Merge pull request #3543 from kinke/dynamic_cast
Browse files Browse the repository at this point in the history
Fix Issue 22218 - Dynamic casts across binary boundaries can easily fail
  • Loading branch information
RazvanN7 committed Aug 20, 2021
2 parents 5a77d7f + 68efbb3 commit a8df891
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 8 deletions.
22 changes: 16 additions & 6 deletions src/rt/cast_.d
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ extern (C):
nothrow:
pure:

// Needed because ClassInfo.opEquals(Object) does a dynamic cast,
// but we are trying to implement dynamic cast.
extern (D) private bool areClassInfosEqual(scope const ClassInfo a, scope const ClassInfo b) @safe
{
if (a is b)
return true;
// take care of potential duplicates across binaries
return a.name == b.name;
}

/******************************************
* Given a pointer:
* If it is an Object, return that Object.
Expand Down Expand Up @@ -80,19 +90,19 @@ void* _d_dynamic_cast(Object o, ClassInfo c)

int _d_isbaseof2(scope ClassInfo oc, scope const ClassInfo c, scope ref size_t offset) @safe
{
if (oc is c)
if (areClassInfosEqual(oc, c))
return true;

do
{
if (oc.base is c)
if (oc.base && areClassInfosEqual(oc.base, c))
return true;

// Bugzilla 2013: Use depth-first search to calculate offset
// from the derived (oc) to the base (c).
foreach (iface; oc.interfaces)
{
if (iface.classinfo is c || _d_isbaseof2(iface.classinfo, c, offset))
if (areClassInfosEqual(iface.classinfo, c) || _d_isbaseof2(iface.classinfo, c, offset))
{
offset += iface.offset;
return true;
Expand All @@ -107,17 +117,17 @@ int _d_isbaseof2(scope ClassInfo oc, scope const ClassInfo c, scope ref size_t o

int _d_isbaseof(scope ClassInfo oc, scope const ClassInfo c) @safe
{
if (oc is c)
if (areClassInfosEqual(oc, c))
return true;

do
{
if (oc.base is c)
if (oc.base && areClassInfosEqual(oc.base, c))
return true;

foreach (iface; oc.interfaces)
{
if (iface.classinfo is c || _d_isbaseof(iface.classinfo, c))
if (areClassInfosEqual(iface.classinfo, c) || _d_isbaseof(iface.classinfo, c))
return true;
}

Expand Down
11 changes: 10 additions & 1 deletion test/shared/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ LINK_SHARED:=1

include ../common.mak

TESTS:=link load linkD linkDR loadDR host finalize
TESTS:=link load linkD linkDR loadDR host finalize dynamiccast
TESTS+=link_linkdep load_linkdep link_loaddep load_loaddep load_13414

EXPORT_DYNAMIC=$(if $(findstring $(OS),linux freebsd dragonflybsd),-L--export-dynamic,)
Expand All @@ -13,9 +13,12 @@ all: $(addprefix $(ROOT)/,$(addsuffix .done,$(TESTS)))

$(ROOT)/loadDR.done $(ROOT)/host.done: RUN_ARGS:=$(DRUNTIMESO)

$(ROOT)/dynamiccast.done: CLEANUP:=rm dynamiccast_endmain dynamiccast_endbar

$(ROOT)/%.done: $(ROOT)/%
@echo Testing $*
$(QUIET)$(TIMELIMIT)$< $(RUN_ARGS)
$(CLEANUP)
@touch $@

$(ROOT)/link: $(SRC)/link.d $(ROOT)/lib.so $(DRUNTIMESO)
Expand All @@ -39,6 +42,12 @@ $(ROOT)/load $(ROOT)/finalize: $(ROOT)/%: $(SRC)/%.d $(ROOT)/lib.so $(DRUNTIMESO
$(ROOT)/load_13414: $(ROOT)/%: $(SRC)/%.d $(ROOT)/lib_13414.so $(DRUNTIMESO)
$(QUIET)$(DMD) $(DFLAGS) -of$@ $< $(LINKDL)

$(ROOT)/dynamiccast: $(SRC)/dynamiccast.d $(ROOT)/dynamiccast.so $(DRUNTIMESO)
$(QUIET)$(DMD) $(DFLAGS) -of$@ $(SRC)/dynamiccast.d $(LINKDL)

$(ROOT)/dynamiccast.so: $(SRC)/dynamiccast.d $(DRUNTIMESO)
$(QUIET)$(DMD) $(DFLAGS) -of$@ $< -version=DLL -fPIC -shared $(LINKDL)

$(ROOT)/linkD: $(SRC)/linkD.c $(ROOT)/lib.so $(DRUNTIMESO)
$(QUIET)$(CC) $(CFLAGS) -o $@ $< $(ROOT)/lib.so $(LDL) -pthread

Expand Down
98 changes: 98 additions & 0 deletions test/shared/src/dynamiccast.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
class C : Exception
{
this() { super(""); }
}

version (DLL)
{
version (Windows)
{
import core.sys.windows.dll;
mixin SimpleDllMain;
}

pragma(mangle, "foo")
export Object foo(Object o)
{
assert(cast(C) o);
return new C;
}

pragma(mangle, "bar")
export void bar(void function() f)
{
import core.stdc.stdio : fopen, fclose;
bool caught;
try
f();
catch (C e)
caught = true;
assert(caught);

// verify we've actually got to the end, because for some reason we can
// end up exiting with code 0 when throwing an exception
fclose(fopen("dynamiccast_endbar", "w"));
throw new C;
}
}
else
{
T getFunc(T)(const(char)* sym, string thisExePath)
{
import core.runtime : Runtime;

version (Windows)
{
import core.sys.windows.winbase : GetProcAddress;
return cast(T) Runtime.loadLibrary("dynamiccast.dll")
.GetProcAddress(sym);
}
else version (Posix)
{
import core.sys.posix.dlfcn : dlsym;
import core.stdc.string : strrchr;

auto name = thisExePath ~ '\0';
const pathlen = strrchr(name.ptr, '/') - name.ptr + 1;
name = name[0 .. pathlen] ~ "dynamiccast.so";
return cast(T) Runtime.loadLibrary(name)
.dlsym(sym);
}
else static assert(0);
}

version (DigitalMars) version (Win64) version = DMD_Win64;

void main(string[] args)
{
import core.stdc.stdio : fopen, fclose, remove;

remove("dynamiccast_endmain");
remove("dynamiccast_endbar");

C c = new C;

auto o = getFunc!(Object function(Object))("foo", args[0])(c);
assert(cast(C) o);

version (DMD_Win64)
{
// FIXME: apparent crash & needs more work, see https://github.com/dlang/druntime/pull/2874
fclose(fopen("dynamiccast_endbar", "w"));
}
else
{
bool caught;
try
getFunc!(void function(void function()))("bar", args[0])(
{ throw new C; });
catch (C e)
caught = true;
assert(caught);
}

// verify we've actually got to the end, because for some reason we can
// end up exiting with code 0 when throwing an exception
fclose(fopen("dynamiccast_endmain", "w"));
}
}
10 changes: 9 additions & 1 deletion test/shared/win64.mak
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ DMD=dmd
MODEL=64
DRUNTIMELIB=druntime64.lib

test: loadlibwin dllrefcount dllgc
test: loadlibwin dllrefcount dllgc dynamiccast

dllrefcount:
$(DMD) -g -m$(MODEL) -conf= -Isrc -defaultlib=$(DRUNTIMELIB) test\shared\src\dllrefcount.d
Expand All @@ -21,3 +21,11 @@ dllgc:
$(DMD) -g -m$(MODEL) -conf= -Isrc -defaultlib=$(DRUNTIMELIB) -ofloaddllgc.exe test\shared\src\dllgc.d
loaddllgc.exe
del loaddllgc.exe loaddllgc.obj dllgc.dll dllgc.obj

dynamiccast:
$(DMD) -g -m$(MODEL) -conf= -Isrc -defaultlib=$(DRUNTIMELIB) -version=DLL -shared -ofdynamiccast.dll test\shared\src\dynamiccast.d
$(DMD) -g -m$(MODEL) -conf= -Isrc -defaultlib=$(DRUNTIMELIB) -ofdynamiccast.exe test\shared\src\dynamiccast.d
dynamiccast.exe
cmd /c "if not exist dynamiccast_endbar exit 1"
cmd /c "if not exist dynamiccast_endmain exit 1"
del dynamiccast.exe dynamiccast.dll dynamiccast.obj dynamiccast_endbar dynamiccast_endmain

0 comments on commit a8df891

Please sign in to comment.