Tyker created this revision.
Tyker added a reviewer: rsmith.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Changes:

- Calls to consteval function and constructors are not evaluated as soon as 
they are reached.
- Add diagnostic for taking address of a consteval function in non-constexpr 
context.
- Add diagnostic for address of consteval function accessible at runtime.
- Add tests

Serialization and importing depends on https://reviews.llvm.org/D63640


Repository:
  rC Clang

https://reviews.llvm.org/D63960

Files:
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/include/clang/Sema/Sema.h
  clang/lib/Sema/SemaDecl.cpp
  clang/lib/Sema/SemaDeclCXX.cpp
  clang/lib/Sema/SemaExpr.cpp
  clang/lib/Sema/SemaLambda.cpp
  clang/lib/Sema/SemaOverload.cpp
  clang/test/SemaCXX/cxx2a-consteval.cpp

Index: clang/test/SemaCXX/cxx2a-consteval.cpp
===================================================================
--- clang/test/SemaCXX/cxx2a-consteval.cpp
+++ clang/test/SemaCXX/cxx2a-consteval.cpp
@@ -56,3 +56,276 @@
 consteval int main() { // expected-error {{'main' is not allowed to be declared consteval}}
   return 0;
 }
+
+int i_runtime; // expected-note+ {{declared here}}
+constexpr int i_constexpr = 0;
+
+consteval int f_eval(int i) {
+// expected-note@-1+ {{declared here}}
+  return i;
+}
+
+constexpr auto l_eval = [](int i) consteval {
+// expected-note@-1+ {{declared here}}
+  return i;
+};
+
+struct A {
+  int I = 0;
+  consteval int f_eval(int i) const {
+// expected-note@-1+ {{declared here}}
+    return I + i;
+// expected-note@-1 {{is not allowed in a constant expression}}
+  }
+};
+
+constexpr A a;
+
+namespace invalid_call {
+
+int d2 = f_eval(i_runtime);
+// expected-error@-1 {{could not be evaluated}}
+// expected-note@-2 {{is not allowed in a constant expression}}
+int l2 = l_eval(i_runtime);
+// expected-error@-1 {{could not be evaluated}}
+// expected-note@-2 {{is not allowed in a constant expression}}
+int m2 = a.f_eval(i_runtime);
+// expected-error@-1 {{could not be evaluated}}
+// expected-note@-2 {{is not allowed in a constant expression}}
+int d4 = f_eval(i_constexpr);
+int l4 = l_eval(i_constexpr);
+int m4 = a.f_eval(i_constexpr);
+
+constexpr int f1(int i) { // expected-note+ {{declared here}}
+  int d0 = f_eval(0);
+  int l0 = l_eval(0);
+  int m0 = a.f_eval(0);
+  int d2 = f_eval(i);
+  // expected-error@-1 {{could not be evaluated}}
+  // expected-error@-2 {{must be initialized}}
+  // FIXME: the error above should not appear when the initializer present but is invalid.
+  // expected-note@-4 {{is not allowed in a constant expression}}
+
+  int l2 = l_eval(i);
+// expected-error@-1 {{could not be evaluated}}
+// expected-note@-2 {{is not allowed in a constant expression}}
+  int m2 = a.f_eval(i);
+// expected-error@-1 {{could not be evaluated}}
+// expected-note@-2 {{is not allowed in a constant expression}}
+  int d6 = f_eval(i_constexpr);
+  int l6 = l_eval(i_constexpr);
+  int m6 = a.f_eval(i_constexpr);
+  int d8 = f_eval(i_runtime);
+// expected-error@-1 {{could not be evaluated}}
+// expected-note@-2 {{is not allowed in a constant expression}}
+  int l8 = l_eval(i_runtime);
+// expected-error@-1 {{could not be evaluated}}
+// expected-note@-2 {{is not allowed in a constant expression}}
+  int m8 = a.f_eval(i_runtime);
+// expected-error@-1 {{could not be evaluated}}
+// expected-note@-2 {{is not allowed in a constant expression}}
+  return 0;
+}
+
+consteval int f2(int i) {
+// expected-error@-1 {{never produces a constant expression}}
+  int d0 = f_eval(i);
+  int l0 = l_eval(i);
+  int m0 = a.f_eval(i);
+  int d1 = f_eval(i_runtime);
+// expected-note@-1 {{is not allowed in a constant expression}}
+  int l1 = l_eval(i_runtime);
+  int m1 = a.f_eval(i_runtime);
+  return 0;
+}
+
+void test() {
+  A a_dependent;
+  // expected-note@-1+ {{declared here}}
+
+  int Int = 0;
+  auto l_dependent = [Int](int i) consteval {
+    // expected-note@-1+ {{declared here}}
+    return Int + i;
+    // expected-note@-1 {{is not allowed in a constant expression}}
+  };
+
+  int i_l = l_dependent(0);
+  // expected-error@-1 {{could not be evaluated}}
+  // expected-note@-2 {{in call}}
+
+  int i_m = a_dependent.f_eval(0);
+  // expected-error@-1 {{could not be evaluated}}
+  // expected-note@-2 {{in call}}
+}
+
+}
+
+namespace taking_address {
+
+using func_type = int(int);
+using mem_ptr_type = int(A::*)(int);
+
+func_type* p1 = (&f_eval);
+// expected-error@-1 {{take address}}
+func_type* p2= &(((f_eval)));
+// expected-error@-1 {{take address}}
+func_type* p3 = (func_type*)f_eval;
+// expected-error@-1 {{take address}}
+func_type* p4 = static_cast<func_type*>(f_eval);
+// expected-error@-1 {{take address}}
+func_type* p5 = reinterpret_cast<func_type*>(f_eval);
+// expected-error@-1 {{take address}}
+func_type* p6 = reinterpret_cast<func_type*>(&reinterpret_cast<char>(f_eval));
+// expected-error@-1 {{take address}}
+func_type* p7 = __builtin_addressof(f_eval);
+// expected-error@-1 {{take address}}
+
+auto p = f_eval;
+// expected-error@-1 {{take address}}
+
+mem_ptr_type m1 = &A::f_eval;
+// expected-error@-1 {{take address}}
+auto* l1 = &decltype(l_eval)::operator();
+// expected-error@-1 {{take address}}
+
+consteval int f(int i) {
+// expected-note@-1+ {{declared here}}
+  return i;
+}
+
+auto ptr = &f;
+// expected-error@-1 {{take address}}
+
+auto f1() {
+  return &f;
+// expected-error@-1 {{take address}}
+}
+
+constexpr auto f2() {
+  return &f;
+// expected-error@-1 {{take address}}
+}
+
+consteval auto f3() {
+  return &f;
+}
+
+consteval int Overload() {
+//expected-note@-1 {{candidate function made ineligible by consteval specifier}}
+  return 0;
+}
+
+int Overload(int i) {
+//expected-note@-1 {{candidate function}}
+  return i;
+}
+
+auto Ptr = &Overload;
+int(*Ptr1)() = &Overload;
+// expected-error@-1 {{does not match}}
+int(*Ptr2)(int) = &Overload;
+
+}
+
+namespace misc {
+
+consteval int f(int i) {
+  return i;
+}
+
+using func_type = int(int);
+
+consteval func_type* f1() {
+  return &f;
+}
+
+int run(int i) {
+  return f1()(i);
+// expected-error@-1 {{contain pointer on consteval declaration}}
+}
+
+struct A {
+  mutable func_type *tmp = nullptr;
+  consteval int f1() const {
+// expected-note@-1 {{declared here}}
+    tmp = &f;
+// expected-note@-1 {{cannot modify}}
+    return 0;
+  }
+};
+
+constexpr A a;
+
+int v(int i) {
+  a.f1();
+// expected-error@-1 {{could not be evaluated}}
+// expected-note@-2 {{in call}}
+  return a.tmp(i);
+}
+
+struct B {
+  func_type *tmp = nullptr;
+  consteval B() {
+    tmp = &f;
+  }
+};
+
+int v2(int i) {
+  constexpr B b;
+// expected-error@-1 {{contain pointer on consteval declaration}}
+  return b.tmp(i);
+}
+
+}
+
+namespace invalid_function {
+using size_t = unsigned long;
+struct A {
+  consteval void *operator new(size_t count);
+  // expected-error@-1 {{operator new cannot be consteval}}
+  consteval void *operator new[](size_t count);
+  // expected-error@-1 {{operator new[] cannot be consteval}}
+  consteval void operator delete(void* ptr);
+  // expected-error@-1 {{operator delete cannot be consteval}}
+  consteval void operator delete[](void* ptr);
+  // expected-error@-1 {{operator delete[] cannot be consteval}}
+  consteval ~A();
+  // expected-error@-1 {{destructor cannot be marked consteval}}
+};
+
+}
+
+namespace Ctor {
+
+struct A {
+  int i;
+  float f;
+  consteval A(int i, float f) : i(i), f(f) {}
+  //expected-note@-1 {{declared here}}
+  consteval A(const A&) = default;
+  //expected-note@-1 {{declared here}}
+  consteval A(A&&) = default;
+  //expected-note@-1 {{declared here}}
+};
+
+void test(int i) {
+  //expected-note@-1 {{declared here}}
+  A a(i, 0);
+  //expected-error@-1 {{could not be evaluated}}
+  //expected-note@-2 {{is not allowed in a constant expression}}
+  A a1(0, 1);
+  //expected-note@-1+ {{declared here}}
+  A a3(a1);
+  //expected-error@-1 {{could not be evaluated}}
+  //expected-note@-2 {{is not allowed in a constant expression}}
+  //expected-note@-3 {{in call}}
+  A a4(static_cast<A&&>(a1));
+  //expected-error@-1 {{could not be evaluated}}
+  //expected-note@-2 {{is not allowed in a constant expression}}
+  //expected-note@-3 {{in call}}
+  constexpr A a5(0, 4);
+  A a6(a5);
+}
+
+}
\ No newline at end of file
Index: clang/lib/Sema/SemaOverload.cpp
===================================================================
--- clang/lib/Sema/SemaOverload.cpp
+++ clang/lib/Sema/SemaOverload.cpp
@@ -9592,6 +9592,18 @@
                                               bool Complain,
                                               bool InOverloadResolution,
                                               SourceLocation Loc) {
+  if (!S.isConstantEvaluated() && FD->isConsteval()) {
+    if (Complain) {
+      if (InOverloadResolution)
+        S.Diag(FD->getBeginLoc(), diag::note_addrof_ovl_consteval);
+      else {
+        S.Diag(Loc, diag::err_invalid_consteval_take_address) << FD;
+        S.Diag(FD->getBeginLoc(), diag::note_declared_at);
+      }
+    }
+    return false;
+  }
+
   if (!isFunctionAlwaysEnabled(S.Context, FD)) {
     if (Complain) {
       if (InOverloadResolution)
Index: clang/lib/Sema/SemaLambda.cpp
===================================================================
--- clang/lib/Sema/SemaLambda.cpp
+++ clang/lib/Sema/SemaLambda.cpp
@@ -1192,7 +1192,9 @@
   // Enter a new evaluation context to insulate the lambda from any
   // cleanups from the enclosing full-expression.
   PushExpressionEvaluationContext(
-      ExpressionEvaluationContext::PotentiallyEvaluated);
+      LSI->CallOperator->isConsteval()
+          ? ExpressionEvaluationContext::ConstantEvaluated
+          : ExpressionEvaluationContext::PotentiallyEvaluated);
 }
 
 void Sema::ActOnLambdaError(SourceLocation StartLoc, Scope *CurScope,
Index: clang/lib/Sema/SemaExpr.cpp
===================================================================
--- clang/lib/Sema/SemaExpr.cpp
+++ clang/lib/Sema/SemaExpr.cpp
@@ -5361,7 +5361,8 @@
 /// TODO: Handle pointer return types.
 static FunctionDecl *rewriteBuiltinFunctionDecl(Sema *Sema, ASTContext &Context,
                                                 const FunctionDecl *FDecl,
-                                                MultiExprArg ArgExprs) {
+                                                MultiExprArg ArgExprs,
+                                                bool Diagnose = true) {
 
   QualType DeclType = FDecl->getType();
   const FunctionProtoType *FT = dyn_cast<FunctionProtoType>(DeclType);
@@ -5378,7 +5379,7 @@
 
     // Convert array arguments to pointer to simplify type lookup.
     ExprResult ArgRes =
-        Sema->DefaultFunctionArrayLvalueConversion(ArgExprs[i++]);
+        Sema->DefaultFunctionArrayLvalueConversion(ArgExprs[i++], Diagnose);
     if (ArgRes.isInvalid())
       return nullptr;
     Expr *Arg = ArgRes.get();
@@ -5539,6 +5540,66 @@
   }
 }
 
+/// Traverses throught an APValue to verify there is no pointers on consteval
+/// functions.
+static bool CheckPointerOnConsteval(const APValue& Value) {
+  switch (Value.getKind()){
+    default:
+      return false;
+    case APValue::Union:
+      return CheckPointerOnConsteval(Value.getUnionValue());
+    case APValue::Struct:
+      for (unsigned I = 0; I < Value.getStructNumBases(); I++)
+        if (CheckPointerOnConsteval(Value.getStructBase(I)))
+          return true;
+      for (unsigned I = 0; I < Value.getStructNumFields(); I++)
+        if (CheckPointerOnConsteval(Value.getStructField(I)))
+          return true;
+      return false;
+    case APValue::Array:
+      for (unsigned I = 0; I < Value.getArrayInitializedElts(); I++)
+        if (CheckPointerOnConsteval(Value.getArrayInitializedElt(I)))
+          return true;
+      return false;
+    case APValue::Vector:
+      for (unsigned I = 0; I < Value.getVectorLength(); I++)
+        if (CheckPointerOnConsteval(Value.getVectorElt(I)))
+          return true;
+      return false;
+    case APValue::MemberPointer:
+      if (auto* FD = dyn_cast<FunctionDecl>(Value.getMemberPointerDecl()))
+        return FD->isConsteval();
+      return false;
+    case APValue::LValue:
+      if (Value.getLValueBase())
+        if (auto* VD = Value.getLValueBase().dyn_cast<const ValueDecl*>())
+          if (auto* FD = dyn_cast<FunctionDecl>(VD))
+            return FD->isConsteval();
+      return false;
+  }
+}
+
+ExprResult Sema::ActOnConstevalCall(FunctionDecl *FD, Expr *Call) {
+  Expr::EvalResult Result;
+  llvm::SmallVector<PartialDiagnosticAt, 8> Diags;
+  Result.Diag = &Diags;
+  Call->EvaluateAsConstantExpr(Result, Expr::EvaluateForCodeGen, Context);
+  if (!Result.Diag->empty()) {
+    Diag(Call->getBeginLoc(), diag::err_invalid_consteval_call)
+        << FD << isa<CXXConstructorDecl>(FD);
+    Diag(FD->getLocation(), diag::note_callee_decl) << FD;
+    for (auto Note : *Result.Diag)
+      Diag(Note.first, Note.second);
+    return ExprError();
+  }
+  if (CheckPointerOnConsteval(Result.Val)) {
+    Diag(Call->getBeginLoc(), diag::err_consteval_address_accessible)
+        << isa<CXXConstructorDecl>(FD);
+    return ExprError();
+  }
+  return ConstantExpr::Create(Context, Call, std::move(Result.Val));
+}
+
 ExprResult Sema::ActOnCallExpr(Scope *Scope, Expr *Fn, SourceLocation LParenLoc,
                                MultiExprArg ArgExprs, SourceLocation RParenLoc,
                                Expr *ExecConfig) {
@@ -5559,6 +5620,10 @@
     }
   }
 
+  if (CallExpr *CExpr = dyn_cast<CallExpr>(Call.get()))
+    if (auto *FD = dyn_cast_or_null<FunctionDecl>(CExpr->getCalleeDecl()))
+      if (FD && FD->isConsteval() && !isConstantEvaluated())
+        Call = ActOnConstevalCall(FD, CExpr);
   return Call;
 }
 
@@ -5676,8 +5741,8 @@
       // Rewrite the function decl for this builtin by replacing parameters
       // with no explicit address space with the address space of the arguments
       // in ArgExprs.
-      if ((FDecl =
-               rewriteBuiltinFunctionDecl(this, Context, FDecl, ArgExprs))) {
+      if ((FDecl = rewriteBuiltinFunctionDecl(this, Context, FDecl, ArgExprs,
+                                              /*Diagnose*/ false))) {
         NDecl = FDecl;
         Fn = DeclRefExpr::Create(
             Context, FDecl->getQualifierLoc(), SourceLocation(), FDecl, false,
Index: clang/lib/Sema/SemaDeclCXX.cpp
===================================================================
--- clang/lib/Sema/SemaDeclCXX.cpp
+++ clang/lib/Sema/SemaDeclCXX.cpp
@@ -1815,7 +1815,8 @@
 
   if (!Inits.count(Field)) {
     if (!Diagnosed) {
-      SemaRef.Diag(Dcl->getLocation(), diag::err_constexpr_ctor_missing_init);
+      SemaRef.Diag(Dcl->getLocation(), diag::err_constexpr_ctor_missing_init)
+          << Dcl->isConsteval();
       Diagnosed = true;
     }
     SemaRef.Diag(Field->getLocation(), diag::note_constexpr_ctor_missing_init);
@@ -2111,7 +2112,7 @@
   SmallVector<PartialDiagnosticAt, 8> Diags;
   if (!Expr::isPotentialConstantExpr(Dcl, Diags)) {
     Diag(Dcl->getLocation(), diag::ext_constexpr_function_never_constant_expr)
-      << isa<CXXConstructorDecl>(Dcl);
+        << isa<CXXConstructorDecl>(Dcl) << Dcl->isConsteval();
     for (size_t I = 0, N = Diags.size(); I != N; ++I)
       Diag(Diags[I].first, Diags[I].second);
     // Don't return false here: we allow this for compatibility in
@@ -6704,7 +6705,9 @@
     //   If a function is explicitly defaulted on its first declaration, it is
     //   implicitly considered to be constexpr if the implicit declaration
     //   would be.
-    MD->setConstexprKind(Constexpr ? CSK_constexpr : CSK_unspecified);
+    MD->setConstexprKind(
+        Constexpr ? (MD->isConsteval() ? CSK_consteval : CSK_constexpr)
+                  : CSK_unspecified);
 
     if (!Type->hasExceptionSpec()) {
       // C++2a [except.spec]p3:
@@ -13066,12 +13069,17 @@
   if (getLangOpts().CUDA && !CheckCUDACall(ConstructLoc, Constructor))
     return ExprError();
 
-  return CXXConstructExpr::Create(
-      Context, DeclInitType, ConstructLoc, Constructor, Elidable,
-      ExprArgs, HadMultipleCandidates, IsListInitialization,
-      IsStdInitListInitialization, RequiresZeroInit,
+  ExprResult CtorExpr = CXXConstructExpr::Create(
+      Context, DeclInitType, ConstructLoc, Constructor, Elidable, ExprArgs,
+      HadMultipleCandidates, IsListInitialization, IsStdInitListInitialization,
+      RequiresZeroInit,
       static_cast<CXXConstructExpr::ConstructionKind>(ConstructKind),
       ParenRange);
+
+  if (Constructor->isConsteval())
+    CtorExpr = ActOnConstevalCall(Constructor, CtorExpr.get());
+
+  return CtorExpr;
 }
 
 ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
Index: clang/lib/Sema/SemaDecl.cpp
===================================================================
--- clang/lib/Sema/SemaDecl.cpp
+++ clang/lib/Sema/SemaDecl.cpp
@@ -8640,6 +8640,22 @@
       if (isa<CXXDestructorDecl>(NewFD))
         Diag(D.getDeclSpec().getConstexprSpecLoc(), diag::err_constexpr_dtor)
             << (ConstexprKind == CSK_consteval);
+      // C++20 [dcl.constexpr]p2: A destructor, an allocation function, or a
+      // deallocation function shall not be declared with the consteval
+      // specifier.
+      if (ConstexprKind == CSK_consteval) {
+        if (CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(NewFD)) {
+          if (MD->getOverloadedOperator() == OO_New ||
+              MD->getOverloadedOperator() == OO_Array_New ||
+              MD->getOverloadedOperator() == OO_Delete ||
+              MD->getOverloadedOperator() == OO_Array_Delete) {
+            Diag(D.getDeclSpec().getConstexprSpecLoc(),
+                 diag::err_invalid_consteval_decl_kind)
+                << MD->getOverloadedOperator() - OO_New;
+            NewFD->setConstexprKind(CSK_constexpr);
+          }
+        }
+      }
     }
 
     // If __module_private__ was specified, mark the function accordingly.
@@ -13003,7 +13019,9 @@
   // Do not push if it is a lambda because one is already pushed when building
   // the lambda in ActOnStartOfLambdaDefinition().
   if (!isLambdaCallOperator(FD))
-    PushExpressionEvaluationContext(ExprEvalContexts.back().Context);
+    PushExpressionEvaluationContext(
+        FD->isConsteval() ? ExpressionEvaluationContext::ConstantEvaluated
+                          : ExprEvalContexts.back().Context);
 
   // Check for defining attributes before the check for redefinition.
   if (const auto *Attr = FD->getAttr<AliasAttr>()) {
Index: clang/include/clang/Sema/Sema.h
===================================================================
--- clang/include/clang/Sema/Sema.h
+++ clang/include/clang/Sema/Sema.h
@@ -2117,6 +2117,8 @@
   void ActOnUninitializedDecl(Decl *dcl);
   void ActOnInitializerError(Decl *Dcl);
 
+  ExprResult ActOnConstevalCall(FunctionDecl *FD, Expr *Call);
+
   void ActOnPureSpecifier(Decl *D, SourceLocation PureSpecLoc);
   void ActOnCXXForRangeDecl(Decl *D);
   StmtResult ActOnCXXForRangeIdentifier(Scope *S, SourceLocation IdentLoc,
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2310,6 +2310,15 @@
   "'constexpr' non-static member function will not be implicitly 'const' "
   "in C++14; add 'const' to avoid a change in behavior">,
   InGroup<DiagGroup<"constexpr-not-const">>;
+def err_invalid_consteval_take_address : Error<
+  "cannot take address of consteval function %0 in non-constexpr context">;
+def err_consteval_address_accessible : Error<
+  "%select{return value|constructed object}0 contain pointer on consteval declaration;"
+  " this would make it accessible at runtime">;
+def err_invalid_consteval_call : Error<
+  "call to consteval %select{function|constructor of}1 %0 could not be evaluated">;
+def err_invalid_consteval_decl_kind : Error<
+  "operator %select{new|delete|new[]|delete[]}0 cannot be consteval specified">;
 def err_invalid_constexpr : Error<
   "%select{function parameter|typedef|non-static data member}0 "
   "cannot be %select{constexpr|consteval}1">;
@@ -2395,7 +2404,7 @@
   "variables defined in a constexpr %select{function|constructor}0 must be "
   "initialized">;
 def ext_constexpr_function_never_constant_expr : ExtWarn<
-  "constexpr %select{function|constructor}0 never produces a "
+  "%select{constexpr|consteval}1 %select{function|constructor}0 never produces a "
   "constant expression">, InGroup<DiagGroup<"invalid-constexpr">>, DefaultError;
 def err_attr_cond_never_constant_expr : Error<
   "%0 attribute expression never produces a constant expression">;
@@ -2433,7 +2442,7 @@
 def err_constexpr_union_ctor_no_init : Error<
   "constexpr union constructor does not initialize any member">;
 def err_constexpr_ctor_missing_init : Error<
-  "constexpr constructor must initialize all members">;
+  "%select{constexpr|consteval}0 constructor must initialize all members">;
 def note_constexpr_ctor_missing_init : Note<
   "member not initialized by constructor">;
 def note_non_literal_no_constexpr_ctors : Note<
@@ -3731,6 +3740,8 @@
     "non-tautological enable_if conditions">;
 def note_addrof_ovl_candidate_disabled_by_enable_if_attr : Note<
     "candidate function made ineligible by enable_if">;
+def note_addrof_ovl_consteval : Note<
+    "candidate function made ineligible by consteval specifier">;
 def note_ovl_candidate_deduced_mismatch : Note<
     "candidate template ignored: deduced type "
     "%diff{$ of %select{|element of }4%ordinal0 parameter does not match "
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to