Repository: onnx/onnx
Pull request: Fix SegFault bug in shape inference
### Description

The bug is SegFault during shape inference if the schema not be set inference function.

The schema has `CheckInputOutputType` for shape inference and `Verify` for raw node proto. And the behavior is not fully aligned.

### Motivation and Context

fixes #5989 


Problem statement:
SegFault during shape inference if the schema not be set inference function
# Bug Report

### Describe the bug
SegFault during shape inference if the schema not be set inference function

### Reproduction instructions

cpp (Optional):

```
ONNX_OPERATOR_SCHEMA(CustomOp);
```

python:

```
import onnx
from onnx import defs

input = """
            <
                ir_version: 7,
                opset_import: [
                    "" : 1
                ]
            >
            agraph (float[N, 128] X, int32 Y) => (float[N] Z)
            {
                Z = CustomOp(X, Y)
            }
           """
model = onnx.parser.parse_model(input)

op_schema = defs.OpSchema(
    'CustomOp',
    '',
    1,
    inputs=[
    ],
    outputs=[
    ],
)

# Uncomment if not register in cpp
# onnx.defs.register_schema(op_schema)
onnx.shape_inference.infer_shapes(model, True)

```

### Expected behavior

Raise a python exception rather than crash.

### Notes

The root case is the input or output of schema is empty and the corresponding field in graph is no-empty. So the `input.back` from schema will invalid.

Hints from issue discussion:

Commit message: reformat py lint

Signed-off-by: opluss <opluss@qq.com>

Patch to review:
diff --git a/onnx/defs/schema.cc b/onnx/defs/schema.cc
index b8fab81d22d..f58230383b1 100644
--- a/onnx/defs/schema.cc
+++ b/onnx/defs/schema.cc
@@ -107,6 +107,30 @@ OpSchemaRegistry* OpSchemaRegistry::Instance() {
 
 void OpSchema::CheckInputOutputType(struct InferenceContext& ctx) const {
   std::unordered_map<std::string, std::string> type_constraints;
+  if (inputs_.empty() && ctx.getNumInputs() > 0) {
+    fail_check(
+        "Node (",
+        domain(),
+        "::",
+        Name(),
+        ":",
+        since_version(),
+        ") need empty inputs, but got ",
+        ctx.getNumInputs(),
+        " in graph");
+  }
+  if (outputs_.empty() && ctx.getNumOutputs() > 0) {
+    fail_check(
+        "Node (",
+        domain(),
+        "::",
+        Name(),
+        ":",
+        since_version(),
+        ") need empty outputs, but got ",
+        ctx.getNumOutputs(),
+        " in graph");
+  }
   // check all input types
   for (size_t in_idx = 0; in_idx < ctx.getNumInputs(); ++in_idx) {
     // If the last input is Variadic by definition, checker still needs to check the rest of actual input's type
diff --git a/onnx/test/shape_inference_test.py b/onnx/test/shape_inference_test.py
index 48012d15b74..75280f6c936 100644
--- a/onnx/test/shape_inference_test.py
+++ b/onnx/test/shape_inference_test.py
@@ -9875,6 +9875,33 @@ def test_compress_with_axis(self) -> None:
         )
         self._assert_inferred(graph, [make_tensor_value_info("output", TensorProto.INT64, (2, "N", 3, None))])  # type: ignore
 
+    def test_check_type_when_schema_has_empty_io(self):
+        input = """
+            <
+                ir_version: 7,
+                opset_import: ["" : 1]
+            >
+            agraph (X, Y) => (Z)
+            {
+                Z = CustomOp(X, Y)
+            }
+           """
+        model = onnx.parser.parse_model(input)
+
+        op_schema = defs.OpSchema(
+            "CustomOp",
+            "",
+            1,
+            inputs=[],
+            outputs=[],
+        )
+        onnx.defs.register_schema(op_schema)
+        with self.assertRaises(onnx.shape_inference.InferenceError):
+            onnx.shape_inference.infer_shapes(model, True)
+        onnx.defs.deregister_schema(
+            op_schema.name, op_schema.since_version, op_schema.domain
+        )
+
 
 if __name__ == "__main__":
     unittest.main()


Code review comments:
diff_hunk: @@ -107,6 +107,30 @@ OpSchemaRegistry* OpSchemaRegistry::Instance() {
 
 void OpSchema::CheckInputOutputType(struct InferenceContext& ctx) const {
   std::unordered_map<std::string, std::string> type_constraints;
+  if (inputs_.empty() && ctx.getNumInputs() > 0) {
+    fail_check(
+        "Node (",
+        domain(),
+        "::",
+        Name(),
+        ":",
+        since_version(),
+        ") need empty inputs, but got ",
+        ctx.getNumInputs(),
+        " in graph");
+  }
+  if (outputs_.empty() && ctx.getNumOutputs() > 0) {
+    fail_check(
+        "Node (",
+        domain(),
+        "::",
+        Name(),
+        ":",
+        since_version(),
+        ") need empty outputs, but got ",
path: onnx/defs/schema.cc
text: @user1:
```suggestion
        ") yields zero outputs, but got ",
```