windows: use SyscallN in mkwinsyscall

The mkwinsyscall command has a hard limit of 15 on the
number of syscall arguments. Windows has several system
calls with more than 15 arguments, for example CreateFontPackage
has 18 arguments.

If the number of arguments is higher than 15 we use SyscallN.

Fixes golang/go#57914

Change-Id: I4205e779a960ae10c0778de7876154e0d7ec00a1
GitHub-Last-Rev: 1f1e96fab7327f803f8213ca4264d359046971cd
GitHub-Pull-Request: golang/sys#171
Reviewed-on: https://go-review.googlesource.com/c/sys/+/518995
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
diff --git a/windows/mkwinsyscall/mkwinsyscall.go b/windows/mkwinsyscall/mkwinsyscall.go
index 11f4873..3947092 100644
--- a/windows/mkwinsyscall/mkwinsyscall.go
+++ b/windows/mkwinsyscall/mkwinsyscall.go
@@ -57,7 +57,6 @@
 	"go/parser"
 	"go/token"
 	"io"
-	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -568,6 +567,8 @@
 		return 12
 	case n <= 15:
 		return 15
+	case n <= 42: // current SyscallN limit
+		return n
 	default:
 		panic("too many arguments to system call")
 	}
@@ -579,6 +580,9 @@
 	if c == 3 {
 		return syscalldot() + "Syscall"
 	}
+	if c > 15 {
+		return syscalldot() + "SyscallN"
+	}
 	return syscalldot() + "Syscall" + strconv.Itoa(c)
 }
 
@@ -923,7 +927,7 @@
 	if *filename == "" {
 		_, err = os.Stdout.Write(data)
 	} else {
-		err = ioutil.WriteFile(*filename, data, 0644)
+		err = os.WriteFile(*filename, data, 0644)
 	}
 	if err != nil {
 		log.Fatal(err)
@@ -1011,7 +1015,7 @@
 
 {{define "results"}}{{if .Rets.List}}{{.Rets.List}} {{end}}{{end}}
 
-{{define "syscall"}}{{.Rets.SetReturnValuesCode}}{{.Syscall}}(proc{{.DLLFuncName}}.Addr(), {{.ParamCount}}, {{.SyscallParamList}}){{end}}
+{{define "syscall"}}{{.Rets.SetReturnValuesCode}}{{.Syscall}}(proc{{.DLLFuncName}}.Addr(),{{if le .ParamCount 15}} {{.ParamCount}},{{end}} {{.SyscallParamList}}){{end}}
 
 {{define "tmpvarsreadback"}}{{range .Params}}{{if .TmpVarReadbackCode}}
 {{.TmpVarReadbackCode}}{{end}}{{end}}{{end}}
diff --git a/windows/mkwinsyscall/mkwinsyscall_test.go b/windows/mkwinsyscall/mkwinsyscall_test.go
index cabbf40..b000e6b 100644
--- a/windows/mkwinsyscall/mkwinsyscall_test.go
+++ b/windows/mkwinsyscall/mkwinsyscall_test.go
@@ -9,6 +9,7 @@
 	"go/format"
 	"os"
 	"path/filepath"
+	"strings"
 	"testing"
 )
 
@@ -48,3 +49,65 @@
 		})
 	}
 }
+
+func TestSyscallXGeneration(t *testing.T) {
+	tests := []struct {
+		name        string
+		wantsysfunc string
+		sig         string
+	}{
+		{
+			name:        "syscall with 2 params",
+			wantsysfunc: "syscall.Syscall",
+			sig:         "Example(a1 *uint16, a2 *uint16) = ",
+		},
+		{
+			name:        "syscall with 6 params",
+			wantsysfunc: "syscall.Syscall6",
+			sig:         "Example(a1 *uint, a2 *uint, a3 *uint, a4 *uint, a5 *uint, a6 *uint) = ",
+		},
+		{
+			name:        "syscall with 15 params",
+			wantsysfunc: "syscall.Syscall15",
+			sig: strings.ReplaceAll(`Example(a1 *uint, a2 *uint, a3 *uint, a4 *uint, a5 *uint, a6 *uint,
+						a7 *uint, a8 *uint, a9 *uint, a10 *uint, a11 *uint, a12 *uint,
+						a13 *uint, a14 *uint, a15 *uint) = `, "\n", ""),
+		},
+		{
+			name:        "syscall with 18 params",
+			wantsysfunc: "syscall.SyscallN",
+			sig: strings.ReplaceAll(`Example(a1 *uint, a2 *uint, a3 *uint, a4 *uint, a5 *uint, a6 *uint,
+						a7 *uint, a8 *uint, a9 *uint, a10 *uint, a11 *uint, a12 *uint,
+						a13 *uint, a14 *uint, a15 *uint, a16 *uint, a17 *uint, a18 *uint) = `, "\n", ""),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Write the syscall into a temp file for testing.
+			prefix := "package windows\n//sys " + tt.sig
+			suffix := ".Example"
+			name := filepath.Join(t.TempDir(), "syscall.go")
+			if err := os.WriteFile(name, []byte(prefix+"example"+suffix), 0666); err != nil {
+				t.Fatal(err)
+			}
+
+			// Ensure parsing, generating, and formatting run without errors.
+			// This is good enough to show that escaping is working.
+			src, err := ParseFiles([]string{name})
+			if err != nil {
+				t.Fatal(err)
+			}
+			var buf bytes.Buffer
+			if err := src.Generate(&buf); err != nil {
+				t.Fatal(err)
+			}
+			if _, err := format.Source(buf.Bytes()); err != nil {
+				t.Fatal(err)
+			}
+
+			if !strings.Contains(buf.String(), tt.wantsysfunc+"(") {
+				t.Fatalf("expected syscall func %q in buffer %s", tt.wantsysfunc, buf.String())
+			}
+		})
+	}
+}