This is an archive of the discontinued LLVM Phabricator instance.

[Sema] Fix consteval function calls with value-dependent args
Needs ReviewPublic

Authored by leoetlino on Nov 14 2021, 12:20 PM.

Details

Reviewers
rsmith
Summary

If any arguments of a consteval function call are value-dependent,
the call cannot be evaluated until instantiation.

This patch fixes Sema::CheckForImmediateInvocation so we don't attempt
to evaluate consteval function calls too early (before instantiation).

This fixes things like:

consteval int f(int n) { return n; }

template <int M>
constexpr int broken() {
  return f(M);
}

Without the value-dependency checks, what happens is that the constant
expression evaluation engine is called on the following expression:

ConstantExpr 'int'
`-CallExpr 'int'
  |-ImplicitCastExpr 'int (*)(int)' <FunctionToPointerDecay>
  | `-DeclRefExpr 'int (int)' lvalue Function 'f' 'int (int)'
  `-DeclRefExpr 'int' NonTypeTemplateParm 'M' 'int'

which obviously fails when it tries to evaluate M.

Diff Detail

Event Timeline

leoetlino created this revision.Nov 14 2021, 12:20 PM
leoetlino requested review of this revision.Nov 14 2021, 12:20 PM
Herald added a project: Restricted Project. · View Herald TranscriptNov 14 2021, 12:20 PM

While this patch doesn't introduce new regressions and does fix compilation of the code snippet mentioned earlier, I'm not sure this is the correct approach, considering there are a bunch of other similar template-related consteval bugs that this patch does *not* fix:

template <typename T>
struct Foo {
    static consteval void ok(int) {}

    static consteval void bad(T) {}

    template <typename U>
    static consteval void call(T x, U fn) { fn(x); }

    static constexpr bool test() {
        ok({});
        bad({});  // error: cannot take address of consteval function 'bad' outside of an immediate invocation
        call({}, bad);  // error: cannot take address of consteval function 'bad' outside of an immediate invocation
        return true;
    }
};

The problem seems to be that the call/arguments are type dependent so nothing is added to ImmediateInvocationCandidates -- and so HandleImmediateInvocations (in SemaExpr.cpp) never removes anything from ReferenceToConsteval despite the references to the consteval functions being used for immediate invocations.

Another variant of the issue:

template <typename T>
struct Bar {
    static consteval void bad(int) {}

    static constexpr bool test() {
        bad(T::value);  // error: cannot take address of consteval function 'bad' outside of an immediate invocation
        return true;
    }
};

Not sure what the best way of fixing those issues would be -- any guidance would be greatly appreciated!

Update: The mentioned cases here are all now fixed at head (after D132031)

// 1
consteval int f(int n) { return n; }

template <int M>
constexpr int broken() {
  return f(M);
}
static_assert(broken<100>() == 100);

// 2
template <typename T>
struct Foo {
    static consteval void ok(int) {}

    static consteval void bad(T) {}

    template <typename U>
    static consteval void call(T x, U fn) { fn(x); }

    static constexpr bool test() {
        ok({});
        bad({});  // error: cannot take address of consteval function 'bad' outside of an immediate invocation
        call({}, bad);  // error: cannot take address of consteval function 'bad' outside of an immediate invocation
        return true;
    }
};

// 3
template <typename T>
struct Bar {
    static consteval void bad(int) {}

    static constexpr bool test() {
        bad(T::value);  // error: cannot take address of consteval function 'bad' outside of an immediate invocation
        return true;
    }
};
Herald added a project: Restricted Project. · View Herald TranscriptAug 18 2022, 11:18 PM