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
16 changes: 11 additions & 5 deletions gen/classes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,12 @@ DValue *DtoCastClass(Loc loc, DValue *val, Type *_to) {
Type *from = val->type->toBasetype();
TypeClass *fc = static_cast<TypeClass *>(from);

// Qualifier-only casts are semantic no-ops; avoid dynamic cast routing.
if (dmd::equivalent(from, to)) {
Logger::println("qualifier-only cast");
return new DImValue(_to, DtoRVal(val));
}

// copy DMD logic:
// if to isBaseOf from with offset: (to ? to + offset : null)
// else if from is C++ and to is C++: to
Expand Down Expand Up @@ -335,7 +341,7 @@ bool DtoIsObjcLinkage(Type *_to) {
DtoResolveClass(to->sym);
return to->sym->classKind == ClassKind::objc;
}

return false;
}

Expand Down Expand Up @@ -391,7 +397,7 @@ DValue *DtoDynamicCastInterface(Loc loc, DValue *val, Type *_to) {
// In this case we want to call the Objective-C runtime to first
// get a Class object from the `id`.
// Then check if class_conformsToProtocol returns true,
// if it does, then we can cast and return the casted value,
// if it does, then we can cast and return the casted value,
// otherwise return null.
if (DtoIsObjcLinkage(_to)) {
llvm::Function *getClassFunc =
Expand All @@ -400,10 +406,10 @@ DValue *DtoDynamicCastInterface(Loc loc, DValue *val, Type *_to) {
llvm::Function *kindOfProtocolFunc =
getRuntimeFunction(loc, gIR->module, "class_conformsToProtocol");

// id -> Class
// id -> Class
LLValue *obj = DtoRVal(val);
LLValue *objClass = gIR->CreateCallOrInvoke(getClassFunc, obj);

// Get prototype_t handle
LLValue *protoTy = getNullPtr();
if (auto ifhndl = _to->isClassHandle()->isInterfaceDeclaration()) {
Expand All @@ -413,7 +419,7 @@ DValue *DtoDynamicCastInterface(Loc loc, DValue *val, Type *_to) {
// Class && kindOfProtocolFunc(Class) ? id : null
LLValue *ret = gIR->ir->CreateSelect(
gIR->CreateCallOrInvoke(kindOfProtocolFunc, objClass, protoTy),
obj,
obj,
getNullPtr()
);
return new DImValue(_to, ret);
Expand Down
7 changes: 4 additions & 3 deletions gen/tocall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,14 +727,15 @@ class ImplicitArgumentsBuilder {
// class pointer
Type *thistype = gIR->func()->decl->vthis->type;
if (thistype != iface->type) {
DImValue *dthis = new DImValue(thistype, DtoLoad(DtoType(thistype),thisptrLval));
thisptrLval = DtoAllocaDump(DtoCastClass(loc, dthis, iface->type));
auto thisVal = DtoLoad(DtoType(thistype), thisptrLval);
DImValue dthis(thistype, thisVal);
thisptrLval = DtoAllocaDump(DtoCastClass(loc, &dthis, iface->type));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

looks like no change was made here, except for the dangerous-looking change from heap allocated dthis to stack allocated. Possibly use-after-scope. Revert this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Does the original heap allocated dthis cause potential leak-like lifetime?

}
}
}
args.push_back(thisptrLval);
} else if (thiscall && dfnval && dfnval->vthis) {

if (objccall && directcall) {

// ... or a Objective-c direct call argument
Expand Down
79 changes: 79 additions & 0 deletions tests/codegen/gh5114.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
module tests.codegen.gh5114;

// During interface contract context setup, a qualifier-only cast for the same
// interface symbol (e.g., `const(I)` -> `I`) must be handled as a repaint/
// bitcast and must not route through dynamic interface cast lowering.
//
// True dynamic interface casts (different interface symbols) are still valid
// and are covered below.
//
// RUN: %ldc -c %s
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nitpick: line 11 already tests compilation, so you can remove line 10

// RUN: %ldc -unittest -main -run %s

extern (D):

interface I {
void fn() const
out {
// Positive case: contract body performs a true interface -> interface cast.
auto b = cast(const(B)) this;
assert(b !is null);
assert(b.b() == 22);
};
}

interface A {
int a();
}

interface B {
int b() const;
}

interface J : I {
}

class C : I, A, B {
override void fn() const
out (; true)
{
}

override int a() {
return 11;
}

override int b() const {
return 22;
}
}

class D : J, B {
override void fn() const
out (; true)
{
}

override int b() const {
return 22;
}
}

unittest {
A a = new C();

// True dynamic cast: interface -> interface, resolved via druntime cast hook.
B b = cast(B) a;

assert(b !is null);
assert(b.b() == 22);

// Ensure the interface contract executes in extern(D) call flow.
I i = cast(I) a;
i.fn();

// Derived-interface call flow for I's contract; this may route through a
// non-same-interface contract context conversion.
J j = new D();
j.fn();
}
Loading