This is an archive of the discontinued LLVM Phabricator instance.

[ASTImporter] Fix corrupted RecordLayout introduced by circular referenced fields
ClosedPublic

Authored by danix800 on Jul 24 2023, 9:04 PM.

Details

Summary

UnaryOperator(&)'s creation might need layout of some records
whose fields importation are still on fly, the layout is incorrectly
computed and cached. Clients relying on this will not work properly
or crash direclty (e.g StaticAnalyzer's MemRegion.cpp (calculateOffset)).

Use UnaryOperator::CreateEmpty() instead of UnaryOperator::Create()
to avoid this computation.

https://github.com/llvm/llvm-project/issues/64170

Diff Detail

Event Timeline

danix800 created this revision.Jul 24 2023, 9:04 PM
Herald added a reviewer: shafik. · View Herald Transcript
Herald added a project: Restricted Project. · View Herald Transcript
Herald added a subscriber: martong. · View Herald Transcript
danix800 requested review of this revision.Jul 24 2023, 9:04 PM
danix800 edited the summary of this revision. (Show Details)Jul 24 2023, 9:17 PM
danix800 updated this revision to Diff 543834.Jul 24 2023, 11:30 PM

Restore in-class initializer importing when minimal importing.

danix800 retitled this revision from [ASTImporter] Fix two cases on fields circular refs to [ASTImporter] Fix corrupted RecordLayout introduced by circular referenced fields.Jul 25 2023, 6:06 PM
danix800 edited the summary of this revision. (Show Details)
danix800 added a reviewer: steakhal.
danix800 updated this revision to Diff 544168.Jul 25 2023, 6:10 PM

Remove case #1 (fixed by https://reviews.llvm.org/D155574 from @balazske which is better).

danix800 edited the summary of this revision. (Show Details)Jul 27 2023, 11:43 AM
shafik added inline comments.Jul 27 2023, 9:12 PM
clang/lib/AST/ASTImporter.cpp
7403

I don't see the following values from the old code used: E->getValueKind(), E->getObjectKind() and E->getFPOptionsOverride()

danix800 added inline comments.Jul 27 2023, 9:32 PM
clang/lib/AST/ASTImporter.cpp
7403

These three values are set for all Exprs in Import(Stmt *FromS):

Expected<Stmt *> ASTImporter::Import(Stmt *FromS) {
  // ...
  if (auto *ToE = dyn_cast<Expr>(*ToSOrErr)) {
    auto *FromE = cast<Expr>(FromS);
    // Copy ExprBitfields, which may not be handled in Expr subclasses
    // constructors.
    ToE->setValueKind(FromE->getValueKind());
    ToE->setObjectKind(FromE->getObjectKind());
    ToE->setDependence(FromE->getDependence());
  }
  // ...
}
danix800 added inline comments.Jul 27 2023, 9:42 PM
clang/lib/AST/ASTImporter.cpp
7403

Oh only the first two are set, FPOptionsOverride is missing! I'll fix that.

danix800 updated this revision to Diff 545016.Jul 27 2023, 10:08 PM

Add missing field value setting (FPOptionsOverride).

balazske added inline comments.Jul 28 2023, 5:51 AM
clang/lib/AST/ASTImporter.cpp
7418

Why is it better to use CreateEmpty instead of the old code? Does Create do something that does not work at this situation (probably getting the layout)? If yes the same should be done later at some point, can you explain how this works?

danix800 added inline comments.Jul 28 2023, 7:57 AM
clang/lib/AST/ASTImporter.cpp
7418

CreateEmpty is used to avoid unnecessarily (re-)computeDependence() of the imported UnaryOperator. See UnaryOperator's ctors for details.

While importing fields there could be deps on current record's layout. In the following testcase, importing A::b
will take this path: A::b => B => ... => B::f => ... => UnaryOperator(&).

class B;
class A {
  B* b;
  int c;
};
class B {
  A *f() { return &((B *)0)->a; }
  A a;
};

The (non-empty) UnaryOperator (&) ctor needs the whole RecordLayout (including A) to be evaluated for
computeDependence(), but none of fields of`A` is imported at this moment. The RecordLayout is incorrectly
computed and what's worse more is, the RecordLayout is cached for later use. Any clients relying on this RecordLayout
would be broken. For example EXPECT_TRUE(ToLayout.getFieldOffset(0) == 0); would simply crash with

ASTTests: /home/xxxxx/Sources/llvm-project-main/clang/include/clang/AST/ASTVector.h:116: clang::ASTVector::const_reference clang::ASTVector<unsigned long>::operator[](unsigned int) const [T = unsigned long]: Assertion `Begin + idx < End' failed.
...
#10 0x0000558b01b39942 clang::ASTVector<unsigned long>::operator[](unsigned int) const /home/xxxxx/Sources/llvm-project-main/clang/include/clang/AST/ASTVector.h:0:5
#11 0x0000558b01aaeb0f clang::ASTRecordLayout::getFieldOffset(unsigned int) const /home/xxxxx/Sources/llvm-project-main/clang/include/clang/AST/RecordLayout.h:201:12
#12 0x0000558b01a6d115 clang::ast_matchers::ASTImporterOptionSpecificTestBase_ImportCirularRefFieldsWithoutCorruptedRecordLayoutCacheTest_Test::TestBody() /home/xxxxx/Sources/llvm-project-main/clang/unittests/AST/ASTImporterTest.cpp:8067:3
...

UnaryOperator is just one example that could introduce this kind of problem. If I understand it correctly, ASTImporter is much like ASTReader which could use CreateEmpty directly to avoid recomputing some of the
data, most of which could be copied from From context, as ASTReader reads node data when deserialization.

Maybe ASTImporter could be ideally refactored into this behaviour on node creation.

The fix looks good, only a person with AST competence should have a look at it because a change in Expr.h.

clang/unittests/AST/ASTImporterTest.cpp
8033

A small thing, ImportCirularRefFieldsWithoutCorruptedRecordLayoutCache (no Test ending) is the usual naming for these tests.

aaron.ballman added inline comments.Aug 1 2023, 8:34 AM
clang/include/clang/AST/Expr.h
2343–2344

Rather than make this a public API, would it instead make sense to add friend class ASTNodeImporter; at the end of the class?

danix800 added inline comments.Aug 1 2023, 9:53 AM
clang/unittests/AST/ASTImporterTest.cpp
8033

Thanks for reminding, I'll fix all testcases ending with Test.

danix800 added inline comments.Aug 1 2023, 10:18 AM
clang/include/clang/AST/Expr.h
2343–2344

Tested & confirmed, it's unnecessary to expose this method, friend is much better. Thanks!

danix800 updated this revision to Diff 546127.Aug 1 2023, 10:35 AM
  1. Update ReleaseNotes.rst;
  2. Fix testcase names (remove Test suffix);
  3. Use friend instead of exposing API.

Have you considered back porting this to clang-17?

Have you considered back porting this to clang-17?

How to backport? I'm not familiar with this. Is it plain cherry-pick to specific branch?

aaron.ballman accepted this revision.Aug 1 2023, 12:22 PM

LGTM!

Have you considered back porting this to clang-17?

How to backport? I'm not familiar with this. Is it plain cherry-pick to specific branch?

We have some documented instructions here: https://llvm.org/docs/GitHub.html#backporting-fixes-to-the-release-branches -- I don't oppose adding this to the release branch (it seems simple and straightforward enough), but if it's not fixing a regression or other critical issue, it might make sense to leave it out of 17.x so there's less moving parts during the release.

This revision is now accepted and ready to land.Aug 1 2023, 12:22 PM

LGTM!

Have you considered back porting this to clang-17?

How to backport? I'm not familiar with this. Is it plain cherry-pick to specific branch?

We have some documented instructions here: https://llvm.org/docs/GitHub.html#backporting-fixes-to-the-release-branches -- I don't oppose adding this to the release branch (it seems simple and straightforward enough), but if it's not fixing a regression or other critical issue, it might make sense to leave it out of 17.x so there's less moving parts during the release.

I'll leave it out of 17.x.