[committed] d: Add warning for call expression without side effects
Checks
Commit Message
Hi,
In the last merge of the dmd front-end with upstream (r14-4830), this
warning got removed from the semantic passes. Reimplement the warning
for the code generation pass instead, where it cannot have an effect on
conditional compilation.
Bootstrapped and regression tested on x86_64-linux-gnu/-m32, committed
to mainline.
Regards,
Iain.
---
gcc/d/ChangeLog:
* d-codegen.cc (call_side_effect_free_p): New function.
* d-tree.h (CALL_EXPR_WARN_IF_UNUSED): New macro.
(call_side_effect_free_p): New prototype.
* expr.cc (ExprVisitor::visit (CallExp *)): Set
CALL_EXPR_WARN_IF_UNUSED on matched call expressions.
(ExprVisitor::visit (NewExp *)): Don't dereference the result of an
allocation call here.
* toir.cc (add_stmt): Emit warning when call expression added to
statement list without being used.
gcc/testsuite/ChangeLog:
* gdc.dg/Wunused_value.d: New test.
---
gcc/d/d-codegen.cc | 54 ++++++++++++++++++++++++++++
gcc/d/d-tree.h | 7 ++++
gcc/d/expr.cc | 13 ++++++-
gcc/d/toir.cc | 32 +++++++++++++++++
gcc/testsuite/gdc.dg/Wunused_value.d | 29 +++++++++++++++
5 files changed, 134 insertions(+), 1 deletion(-)
create mode 100644 gcc/testsuite/gdc.dg/Wunused_value.d
@@ -2102,6 +2102,60 @@ get_function_type (Type *t)
return tf;
}
+/* Returns TRUE if calling the function FUNC, or the function or delegate type
+ TYPE is free of side effects. */
+
+bool
+call_side_effect_free_p (FuncDeclaration *func, Type *type)
+{
+ gcc_assert (func != NULL || type != NULL);
+
+ if (func != NULL)
+ {
+ /* Constructor and invariant calls can't be `pure'. */
+ if (func->isCtorDeclaration () || func->isInvariantDeclaration ())
+ return false;
+
+ /* Must be a `nothrow' function. */
+ TypeFunction *tf = func->type->toTypeFunction ();
+ if (!tf->isnothrow ())
+ return false;
+
+ /* Return type can't be `void' or `noreturn', as that implies all work is
+ done via side effects. */
+ if (tf->next->ty == TY::Tvoid || tf->next->ty == TY::Tnoreturn)
+ return false;
+
+ /* Only consider it as `pure' if it can't modify its arguments. */
+ if (func->isPure () == PURE::const_)
+ return true;
+ }
+
+ if (type != NULL)
+ {
+ TypeFunction *tf = get_function_type (type);
+
+ /* Must be a `nothrow` function type. */
+ if (tf == NULL || !tf->isnothrow ())
+ return false;
+
+ /* Return type can't be `void' or `noreturn', as that implies all work is
+ done via side effects. */
+ if (tf->next->ty == TY::Tvoid || tf->next->ty == TY::Tnoreturn)
+ return false;
+
+ /* Delegates that can modify its context can't be `pure'. */
+ if (type->isTypeDelegate () && tf->isMutable ())
+ return false;
+
+ /* Only consider it as `pure' if it can't modify its arguments. */
+ if (tf->purity == PURE::const_)
+ return true;
+ }
+
+ return false;
+}
+
/* Returns TRUE if CALLEE is a plain nested function outside the scope of
CALLER. In which case, CALLEE is being called through an alias that was
passed to CALLER. */
@@ -47,6 +47,7 @@ typedef Array <Expression *> Expressions;
/* Usage of TREE_LANG_FLAG_?:
0: METHOD_CALL_EXPR
+ 1: CALL_EXPR_WARN_IF_UNUSED (in CALL_EXPR).
Usage of TYPE_LANG_FLAG_?:
0: TYPE_SHARED
@@ -357,6 +358,11 @@ lang_tree_node
#define METHOD_CALL_EXPR(NODE) \
(TREE_LANG_FLAG_0 (NODE))
+/* True if the CALL_EXPR is free of side effects, and its return value
+ should not be discarded. */
+#define CALL_EXPR_WARN_IF_UNUSED(NODE) \
+ (TREE_LANG_FLAG_1 (CALL_EXPR_CHECK (NODE)))
+
/* True if the type was declared 'shared'. */
#define TYPE_SHARED(NODE) \
(TYPE_LANG_FLAG_0 (NODE))
@@ -594,6 +600,7 @@ extern tree build_bounds_slice_condition (SliceExp *, tree, tree, tree);
extern bool array_bounds_check (void);
extern bool checkaction_trap_p (void);
extern TypeFunction *get_function_type (Type *);
+extern bool call_side_effect_free_p (FuncDeclaration *, Type *);
extern bool call_by_alias_p (FuncDeclaration *, FuncDeclaration *);
extern tree d_build_call_expr (FuncDeclaration *, tree, Expressions *);
extern tree d_build_call (TypeFunction *, tree, tree, Expressions *);
@@ -1714,6 +1714,12 @@ public:
build the call expression. */
tree exp = d_build_call (tf, callee, object, e->arguments);
+ /* Record whether the call expression has no side effects, so we can check
+ for an unused return value later. */
+ if (TREE_CODE (exp) == CALL_EXPR && CALL_EXPR_FN (exp) != NULL_TREE
+ && call_side_effect_free_p (e->f, e->e1->type))
+ CALL_EXPR_WARN_IF_UNUSED (exp) = 1;
+
if (returnvalue != NULL_TREE)
exp = compound_expr (exp, returnvalue);
@@ -2338,7 +2344,12 @@ public:
new_call = d_save_expr (new_call);
se->type = sd->type;
se->sym = new_call;
- result = compound_expr (build_expr (se), new_call);
+
+ /* Setting `se->sym' would mean that the result of the
+ constructed struct literal expression is `*(new_call)'.
+ Strip off the indirect reference, as we don't mean to
+ compute the value yet. */
+ result = build_address (build_expr (se));
}
else
result = new_call;
@@ -215,6 +215,38 @@ add_stmt (tree t)
if (!TREE_SIDE_EFFECTS (t))
return;
+ /* If a call expression has no side effects, and there's no explicit
+ `cast(void)', then issue a warning about the unused return value. */
+ if (TREE_CODE (t) == INDIRECT_REF)
+ {
+ t = TREE_OPERAND (t, 0);
+
+ if (TREE_CODE (TREE_TYPE (t)) != REFERENCE_TYPE)
+ warning (OPT_Wunused_value, "value computed is not used");
+ }
+
+ if (TREE_CODE (t) == CALL_EXPR && CALL_EXPR_FN (t) != NULL_TREE
+ && CALL_EXPR_WARN_IF_UNUSED (t))
+ {
+ tree callee = CALL_EXPR_FN (t);
+
+ /* It's a call to a function or function pointer. */
+ if (TREE_CODE (callee) == ADDR_EXPR
+ && VAR_OR_FUNCTION_DECL_P (TREE_OPERAND (callee, 0)))
+ callee = TREE_OPERAND (callee, 0);
+
+ /* It's a call to a delegate object. */
+ if (TREE_CODE (callee) == COMPONENT_REF
+ && TREE_CODE (TREE_TYPE (TREE_OPERAND (callee, 0))) == RECORD_TYPE
+ && TYPE_DELEGATE (TREE_TYPE (TREE_OPERAND (callee, 0))))
+ callee = TREE_OPERAND (callee, 0);
+
+ warning (OPT_Wunused_value,
+ "calling %qE without side effects discards return value "
+ "of type %qT; prepend a %<cast(void)%> if intentional",
+ callee, TREE_TYPE (t));
+ }
+
if (TREE_CODE (t) == COMPOUND_EXPR)
{
/* Push out each comma expressions as separate statements. */
new file mode 100644
@@ -0,0 +1,29 @@
+// { dg-do compile }
+// { dg-options "-Wunused-value" }
+
+@safe pure nothrow T t1(T)(T x)
+{
+ return x * x;
+}
+
+nothrow pure int f1(immutable(int)[] a)
+{
+ return 0;
+}
+
+nothrow pure int f2(immutable(int)* p)
+{
+ return 0;
+}
+
+void test()
+{
+ int x = 3;
+ t1(x); // { dg-warning "without side effects discards return value" }
+
+ auto fp = &t1!int;
+ fp(x); // { dg-warning "without side effects discards return value" }
+
+ f1([]); // { dg-warning "without side effects discards return value" }
+ f2(null); // { dg-warning "without side effects discards return value" }
+}