]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
d: Add warning for call expression without side effects
authorIain Buclaw <ibuclaw@gdcproject.org>
Sat, 28 Oct 2023 07:42:15 +0000 (09:42 +0200)
committerIain Buclaw <ibuclaw@gdcproject.org>
Sat, 28 Oct 2023 07:53:36 +0000 (09:53 +0200)
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.

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
gcc/d/d-tree.h
gcc/d/expr.cc
gcc/d/toir.cc
gcc/testsuite/gdc.dg/Wunused_value.d [new file with mode: 0644]

index 155f5d0d6188f4f551440227dd15d7be4cbe7e39..91ddb1b657ebeeb5b5366dd9ff5468897fe1ad7e 100644 (file)
@@ -2102,6 +2102,60 @@ get_function_type (Type *t)
   return tf;
 }
 
+/* Returns TRUE if calling the function FUNC, or calling a function or delegate
+   object of type TYPE is be 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.  */
index 66c2f2465c864ae0f535944ae9ca097f351fed52..ed26533feb4d23a0e9131b32649acb002a556c44 100644 (file)
@@ -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 *);
index 52243e618996a5473709edd6eb147999c9ab5144..72180b100afda3657eb985a3afb043fe1b0d1706 100644 (file)
@@ -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;
index db6f71babdae8fb9c90239edd1e55260ceb00dd3..f87e094afcaa88d7ef7239d2d55a0f8b3661fb4e 100644 (file)
@@ -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.  */
diff --git a/gcc/testsuite/gdc.dg/Wunused_value.d b/gcc/testsuite/gdc.dg/Wunused_value.d
new file mode 100644 (file)
index 0000000..0afc881
--- /dev/null
@@ -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" }
+}