Fix null ptr dereference in REDUCE_* cpu implementation

When asked to reduce across all dimensions, reduce would produce a
zero-sized tensor without dimensions and cause segmentation fault in the
implementation.

The change fixes the bug by making the op output a tensor of size [1] in
this case.

Also, updated the bug to clarify this behaviour.

Bug: 155508675
Test: NNTest_static
Change-Id: Ie98d8fa2e508255fd50f6bd8184dc323ba90fac8
Merged-In: Ie98d8fa2e508255fd50f6bd8184dc323ba90fac8
(cherry picked from commit 62160e554e7abf4a29be003dd5938c65d70f649a)
diff --git a/common/operations/Reduce.cpp b/common/operations/Reduce.cpp
index d11b762..220a4dc 100644
--- a/common/operations/Reduce.cpp
+++ b/common/operations/Reduce.cpp
@@ -155,6 +155,11 @@
         }
     }
 
+    // Handle the case when all dimensions are removed
+    if (outputShape.dimensions.empty()) {
+        outputShape.dimensions.push_back(1);
+    }
+
     return context->setOutputShape(kOutputTensor, outputShape);
 }
 
diff --git a/runtime/include/NeuralNetworks.h b/runtime/include/NeuralNetworks.h
index 8911eaf..82c6683 100644
--- a/runtime/include/NeuralNetworks.h
+++ b/runtime/include/NeuralNetworks.h
@@ -4381,6 +4381,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandCode} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
      *
      * Available since API level 29.
      */
@@ -4408,6 +4410,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandCode} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
      *
      * Available since API level 29.
      */
@@ -4438,6 +4442,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandCode} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
      *      For a {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} and
      *      {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED} tensor,
      *      the scale and zeroPoint must be the same as input0.
@@ -4471,6 +4477,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandCode} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
      *      For a {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} and
      *      {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED} tensor,
      *      the scale and zeroPoint must be the same as input0.
@@ -4501,6 +4509,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandCode} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
      *
      * Available since API level 29.
      */
@@ -4528,6 +4538,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link OperandCode} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
      *
      * Available since API level 29.
      */
diff --git a/runtime/test/generated/spec_V1_2/reduce_all_b155508675.example.cpp b/runtime/test/generated/spec_V1_2/reduce_all_b155508675.example.cpp
new file mode 100644
index 0000000..10be3a0
--- /dev/null
+++ b/runtime/test/generated/spec_V1_2/reduce_all_b155508675.example.cpp
@@ -0,0 +1,73 @@
+// Generated from reduce_all_b155508675.mod.py
+// DO NOT EDIT
+// clang-format off
+#include "TestHarness.h"
+using namespace test_helper;
+
+namespace generated_tests::reduce_all_b155508675 {
+
+const TestModel& get_test_model() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {0},
+                .operands = {{ // op1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_BOOL8,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0, 1, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_INT32,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::BOOL,
+                            .zeroPoint = 0
+                        }, { // op2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_BOOL8,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {0, 1, 2},
+                            .outputs = {3},
+                            .type = TestOperationType::REDUCE_ALL
+                        }},
+                .outputIndexes = {3}
+            },
+        .minSupportedVersion = TestHalVersion::V1_2,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model = TestModelManager::get().add("reduce_all_b155508675", get_test_model());
+
+}  // namespace generated_tests::reduce_all_b155508675
+
diff --git a/runtime/test/generated/spec_V1_2/reduce_any_b155508675.example.cpp b/runtime/test/generated/spec_V1_2/reduce_any_b155508675.example.cpp
new file mode 100644
index 0000000..603bd51
--- /dev/null
+++ b/runtime/test/generated/spec_V1_2/reduce_any_b155508675.example.cpp
@@ -0,0 +1,73 @@
+// Generated from reduce_any_b155508675.mod.py
+// DO NOT EDIT
+// clang-format off
+#include "TestHarness.h"
+using namespace test_helper;
+
+namespace generated_tests::reduce_any_b155508675 {
+
+const TestModel& get_test_model() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {0},
+                .operands = {{ // op1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_BOOL8,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0, 1, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_INT32,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::BOOL,
+                            .zeroPoint = 0
+                        }, { // op2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_BOOL8,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {0, 1, 2},
+                            .outputs = {3},
+                            .type = TestOperationType::REDUCE_ANY
+                        }},
+                .outputIndexes = {3}
+            },
+        .minSupportedVersion = TestHalVersion::V1_2,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model = TestModelManager::get().add("reduce_any_b155508675", get_test_model());
+
+}  // namespace generated_tests::reduce_any_b155508675
+
diff --git a/runtime/test/generated/spec_V1_2/reduce_max_b155508675.example.cpp b/runtime/test/generated/spec_V1_2/reduce_max_b155508675.example.cpp
new file mode 100644
index 0000000..17af912
--- /dev/null
+++ b/runtime/test/generated/spec_V1_2/reduce_max_b155508675.example.cpp
@@ -0,0 +1,174 @@
+// Generated from reduce_max_b155508675.mod.py
+// DO NOT EDIT
+// clang-format off
+#include "TestHarness.h"
+using namespace test_helper;
+
+namespace generated_tests::reduce_max_b155508675 {
+
+const TestModel& get_test_model() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {0},
+                .operands = {{ // op1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0, 1, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_INT32,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::BOOL,
+                            .zeroPoint = 0
+                        }, { // op2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {0, 1, 2},
+                            .outputs = {3},
+                            .type = TestOperationType::REDUCE_MAX
+                        }},
+                .outputIndexes = {3}
+            },
+        .minSupportedVersion = TestHalVersion::V1_2,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model = TestModelManager::get().add("reduce_max_b155508675", get_test_model());
+
+}  // namespace generated_tests::reduce_max_b155508675
+
+namespace generated_tests::reduce_max_b155508675 {
+
+const TestModel& get_test_model_all_inputs_as_internal() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {4},
+                .operands = {{ // op1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::TEMPORARY_VARIABLE,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0, 1, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_INT32,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::BOOL,
+                            .zeroPoint = 0
+                        }, { // op2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // op1_new
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // dummy
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({0.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::INT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {4, 5, 6},
+                            .outputs = {0},
+                            .type = TestOperationType::ADD
+                        }, {
+                            .inputs = {0, 1, 2},
+                            .outputs = {3},
+                            .type = TestOperationType::REDUCE_MAX
+                        }},
+                .outputIndexes = {3}
+            },
+        .minSupportedVersion = TestHalVersion::V1_2,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_all_inputs_as_internal = TestModelManager::get().add("reduce_max_b155508675_all_inputs_as_internal", get_test_model_all_inputs_as_internal());
+
+}  // namespace generated_tests::reduce_max_b155508675
+
diff --git a/runtime/test/generated/spec_V1_2/reduce_min_b155508675.example.cpp b/runtime/test/generated/spec_V1_2/reduce_min_b155508675.example.cpp
new file mode 100644
index 0000000..170554e
--- /dev/null
+++ b/runtime/test/generated/spec_V1_2/reduce_min_b155508675.example.cpp
@@ -0,0 +1,174 @@
+// Generated from reduce_min_b155508675.mod.py
+// DO NOT EDIT
+// clang-format off
+#include "TestHarness.h"
+using namespace test_helper;
+
+namespace generated_tests::reduce_min_b155508675 {
+
+const TestModel& get_test_model() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {0},
+                .operands = {{ // op1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0, 1, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_INT32,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::BOOL,
+                            .zeroPoint = 0
+                        }, { // op2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {0, 1, 2},
+                            .outputs = {3},
+                            .type = TestOperationType::REDUCE_MIN
+                        }},
+                .outputIndexes = {3}
+            },
+        .minSupportedVersion = TestHalVersion::V1_2,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model = TestModelManager::get().add("reduce_min_b155508675", get_test_model());
+
+}  // namespace generated_tests::reduce_min_b155508675
+
+namespace generated_tests::reduce_min_b155508675 {
+
+const TestModel& get_test_model_all_inputs_as_internal() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {4},
+                .operands = {{ // op1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::TEMPORARY_VARIABLE,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0, 1, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_INT32,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::BOOL,
+                            .zeroPoint = 0
+                        }, { // op2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // op1_new
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // dummy
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({0.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::INT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {4, 5, 6},
+                            .outputs = {0},
+                            .type = TestOperationType::ADD
+                        }, {
+                            .inputs = {0, 1, 2},
+                            .outputs = {3},
+                            .type = TestOperationType::REDUCE_MIN
+                        }},
+                .outputIndexes = {3}
+            },
+        .minSupportedVersion = TestHalVersion::V1_2,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_all_inputs_as_internal = TestModelManager::get().add("reduce_min_b155508675_all_inputs_as_internal", get_test_model_all_inputs_as_internal());
+
+}  // namespace generated_tests::reduce_min_b155508675
+
diff --git a/runtime/test/generated/spec_V1_2/reduce_prod_b155508675.example.cpp b/runtime/test/generated/spec_V1_2/reduce_prod_b155508675.example.cpp
new file mode 100644
index 0000000..8fcb2fc
--- /dev/null
+++ b/runtime/test/generated/spec_V1_2/reduce_prod_b155508675.example.cpp
@@ -0,0 +1,174 @@
+// Generated from reduce_prod_b155508675.mod.py
+// DO NOT EDIT
+// clang-format off
+#include "TestHarness.h"
+using namespace test_helper;
+
+namespace generated_tests::reduce_prod_b155508675 {
+
+const TestModel& get_test_model() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {0},
+                .operands = {{ // op1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0, 1, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_INT32,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::BOOL,
+                            .zeroPoint = 0
+                        }, { // op2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {0, 1, 2},
+                            .outputs = {3},
+                            .type = TestOperationType::REDUCE_PROD
+                        }},
+                .outputIndexes = {3}
+            },
+        .minSupportedVersion = TestHalVersion::V1_2,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model = TestModelManager::get().add("reduce_prod_b155508675", get_test_model());
+
+}  // namespace generated_tests::reduce_prod_b155508675
+
+namespace generated_tests::reduce_prod_b155508675 {
+
+const TestModel& get_test_model_all_inputs_as_internal() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {4},
+                .operands = {{ // op1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::TEMPORARY_VARIABLE,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0, 1, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_INT32,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::BOOL,
+                            .zeroPoint = 0
+                        }, { // op2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // op1_new
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // dummy
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({0.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::INT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {4, 5, 6},
+                            .outputs = {0},
+                            .type = TestOperationType::ADD
+                        }, {
+                            .inputs = {0, 1, 2},
+                            .outputs = {3},
+                            .type = TestOperationType::REDUCE_PROD
+                        }},
+                .outputIndexes = {3}
+            },
+        .minSupportedVersion = TestHalVersion::V1_2,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_all_inputs_as_internal = TestModelManager::get().add("reduce_prod_b155508675_all_inputs_as_internal", get_test_model_all_inputs_as_internal());
+
+}  // namespace generated_tests::reduce_prod_b155508675
+
diff --git a/runtime/test/generated/spec_V1_2/reduce_sum_b155508675.example.cpp b/runtime/test/generated/spec_V1_2/reduce_sum_b155508675.example.cpp
new file mode 100644
index 0000000..e210964
--- /dev/null
+++ b/runtime/test/generated/spec_V1_2/reduce_sum_b155508675.example.cpp
@@ -0,0 +1,174 @@
+// Generated from reduce_sum_b155508675.mod.py
+// DO NOT EDIT
+// clang-format off
+#include "TestHarness.h"
+using namespace test_helper;
+
+namespace generated_tests::reduce_sum_b155508675 {
+
+const TestModel& get_test_model() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {0},
+                .operands = {{ // op1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0, 1, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_INT32,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::BOOL,
+                            .zeroPoint = 0
+                        }, { // op2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {0, 1, 2},
+                            .outputs = {3},
+                            .type = TestOperationType::REDUCE_SUM
+                        }},
+                .outputIndexes = {3}
+            },
+        .minSupportedVersion = TestHalVersion::V1_2,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model = TestModelManager::get().add("reduce_sum_b155508675", get_test_model());
+
+}  // namespace generated_tests::reduce_sum_b155508675
+
+namespace generated_tests::reduce_sum_b155508675 {
+
+const TestModel& get_test_model_all_inputs_as_internal() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {4},
+                .operands = {{ // op1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::TEMPORARY_VARIABLE,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0, 1, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_INT32,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<bool8>({false}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::BOOL,
+                            .zeroPoint = 0
+                        }, { // op2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // op1_new
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({3.0f}),
+                            .dimensions = {1, 1, 1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // dummy
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<float>({0.0f}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::TENSOR_FLOAT32,
+                            .zeroPoint = 0
+                        }, { // param2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::INT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {4, 5, 6},
+                            .outputs = {0},
+                            .type = TestOperationType::ADD
+                        }, {
+                            .inputs = {0, 1, 2},
+                            .outputs = {3},
+                            .type = TestOperationType::REDUCE_SUM
+                        }},
+                .outputIndexes = {3}
+            },
+        .minSupportedVersion = TestHalVersion::V1_2,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_all_inputs_as_internal = TestModelManager::get().add("reduce_sum_b155508675_all_inputs_as_internal", get_test_model_all_inputs_as_internal());
+
+}  // namespace generated_tests::reduce_sum_b155508675
+
diff --git a/runtime/test/specs/V1_2/reduce_all_b155508675.mod.py b/runtime/test/specs/V1_2/reduce_all_b155508675.mod.py
new file mode 100644
index 0000000..f0fb40e
--- /dev/null
+++ b/runtime/test/specs/V1_2/reduce_all_b155508675.mod.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Model operands
+op1 = Input("op1", ["TENSOR_BOOL8", [1, 1, 1]])
+op2 = Output("op2", ["TENSOR_BOOL8", [1]])
+
+# Model operations
+model = Model()
+model.Operation("REDUCE_ALL", op1, [0, 1, 2], False).To(op2)
+
+# Example
+Example({
+    op1: [False],
+    op2: [False],
+}, model=model)
diff --git a/runtime/test/specs/V1_2/reduce_any_b155508675.mod.py b/runtime/test/specs/V1_2/reduce_any_b155508675.mod.py
new file mode 100644
index 0000000..67e955e
--- /dev/null
+++ b/runtime/test/specs/V1_2/reduce_any_b155508675.mod.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Model operands
+op1 = Input("op1", ["TENSOR_BOOL8", [1, 1, 1]])
+op2 = Output("op2", ["TENSOR_BOOL8", [1]])
+
+# Model operations
+model = Model()
+model.Operation("REDUCE_ANY", op1, [0, 1, 2], False).To(op2)
+
+# Example
+Example({
+    op1: [False],
+    op2: [False],
+}, model=model)
diff --git a/runtime/test/specs/V1_2/reduce_max_b155508675.mod.py b/runtime/test/specs/V1_2/reduce_max_b155508675.mod.py
new file mode 100644
index 0000000..72c8e65
--- /dev/null
+++ b/runtime/test/specs/V1_2/reduce_max_b155508675.mod.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Model operands
+op1 = Input("op1", ["TENSOR_FLOAT32", [1, 1, 1]])
+op2 = Output("op2", ["TENSOR_FLOAT32", [1]])
+
+# Model operations
+model = Model()
+model.Operation("REDUCE_MAX", op1, [0, 1, 2], False).To(op2)
+
+# Example
+Example({
+    op1: [3.0],
+    op2: [3.0],
+}, model=model)
diff --git a/runtime/test/specs/V1_2/reduce_min_b155508675.mod.py b/runtime/test/specs/V1_2/reduce_min_b155508675.mod.py
new file mode 100644
index 0000000..0fb9c8f
--- /dev/null
+++ b/runtime/test/specs/V1_2/reduce_min_b155508675.mod.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Model operands
+op1 = Input("op1", ["TENSOR_FLOAT32", [1, 1, 1]])
+op2 = Output("op2", ["TENSOR_FLOAT32", [1]])
+
+# Model operations
+model = Model()
+model.Operation("REDUCE_MIN", op1, [0, 1, 2], False).To(op2)
+
+# Example
+Example({
+    op1: [3.0],
+    op2: [3.0],
+}, model=model)
diff --git a/runtime/test/specs/V1_2/reduce_prod_b155508675.mod.py b/runtime/test/specs/V1_2/reduce_prod_b155508675.mod.py
new file mode 100644
index 0000000..212c9b3
--- /dev/null
+++ b/runtime/test/specs/V1_2/reduce_prod_b155508675.mod.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Model operands
+op1 = Input("op1", ["TENSOR_FLOAT32", [1, 1, 1]])
+op2 = Output("op2", ["TENSOR_FLOAT32", [1]])
+
+# Model operations
+model = Model()
+model.Operation("REDUCE_PROD", op1, [0, 1, 2], False).To(op2)
+
+# Example
+Example({
+    op1: [3.0],
+    op2: [3.0],
+}, model=model)
diff --git a/runtime/test/specs/V1_2/reduce_sum_b155508675.mod.py b/runtime/test/specs/V1_2/reduce_sum_b155508675.mod.py
new file mode 100644
index 0000000..a2cb79d
--- /dev/null
+++ b/runtime/test/specs/V1_2/reduce_sum_b155508675.mod.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Model operands
+op1 = Input("op1", ["TENSOR_FLOAT32", [1, 1, 1]])
+op2 = Output("op2", ["TENSOR_FLOAT32", [1]])
+
+# Model operations
+model = Model()
+model.Operation("REDUCE_SUM", op1, [0, 1, 2], False).To(op2)
+
+# Example
+Example({
+    op1: [3.0],
+    op2: [3.0],
+}, model=model)
diff --git a/tools/api/types.spec b/tools/api/types.spec
index 3d63f2f..606af5e 100644
--- a/tools/api/types.spec
+++ b/tools/api/types.spec
@@ -4996,6 +4996,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link %{OperandType}} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
 %insert-lines AVAIL29
      */
     %{DeclareOperation_1.2 REDUCE_ALL 75},
@@ -5022,6 +5024,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link %{OperandType}} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
 %insert-lines AVAIL29
      */
     %{DeclareOperation_1.2 REDUCE_ANY 76},
@@ -5053,6 +5057,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link %{OperandType}} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
 %kind ndk hal_1.3+
      *      For a {@link %{OperandTypeLinkPfx}TENSOR_QUANT8_ASYMM} and
      *      {@link %{OperandTypeLinkPfx}TENSOR_QUANT8_ASYMM_SIGNED} tensor,
@@ -5092,6 +5098,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link %{OperandType}} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
 %kind ndk hal_1.3+
      *      For a {@link %{OperandTypeLinkPfx}TENSOR_QUANT8_ASYMM} and
      *      {@link %{OperandTypeLinkPfx}TENSOR_QUANT8_ASYMM_SIGNED} tensor,
@@ -5126,6 +5134,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link %{OperandType}} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
 %insert-lines AVAIL29
      */
     %{DeclareOperation_1.2 REDUCE_PROD 79},
@@ -5152,6 +5162,8 @@
      *
      * Outputs:
      * * 0: A tensor of the same {@link %{OperandType}} as input0.
+     *      If all dimensions are reduced and keep_dims is false, the output
+     *      shape is [1].
 %insert-lines AVAIL29
      */
     %{DeclareOperation_1.2 REDUCE_SUM 80},