diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 69bf65d00bf..cbb76d1f1cd 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -2970,19 +2970,30 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
 
 	/*
 	 * If there's a test expression, we have to evaluate it and save the value
-	 * where the CaseTestExpr placeholders can find it. We must save and
+	 * where the CaseTestExpr placeholders can find it.  We must save and
 	 * restore prior setting of econtext's caseValue fields, in case this node
-	 * is itself within a larger CASE.
+	 * is itself within a larger CASE.  Furthermore, don't assign to the
+	 * econtext fields until after returning from evaluation of the test
+	 * expression.  We used to pass &econtext->caseValue_isNull to the
+	 * recursive call, but that leads to aliasing that variable within said
+	 * call, which can (and did) produce bugs when the test expression itself
+	 * contains a CASE.
+	 *
+	 * If there's no test expression, we don't actually need to save and
+	 * restore these fields; but it's less code to just do so unconditionally.
 	 */
 	save_datum = econtext->caseValue_datum;
 	save_isNull = econtext->caseValue_isNull;
 
 	if (caseExpr->arg)
 	{
+		bool		arg_isNull;
+
 		econtext->caseValue_datum = ExecEvalExpr(caseExpr->arg,
 												 econtext,
-												 &econtext->caseValue_isNull,
+												 &arg_isNull,
 												 NULL);
+		econtext->caseValue_isNull = arg_isNull;
 	}
 
 	/*
@@ -2994,10 +3005,11 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
 	{
 		CaseWhenState *wclause = lfirst(clause);
 		Datum		clause_value;
+		bool		clause_isNull;
 
 		clause_value = ExecEvalExpr(wclause->expr,
 									econtext,
-									isNull,
+									&clause_isNull,
 									NULL);
 
 		/*
@@ -3005,7 +3017,7 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
 		 * statement is satisfied.  A NULL result from the test is not
 		 * considered true.
 		 */
-		if (DatumGetBool(clause_value) && !*isNull)
+		if (DatumGetBool(clause_value) && !clause_isNull)
 		{
 			econtext->caseValue_datum = save_datum;
 			econtext->caseValue_isNull = save_isNull;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a69af7cd7d2..4e23898ff91 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -106,6 +106,8 @@ static bool contain_volatile_functions_not_nextval_walker(Node *node, void *cont
 static bool has_parallel_hazard_walker(Node *node,
 						   has_parallel_hazard_arg *context);
 static bool contain_nonstrict_functions_walker(Node *node, void *context);
+static bool contain_context_dependent_node(Node *clause);
+static bool contain_context_dependent_node_walker(Node *node, int *flags);
 static bool contain_leaked_vars_walker(Node *node, void *context);
 static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
 static List *find_nonnullable_vars_walker(Node *node, bool top_level);
@@ -1334,6 +1336,76 @@ contain_nonstrict_functions_walker(Node *node, void *context)
 								  context);
 }
 
+/*****************************************************************************
+ *		Check clauses for context-dependent nodes
+ *****************************************************************************/
+
+/*
+ * contain_context_dependent_node
+ *	  Recursively search for context-dependent nodes within a clause.
+ *
+ * CaseTestExpr nodes must appear directly within the corresponding CaseExpr,
+ * not nested within another one, or they'll see the wrong test value.  If one
+ * appears "bare" in the arguments of a SQL function, then we can't inline the
+ * SQL function for fear of creating such a situation.
+ *
+ * CoerceToDomainValue would have the same issue if domain CHECK expressions
+ * could get inlined into larger expressions, but presently that's impossible.
+ * Still, it might be allowed in future, or other node types with similar
+ * issues might get invented.  So give this function a generic name, and set
+ * up the recursion state to allow multiple flag bits.
+ */
+static bool
+contain_context_dependent_node(Node *clause)
+{
+	int			flags = 0;
+
+	return contain_context_dependent_node_walker(clause, &flags);
+}
+
+#define CCDN_IN_CASEEXPR	0x0001		/* CaseTestExpr okay here? */
+
+static bool
+contain_context_dependent_node_walker(Node *node, int *flags)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, CaseTestExpr))
+		return !(*flags & CCDN_IN_CASEEXPR);
+	if (IsA(node, CaseExpr))
+	{
+		CaseExpr   *caseexpr = (CaseExpr *) node;
+
+		/*
+		 * If this CASE doesn't have a test expression, then it doesn't create
+		 * a context in which CaseTestExprs should appear, so just fall
+		 * through and treat it as a generic expression node.
+		 */
+		if (caseexpr->arg)
+		{
+			int			save_flags = *flags;
+			bool		res;
+
+			/*
+			 * Note: in principle, we could distinguish the various sub-parts
+			 * of a CASE construct and set the flag bit only for some of them,
+			 * since we are only expecting CaseTestExprs to appear in the
+			 * "expr" subtree of the CaseWhen nodes.  But it doesn't really
+			 * seem worth any extra code.  If there are any bare CaseTestExprs
+			 * elsewhere in the CASE, something's wrong already.
+			 */
+			*flags |= CCDN_IN_CASEEXPR;
+			res = expression_tree_walker(node,
+									   contain_context_dependent_node_walker,
+										 (void *) flags);
+			*flags = save_flags;
+			return res;
+		}
+	}
+	return expression_tree_walker(node, contain_context_dependent_node_walker,
+								  (void *) flags);
+}
+
 /*****************************************************************************
  *		  Check clauses for Vars passed to non-leakproof functions
  *****************************************************************************/
@@ -4178,6 +4250,8 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
  * doesn't work in the general case because it discards information such
  * as OUT-parameter declarations.
  *
+ * Also, context-dependent expression nodes in the argument list are trouble.
+ *
  * Returns a simplified expression if successful, or NULL if cannot
  * simplify the function.
  */
@@ -4372,6 +4446,13 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 		contain_nonstrict_functions(newexpr))
 		goto fail;
 
+	/*
+	 * If any parameter expression contains a context-dependent node, we can't
+	 * inline, for fear of putting such a node into the wrong context.
+	 */
+	if (contain_context_dependent_node((Node *) args))
+		goto fail;
+
 	/*
 	 * We may be able to do it; there are still checks on parameter usage to
 	 * make, but those are most easily done in combination with the actual
diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out
index c564eedb948..35b6476e501 100644
--- a/src/test/regress/expected/case.out
+++ b/src/test/regress/expected/case.out
@@ -296,8 +296,52 @@ SELECT * FROM CASE_TBL;
   -8 |  10.1
 (4 rows)
 
+--
+-- Nested CASE expressions
+--
+-- This test exercises a bug caused by aliasing econtext->caseValue_isNull
+-- with the isNull argument of the inner CASE's ExecEvalCase() call.  After
+-- evaluating the vol(null) expression in the inner CASE's second WHEN-clause,
+-- the isNull flag for the case test value incorrectly became true, causing
+-- the third WHEN-clause not to match.  The volatile function calls are needed
+-- to prevent constant-folding in the planner, which would hide the bug.
+CREATE FUNCTION vol(text) returns text as
+  'begin return $1; end' language plpgsql volatile;
+SELECT CASE
+  (CASE vol('bar')
+    WHEN 'foo' THEN 'it was foo!'
+    WHEN vol(null) THEN 'null input'
+    WHEN 'bar' THEN 'it was bar!' END
+  )
+  WHEN 'it was foo!' THEN 'foo recognized'
+  WHEN 'it was bar!' THEN 'bar recognized'
+  ELSE 'unrecognized' END;
+      case      
+----------------
+ bar recognized
+(1 row)
+
+-- In this case, we can't inline the SQL function without confusing things.
+CREATE DOMAIN foodomain AS text;
+CREATE FUNCTION volfoo(text) returns foodomain as
+  'begin return $1::foodomain; end' language plpgsql volatile;
+CREATE FUNCTION inline_eq(foodomain, foodomain) returns boolean as
+  'SELECT CASE $2::text WHEN $1::text THEN true ELSE false END' language sql;
+CREATE OPERATOR = (procedure = inline_eq,
+                   leftarg = foodomain, rightarg = foodomain);
+SELECT CASE volfoo('bar') WHEN 'foo'::foodomain THEN 'is foo' ELSE 'is not foo' END;
+    case    
+------------
+ is not foo
+(1 row)
+
 --
 -- Clean up
 --
 DROP TABLE CASE_TBL;
 DROP TABLE CASE2_TBL;
+DROP OPERATOR = (foodomain, foodomain);
+DROP FUNCTION inline_eq(foodomain, foodomain);
+DROP FUNCTION volfoo(text);
+DROP DOMAIN foodomain;
+DROP FUNCTION vol(text);
diff --git a/src/test/regress/sql/case.sql b/src/test/regress/sql/case.sql
index 5f41753337d..b2377e46109 100644
--- a/src/test/regress/sql/case.sql
+++ b/src/test/regress/sql/case.sql
@@ -156,9 +156,52 @@ UPDATE CASE_TBL
 
 SELECT * FROM CASE_TBL;
 
+--
+-- Nested CASE expressions
+--
+
+-- This test exercises a bug caused by aliasing econtext->caseValue_isNull
+-- with the isNull argument of the inner CASE's ExecEvalCase() call.  After
+-- evaluating the vol(null) expression in the inner CASE's second WHEN-clause,
+-- the isNull flag for the case test value incorrectly became true, causing
+-- the third WHEN-clause not to match.  The volatile function calls are needed
+-- to prevent constant-folding in the planner, which would hide the bug.
+
+CREATE FUNCTION vol(text) returns text as
+  'begin return $1; end' language plpgsql volatile;
+
+SELECT CASE
+  (CASE vol('bar')
+    WHEN 'foo' THEN 'it was foo!'
+    WHEN vol(null) THEN 'null input'
+    WHEN 'bar' THEN 'it was bar!' END
+  )
+  WHEN 'it was foo!' THEN 'foo recognized'
+  WHEN 'it was bar!' THEN 'bar recognized'
+  ELSE 'unrecognized' END;
+
+-- In this case, we can't inline the SQL function without confusing things.
+CREATE DOMAIN foodomain AS text;
+
+CREATE FUNCTION volfoo(text) returns foodomain as
+  'begin return $1::foodomain; end' language plpgsql volatile;
+
+CREATE FUNCTION inline_eq(foodomain, foodomain) returns boolean as
+  'SELECT CASE $2::text WHEN $1::text THEN true ELSE false END' language sql;
+
+CREATE OPERATOR = (procedure = inline_eq,
+                   leftarg = foodomain, rightarg = foodomain);
+
+SELECT CASE volfoo('bar') WHEN 'foo'::foodomain THEN 'is foo' ELSE 'is not foo' END;
+
 --
 -- Clean up
 --
 
 DROP TABLE CASE_TBL;
 DROP TABLE CASE2_TBL;
+DROP OPERATOR = (foodomain, foodomain);
+DROP FUNCTION inline_eq(foodomain, foodomain);
+DROP FUNCTION volfoo(text);
+DROP DOMAIN foodomain;
+DROP FUNCTION vol(text);