Skip to content

[TypeChecker] Don’t crash if a ExplicitCastExpr doesn’t have a cast type #59664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 29, 2022

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Jun 23, 2022

If the type of an ExplicitCastExpr is not valid, it is a null type, which causes a crash when checking if the cast type has parameterized existentials.

rdar://95629905

@ahoppen ahoppen requested review from xedin and CodaFi June 23, 2022 13:26
@ahoppen
Copy link
Member Author

ahoppen commented Jun 23, 2022

@swift-ci Please smoke test

@lucianopa-msft
Copy link

Should we add a regression test for that?

@ahoppen
Copy link
Member Author

ahoppen commented Jun 23, 2022

I don’t have a reproducing test case unfortunately. The fix is just based on a crash trace.

@AnthonyLatsis
Copy link
Collaborator

The fix is just based on a crash trace.

So the radar just included the trace and no code? Hopefully Pavel can come up with one, not too familiar with result builders myself. It could be that the absence of a type here is not benign to begin with.

@xedin
Copy link
Contributor

xedin commented Jun 23, 2022

I am out this week so I will take a look at this as soon as possible when I am back!

@@ -3229,7 +3229,7 @@ class ExprAvailabilityWalker : public ASTWalker {
maybeDiagParameterizedExistentialErasure(EE, Where);
}
if (auto *CC = dyn_cast<ExplicitCastExpr>(E)) {
if (!isa<CoerceExpr>(CC) &&
if (!isa<CoerceExpr>(CC) && CC->getCastType() &&
Copy link
Contributor

@xedin xedin Jun 24, 2022

Choose a reason for hiding this comment

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

I took a quick look at this and I might have an idea about what is going on. Based on the stacktrace -diagnoseStmtAvailability walks into an AbstractClosureExpr because ExprAvailabilityWalker always returns true from shouldWalkIntoClosure but:

  1. Result builders currently have multi-statement closure inference disabled due to some ordering issues related to one-way constraints;
  2. In code completion mode we always pass LeaveClosureBodiesUnchecked to the constraint system;

I wonder if something like this might reproduce the crash:

import SwiftUI

struct S<T> {
}

extension S : ExpressibleByIntegerLiteral {
  typealias IntegerLiteralType = Int8

  init(integerLiteral: IntegerLiteralType) {
  }
}

struct MyView : View {
  var body: some View {
    Group {
      EmptyView()

      test {
        let x =  #^COMPLETE^# as S<String>
        print(x)
      }
    }
  }

  func test(_: () -> Void) -> some View {
    return EmptyView()
  }
}

Copy link
Member Author

Choose a reason for hiding this comment

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

That example doesn’t reproduce the crash for me. AFAICT closures aren’t walked
https://github.com/apple/swift/blob/ba42be5fcf2d663a589a3f5e4efe0ffcffb101fa/lib/Sema/MiscDiagnostics.cpp#L64-L65

I very much suspect that this issue will be resolved fundamentally when we get rid of LeaveClosureBodiesUnchecked and I’m wondering if it makes sense to hunt for a reproducing test case now since the fix seems pretty straight-forward.

Copy link
Contributor

Choose a reason for hiding this comment

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

The check you pointed out would skip walking into some decls in the closures, not the closures themselves though. I think this issue needs to be addressed in a different way by adjusting the check in ExprAvailabilityWalker, otherwise it would just crash in some other place…

Copy link
Member Author

Choose a reason for hiding this comment

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

I managed to find a reproducer that’s not code completion specific. With this reproducer at hand, I would argue that the fix is correct because AFAICT it appears correct that a ExplicitCastExpr has no CastType if that type is invalid. So we need to check if the type is valid (i.e. not null) before calling hasParameterizedExistential.

Copy link
Contributor

Choose a reason for hiding this comment

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

How did an expression with incorrect cast end up getting past to a syntactic diagnostics?

Copy link
Contributor

Choose a reason for hiding this comment

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

AFAICT it appears correct that a ExplicitCastExpr has no CastType if that type is invalid.

I don't know for sure if that makes sense in this case, but some other places I saw where the type was invalid the expected was to have an ErrorType instead of a null type. But again not sure if that is the case here...

… type

If the type of an `ExplicitCastExpr` is not valid, it is a null type, which causes a crash when checking if the cast type has parameterized existentials.

rdar://95629905
@ahoppen ahoppen force-pushed the pr/cast-with-no-type branch from 5b7712c to 28a8e07 Compare June 27, 2022 15:22
@ahoppen ahoppen changed the title [TypeChecker] Don’t crash if a ExplicitCastExpr doesn’t have a cast type [TypeChecker] Propagate HadError from elements in BraceStmts Jun 28, 2022
@ahoppen ahoppen force-pushed the pr/cast-with-no-type branch from 28a8e07 to 3f82b4c Compare June 28, 2022 11:45
@ahoppen
Copy link
Member Author

ahoppen commented Jun 28, 2022

@swift-ci Please smoke test

@ahoppen
Copy link
Member Author

ahoppen commented Jun 28, 2022

@swift-ci Please SourceKit stress test

Comment on lines 6485 to 6496
struct TypecheckedBodyResult {
/// The body of the function with as many statements type checked as
/// possible.
BraceStmt *TypecheckedBody;
/// If a single statement in the type checked body has an error, this is
/// \c true.
bool HadError;
};

/// Retrieve the type-checked body of the given function, or \c nullptr if
/// there's no body available.
BraceStmt *getTypecheckedBody() const;
TypecheckedBodyResult getTypecheckedBody() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious why this change is needed, is it not sufficient to return an ErrorExpr body if we encounter an error? For some context, we eventually want SILGen to be able to handle emitting potentially invalid ASTs with an inverted compiler pipeline, so the idea was we could teach SILGen to emit an ErrorExpr as an undef and just emit bodies that way.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, because editor functionality (most notably cursor info + indexing) still wants to walk the partially type-checked brace statement to salvage as much information as possible from it. If we just return ErrorExpr (or similar here), that’s no longer possible.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm okay, makes sense. Eventually we really ought to do this at a finer grained level though, e.g have typeCheckExpression produce individual ErrorExprs or type expressions as ErrorType. It's really unfortunate to have type-checking requests return un-type-checked AST on failure.

@ahoppen ahoppen force-pushed the pr/cast-with-no-type branch from 3f82b4c to 28a8e07 Compare June 28, 2022 22:11
@ahoppen ahoppen changed the title [TypeChecker] Propagate HadError from elements in BraceStmts [TypeChecker] Don’t crash if a ExplicitCastExpr doesn’t have a cast type Jun 28, 2022
@ahoppen
Copy link
Member Author

ahoppen commented Jun 28, 2022

The stress tester found issues with the principled fix of propagating HadError from elements in BraceStmts, related to how we type check closures separately for code completion.

I decided to scope the fix down to the original nullptr check because it’s way less risky. I’ll look into the proper fix once we get rid of LeaveClosureBodyUnchecked.

@ahoppen
Copy link
Member Author

ahoppen commented Jun 28, 2022

@swift-ci Please smoke test

@ahoppen ahoppen merged commit 9736869 into swiftlang:main Jun 29, 2022
@ahoppen ahoppen deleted the pr/cast-with-no-type branch June 29, 2022 07:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants