Repository: bytedance/sonic
Pull request #188 (2022-02-16 10:28:59)

fix:(loader) init `_Moduledata.gcdata`pointer to avoid panic on plugin-mode


Patch:
diff --git a/.github/workflows/push-check-go116.yml b/.github/workflows/push-check-go116.yml
index c35f082..a962f08 100644
--- a/.github/workflows/push-check-go116.yml
+++ b/.github/workflows/push-check-go116.yml
@@ -21,4 +21,4 @@ jobs:
             ${{ runner.os }}-go-
 
       - name: Unit Test
-        run: GOMAXPROCS=4 go test -v -race -gcflags=-d=checkptr=0 -covermode=atomic -coverprofile=coverage.out ./...
+        run: GOMAXPROCS=4 go test -v -gcflags=-d=checkptr=0 -race -covermode=atomic -coverprofile=coverage.out ./...
diff --git a/internal/loader/funcdata.go b/internal/loader/funcdata.go
index 047a0da..3e916f6 100644
--- a/internal/loader/funcdata.go
+++ b/internal/loader/funcdata.go
@@ -53,6 +53,8 @@ var (
     modList []*_ModuleData
 )
 
+var emptyByte byte
+
 func encodeVariant(v int) []byte {
     var u int
     var r []byte
diff --git a/internal/loader/funcdata_go115.go b/internal/loader/funcdata_go115.go
index bd334de..3673da8 100644
--- a/internal/loader/funcdata_go115.go
+++ b/internal/loader/funcdata_go115.go
@@ -155,6 +155,8 @@ func registerFunction(name string, pc uintptr, textSize uintptr, fp int, args in
         minpc       : minpc,
         maxpc       : maxpc,
         modulename  : name,
+        gcdata: uintptr(unsafe.Pointer(&emptyByte)),
+        gcbss: uintptr(unsafe.Pointer(&emptyByte)),
     }
 
     /* verify and register the new module */
diff --git a/internal/loader/funcdata_go116.go b/internal/loader/funcdata_go116.go
index 1316b64..20d7df1 100644
--- a/internal/loader/funcdata_go116.go
+++ b/internal/loader/funcdata_go116.go
@@ -159,6 +159,8 @@ func registerFunction(name string, pc uintptr, textSize uintptr, fp int, args in
         minpc       : minpc,
         maxpc       : maxpc,
         modulename  : name,
+        gcdata: uintptr(unsafe.Pointer(&emptyByte)),
+        gcbss: uintptr(unsafe.Pointer(&emptyByte)),
     }
 
     /* verify and register the new module */
diff --git a/internal/loader/funcdata_go118.go b/internal/loader/funcdata_go118.go
index 07312fa..433a6ba 100644
--- a/internal/loader/funcdata_go118.go
+++ b/internal/loader/funcdata_go118.go
@@ -28,7 +28,6 @@ import (
 // This list must match the list in cmd/internal/objabi/funcid.go.
 type funcFlag uint8
 
-
 type _Func struct {
     entryOff    uint32 // start pc
     nameoff     int32   // function name
@@ -186,6 +185,8 @@ func registerFunction(name string, pc uintptr, textSize uintptr, fp int, args in
         minpc       : minpc,
         maxpc       : maxpc,
         modulename  : name,
+        gcdata: uintptr(unsafe.Pointer(&emptyByte)),
+        gcbss: uintptr(unsafe.Pointer(&emptyByte)),
         gofunc: base,
     }
 


Test patch:
diff --git a/issue_test/plugin/main.go b/issue_test/plugin/main.go
new file mode 100644
index 0000000..b64f761
--- /dev/null
+++ b/issue_test/plugin/main.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+    `fmt`
+
+    `github.com/bytedance/sonic`
+)
+
+var V int
+
+var Obj map[string]string
+
+func init() {
+    if err := sonic.UnmarshalString(`{"a":"b"}`, &Obj); err != nil {
+        panic(err)
+    }
+}
+
+func F() { fmt.Printf("Hello, number %d\n", V) }
+
+func Unmarshal(json string, val interface{}) error {
+    return sonic.UnmarshalString(json, val)
+}
diff --git a/issue_test/plugin_test.go b/issue_test/plugin_test.go
new file mode 100644
index 0000000..89d41cc
--- /dev/null
+++ b/issue_test/plugin_test.go
@@ -0,0 +1,96 @@
+// +build !race
+
+/*
+ * Copyright 2021 ByteDance Inc.
+ *
+ * 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.
+ */
+
+package issue_test
+
+import (
+    `bytes`
+    `fmt`
+    `os/exec`
+    `plugin`
+    `runtime`
+    `testing`
+    `reflect`
+
+    _ `github.com/bytedance/sonic`
+)
+
+func buildPlugin() {
+    out := bytes.NewBuffer(nil)
+    bin0, err := exec.LookPath("rm")
+    if err != nil {
+        panic(err)
+    }
+    cmd0 := exec.Cmd{
+        Path: bin0,
+        Args: []string{"rm", "-f", "plugin/plugin."+runtime.Version()+".so"},
+        Stdout: out,
+        Stderr: out,
+    }
+    if err := cmd0.Run(); err != nil {
+        panic(string(out.Bytes()))
+    }
+    out.Reset()
+    bin, err := exec.LookPath("go")
+    if err != nil {
+        panic(err)
+    }
+    cmd := exec.Cmd{
+        Path: bin,
+        Args: []string{"go", "build", "-buildmode", "plugin", "-o", "plugin/plugin."+runtime.Version()+".so", "plugin/main.go"},
+        Stdout: out,
+        Stderr: out,
+    }
+    if err := cmd.Run(); err != nil {
+        panic(string(out.Bytes()))
+    }
+}
+
+
+func TestPlugin(t *testing.T) {
+    buildPlugin()
+    p, err := plugin.Open("plugin/plugin."+runtime.Version()+".so")
+    if err != nil {
+        t.Fatal(err)
+    }
+    v, err := p.Lookup("V")
+    if err != nil {
+        t.Fatal(err)
+    }
+    f, err := p.Lookup("F")
+    if err != nil {
+        t.Fatal(err)
+    }
+    *v.(*int) = 7
+    f.(func())() // prints "Hello, number 7"
+    obj, err := p.Lookup("Obj")
+    m := *(obj.(*map[string]string))
+    fmt.Printf("%#v\n", m)
+    d, err := p.Lookup("Unmarshal")
+    if err != nil {
+        t.Fatal(err)
+    }
+    dec := d.(func(json string, val interface{}) error)
+    var exp map[string]string
+    if err := dec(`{"a":"b"}`, &exp); err != nil {
+        t.Fatal(err)
+    }
+    if !reflect.DeepEqual(m, exp) {
+        t.Fatal(m, exp)
+    }
+}