Skip to content

[Batch mode] Cope with bugs that cause error suppression. #23735

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 27 commits into from
Apr 6, 2019

Conversation

davidungar
Copy link
Contributor

@davidungar davidungar commented Apr 2, 2019

In batch mode, any diagnostics located in non-primary files are suppressed, with the expectation that they will surface when the containing file is compiled as primary. Although this assumption should hold, it does not always. For example, certain inputs can interact with associated type inferencing to violate it.

This PR tracks the current primary input in the DiagnosticEngine, which passed it down to the DiagnosticConsumer. The FileSpecificDiagnosticConsumer uses this information to output the diagnostic into the appropriate primary's .dia file. It addresses rdar://49303574.

Needs apple/swift-lldb#1431

@davidungar
Copy link
Contributor Author

@swift-ci please smoke test os x platform

@davidungar davidungar requested a review from jrose-apple April 2, 2019 07:53
@davidungar
Copy link
Contributor Author

@swift-ci please smoke test os x platform

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

This can't work as is. We emit diagnostics when we first see a problem, but we might first see a problem for declaration X1 in file X because we're trying to use X1 in file Y. Putting that diagnostic in Y's .dia file isn't correct because it won't always be Y that first tries to use X1.

@davidungar
Copy link
Contributor Author

Please test with following pull request:
apple/swift-lldb#1431

@swift-ci please smoke test os x platform

@davidungar
Copy link
Contributor Author

This can't work as is. We emit diagnostics when we first see a problem, but we might first see a problem for declaration X1 in file X because we're trying to use X1 in file Y. Putting that diagnostic in Y's .dia file isn't correct because it won't always be Y that first tries to use X1.

Thanks! But I don't understand. Isn't this PR doing the same thing as in the pre-batch-mode, single-file case? That's what it seems to do in non-batch mode.

Also, what is the significance of "first"? Why is that important?

@jrose-apple jrose-apple dismissed their stale review April 3, 2019 03:09

David and I talked in person (well, over live video) and he convinced me that 1. We won't lose diagnostics this way even if they're not always ascribed to the "right" file due to other bugs; they may move from file to file but won't disappear. 2. In a particularly contrived set of dependencies we might end up with warnings that persist in incremental builds even if you fix the issue, but (a) those would be fixed by clean builds, (b) it's not clear that that contrived case can even be built today, and (c) that's still better than not seeing diagnostics that are present. 3. We already do this when we compile a single file, because that looks the same as "no batch mode" and so we don't try to filter diagnostics in any special way. So I'm taking another look.

ArrayRef<DiagnosticArgument> FormatArgs,
const DiagnosticInfo &Info) = 0;
const DiagnosticInfo &Info,
StringRef currentPrimaryInput) = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm. I feel really weird having a DiagnosticConsumer know about "primary inputs". DiagnosticConsumer is a general interface, and not everything we do has a particular primary file associated with it. Moreover, "primary files" are a Frontend concept, and DiagnosticConsumer is down in AST, so this smells like a conceptual layering violation to me. (Also, I can't remember if whole-module mode uses "primary files" at all.)

An alternate idea I had was to use a SourceLoc as the "current" token, which for now could be the start of the "current" file. I still don't really know what to call it, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your clear summary of our talk.

I like your thinking about the layering. If we pass in a SourceLoc, we could call it the diagnosticDestinationOfLastResort. Or the finalRestingPlace.

OTOH, it might seem unintuitive to use a SourceLoc as a stand-in for the .dia file. Here's another idea: we could pass in the .dia file of the current primary, but then FileSpecificDiagnosticConsumer would need another (unordered) map. What do you think? Any other ideas?

Copy link
Contributor Author

@davidungar davidungar Apr 3, 2019

Choose a reason for hiding this comment

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

Another thought, we could call the thing that's passed in to the DiagnosticEngine, by it filename or location, the diagnosticOrphanage, since it's where homeless ones go. diagnosticMotel? (Diagnostics check in but they don't check out.) "Refuge"? I hope all these suggestions will inspire you to think of something really good!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me try it with a location and see how it looks

Copy link
Contributor

Choose a reason for hiding this comment

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

I really want to keep knowledge of FileSpecificDiagnosticConsumer out of DiagnosticEngine as much as possible, mostly to preserve separation of concerns. It already feels weird to me to have it track which file is currently being processed.

fallbackBufferLocation? (I put "buffer" in there to make it clear that the exact SourceLoc isn't relevant.) activeBuffer? bufferIndirectlyResponsibleForDiagnostic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are good! I'll ponder. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another design alternative would be to not store the fallbackBufferLocation in the DiagnosticEngine, but for it to merely forward the set method to all consumers. The FileSpecificDiagnosticConsumer would be the only one to store it. How does that alternative feel to you?

Optional<FileSpecificDiagnosticConsumer::Subconsumer *> subconsumer;
auto subconsumer = findSubconsumerAndRememberItForNotes(SM, Loc, Kind);
if (subconsumer.hasValue() && !(*subconsumer)->getConsumer())
subconsumer = findSubconsumerForPrimaryCausingErrorInNonprimary(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you need to remember-for-notes in this case too, so I suggest just pushing this into your new findSubconsumerAndRememberItForNotes, or even subconsumerForLocation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, and I was under the impression that the code in this PR does that. Specifically,findSubconsumerForPrimaryCausingErrorInNonprimary returns through findSubconsumerAndRememberItForNotes. I see why it was misleading, though I don't have a better alternative.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you think it's clearer, findSubconsumerForPrimaryCausingErrorInNonprimary could return a possibly-invalid SourceLoc. And the call to findSubconsumerAndRememberItForNotes could then be moved up into handleDiagnostic.

Hmm... I have an idea for a better refactoring. Will try it out.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, whoops. I read the code and didn't take that in at all because of the name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But your reaction was good because it led to clearer code!

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

Thanks, this factoring of the consumer-finding makes more sense to me too.

}

Optional<FileSpecificDiagnosticConsumer::Subconsumer *>
FileSpecificDiagnosticConsumer::findSubconsumerForAnyKind(
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd just drop the "ForAnyKind" here. This is the general entry point for finding a consumer; the other one specifically calls out the "non-note"-ness. I guess it is edging a little close to the lower-level subconsumerForLocation, but the longer name isn't addressing that part anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure! I tried thinking of a name including "AndMakingSureThatNotesWouldBeInTheSamePlaceAsDiagnostics" but I couldn't find anything I liked.

@davidungar
Copy link
Contributor Author

Glad the new factoring works. I have a bug to track down, then I'll rerun the tests.

David Ungar added 2 commits April 3, 2019 11:01
No more vacuous subconsumers. Output into active primary or everywhere.
@davidungar
Copy link
Contributor Author

The new refactoring led to a more complete change for how we handle non-primaries. I've pushed that so you can see it. Still plan to rename defaultDiagnosticLoc to one of your suggestions.

@davidungar
Copy link
Contributor Author

Please test with following pull request:
apple/swift-lldb#1431

@swift-ci please smoke test os x platform

@davidungar
Copy link
Contributor Author

Please test with following pull request:
apple/swift-lldb#1431

@swift-ci please smoke test os x platform

@davidungar
Copy link
Contributor Author

@jrose-apple I've finished the changes we spoke of, and also they led to a redoing on the subconsumer invariants. (Consumer cannot not be null anymore.) Check it out at your convenience and let me know. (Thanks for the great name!)

Also, I'm starting to look at the testing aspect. I want to include a test before landing this.

Thanks.

@davidungar
Copy link
Contributor Author

Please test with following pull request:
apple/swift-lldb#1431

@swift-ci please smoke test os x platform

1 similar comment
@davidungar
Copy link
Contributor Author

Please test with following pull request:
apple/swift-lldb#1431

@swift-ci please smoke test os x platform

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

Looks reasonable. It needs a new test, though, even if it's a new case in unittests/ that's contrived.

StringRef FormatString,
ArrayRef<DiagnosticArgument> FormatArgs,
const DiagnosticInfo &Info,
SourceLoc bufferIndirectlyCausingDiagnostic) = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you document the parameter too? It's pretty subtle for someone writing their own DiagnosticConsumer. (It may even be worth mentioning that it's safe to ignore if your consumer doesn't do anything special with different files.)

Copy link
Contributor

Choose a reason for hiding this comment

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

You could mention setBufferIndirectlyCausingDiagnosticToInput too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea! Done, but a little verbosely.

void setBufferIndirectlyCausingDiagnosticToInput(
StringRef defaultDiagnosticInputFile);
void resetBufferIndirectlyCausingDiagnostic();
SourceLoc getDefaultDiagnostLoc() const {
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: "Diagnost".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks!! Fixing it.

/// a non-primary file, use this affordance to place it in the .dia
/// file for the primary that is currently being worked on.
void setBufferIndirectlyCausingDiagnosticToInput(
StringRef defaultDiagnosticInputFile);
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, why make the DiagnosticEngine do the string lookup? It feels to me like DiagnosticEngine API ought to traffic in SourceLocs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The alternatives that I see are:

  1. Expose the DiagnosticEngine::SourcMgr instance variable (via a get method?) so that a method in the RAII struct can use it do to the string lookup.
  2. Do the lookup in every caller of the RAII class (two, so far).
  3. Pass in a SourceManager to the RAII constructor.

Which, if any, are better than what's there now?

Copy link
Contributor

@jrose-apple jrose-apple Apr 4, 2019

Choose a reason for hiding this comment

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

I was thinking # 2 is the best because we probably don't have to do a string lookup in all cases. In particular, SourceFile::getBufferID is available in all cases, right?

(I guess we could make this interface take a buffer ID too, but I don't like that either because it doesn't have a strong type today, and it's also not a type we traffic in in many parts of the compiler. The other advantage of a SourceLoc is that we could some day use it for something more specific.)

Copy link
Contributor Author

@davidungar davidungar Apr 5, 2019

Choose a reason for hiding this comment

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

I tried a couple of alternatives. As a result, I quite dislike #2 because we end up with too many lines (for my taste) of duplicated code. Rather, prefer moving the functionality in question to the RAII object, which is sort of a go-between anyway. Also, following your suggestion, the id is obtained without using the filename. So the RAII object gets a SourceFile, translates that to a SourceLoc, and passes that in to the DiagnosticEngine. This layering feels right to me. If you think it best, the RAII definitions and declarations can be moved out of DiagnosticEngine.*. Just tell me where to put them & I'm happy to do it.

@@ -118,6 +118,11 @@ FileSpecificDiagnosticConsumer::subconsumerForLocation(SourceManager &SM,
if (loc.isInvalid())
return None;

// What if a there's a consumer for fixits but there is no fixits output path?
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: "fix-its". Also, I don't understand the point here—what makes fix-it consumers different from serializing consumers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tx for the nitpick. I've clarified the comment, hope it works better now.

@@ -1218,6 +1219,9 @@ static bool performCompileStepsPostSILGen(
SILOptions &SILOpts = Invocation.getSILOptions();
IRGenOptions &IRGenOpts = Invocation.getIRGenOptions();

BufferIndirectlyCausingDiagnosticRAII cpi(Context.Diags,
PSPs.MainInputFilenameForDebugInfo);
Copy link
Contributor

Choose a reason for hiding this comment

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

Eek. Now it's no longer "for debug info". I don't think this is safe to use because the name for debug info may not be the same as the name of the buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks--great catch! I switched it to use the MSF when it's a source file. Does that look right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tx.

// multiple times. So, create a diagnostic "eater" for those non-primary
// files.
//
// This routine gets called for \c createJSONFixItDiagnosticConsumerIfNeeded
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems overly specific. "This routine gets called in cases where…" seems fine to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure!

@@ -1218,6 +1219,11 @@ static bool performCompileStepsPostSILGen(
SILOptions &SILOpts = Invocation.getSILOptions();
IRGenOptions &IRGenOpts = Invocation.getIRGenOptions();

BufferIndirectlyCausingDiagnosticRAII ricd(
Copy link
Contributor

Choose a reason for hiding this comment

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

In case it's useful, here's the idiom for an RAII you may or may not want:

Optional<BufferIndirectlyCausingDiagnosticRAII> bicd;
if (condition)
  bicd.emplace(Context.Diags, "foo");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, that's better. I 'll use it.

@davidungar
Copy link
Contributor Author

Regarding testing, see https://github.com/apple/swift/pull/23735/files#diff-43eb268d973af29cc397746d4ed96b7a , which shows a modified test that is part of this PR. The test fails with the status quo and passes with this fix. Is that enough?

@davidungar
Copy link
Contributor Author

Please test with following pull request:
apple/swift-lldb#1431

@swift-ci please smoke test os x platform

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

Implementation looks good. :-) All remaining comments are polish.

@@ -118,6 +118,12 @@ FileSpecificDiagnosticConsumer::subconsumerForLocation(SourceManager &SM,
if (loc.isInvalid())
return None;

// What if a there's a FileSpecificDiagnosticConsumer but there are no
// subconsumers in it? (This situation obtains for the fix-its
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: "obtains" instead of "occurs".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I meant "obtains", but "occurs" is better. Fixed, tx.

: Diags(SF.getASTContext().Diags) {
auto id = SF.getBufferID();
if (!id)
return;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: This won't come up in practice because you only modify the setting using RAII, but otherwise: should it return? Or should it reset the buffer instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it was reset in an earlier draft. Hmm... This question feels like it illuminates a flaw: We don't account for nested RAII's. Rather than implement a push-down stack for the buffer, let me try just outlawing such nesting for today with an assertion. That will at least detect the occurrence of such a situation.

@@ -1,18 +0,0 @@
// Ensure that an error in a primary causes an error in the errorless primary.
Copy link
Contributor

Choose a reason for hiding this comment

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

This test is still relevant, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see, it is partly still relevant because it tests an erroneous primary. I'll modify it and put it back. Thanks for the catch! (Although we may have other tests that cover this.)

@@ -1,16 +0,0 @@
// Ensure that an error in a non-primary causes an error in the errorless primary.
Copy link
Contributor

Choose a reason for hiding this comment

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

I can't tell if this one is still relevant or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it is still needed because it checks for:

  1. The absence of "compilation stopped by errors in other files", which is a string which is not in the compiler at all anymore, AFAICT.
  2. The empty.dia file existing and being empty, but this same check is in the remaining test, serialized-diagnostics-batch-mode-nonprimary-errors-only.swift.

Summary: one predicate is meaningless today because the code is no longer in the compiler, and the other is covered by the remaining test.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess I feel like it's still a slightly different configuration even if it doesn't test any new code paths. But I'll defer to you on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. You have a point, but I think I'm confident that this test doesn't carry it's weight.

@davidungar
Copy link
Contributor Author

Please test with following pull request:
apple/swift-lldb#1431

@swift-ci please smoke test linux platform

@davidungar
Copy link
Contributor Author

Please test with following pull request:
apple/swift-lldb#1431

@swift-ci please smoke test os x platform

@davidungar
Copy link
Contributor Author

Please test with following pull request:
apple/swift-lldb#1431

@swift-ci please smoke test linux platform

@davidungar
Copy link
Contributor Author

PS: Thanks for that "Implementation looks good"!

@davidungar
Copy link
Contributor Author

Please test with following pull request:
apple/swift-lldb#1431

@swift-ci please smoke test os x platform

@davidungar davidungar merged commit cf5d81c into swiftlang:master Apr 6, 2019
davidungar pushed a commit to davidungar/swift that referenced this pull request Apr 7, 2019
[Batch mode] Cope with bugs that cause error  suppression.
davidungar pushed a commit that referenced this pull request Apr 7, 2019
…sion-fix-swift-5.1-branch

[Batch mode] Merge pull request #23735 -rdar-40167351
davidungar pushed a commit to davidungar/swift that referenced this pull request Apr 9, 2019
[Batch mode] Cope with bugs that cause error  suppression.
# Conflicts:
#	include/swift/AST/DiagnosticConsumer.h
#	lib/FrontendTool/FrontendTool.cpp
#	tools/libSwiftSyntaxParser/libSwiftSyntaxParser.cpp
@davidungar davidungar deleted the tracking-primary branch September 23, 2019 16:18
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.

2 participants