From 1362d4bf951e870fec038a9b6252532f5e5b145e Mon Sep 17 00:00:00 2001 From: Geon Kim Date: Sat, 26 Aug 2023 03:28:41 +0900 Subject: [PATCH] Initial commit --- .github/workflows/test.yml | 28 ++++++++++ .gitignore | 21 ++++++++ LICENSE | 21 ++++++++ README.md | 7 +++ go.mod | 11 ++++ go.sum | 10 ++++ ununsafe.go | 66 +++++++++++++++++++++++ ununsafe_test.go | 108 +++++++++++++++++++++++++++++++++++++ 8 files changed, 272 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 ununsafe.go create mode 100644 ununsafe_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..42c3413 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,28 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b735ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..10c811d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Geon Kim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c665d4f --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# ununsafe + +[![Go Reference](https://pkg.go.dev/badge/github.com/KimMachineGun/ununsafe.svg)](https://pkg.go.dev/github.com/KimMachineGun/ununsafe) +[![Go Report Card](https://goreportcard.com/badge/github.com/KimMachineGun/ununsafe)](https://goreportcard.com/report/github.com/KimMachineGun/ununsafe) +[![Test](https://github.com/KimMachineGun/ununsafe/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/KimMachineGun/ununsafe/actions/workflows/test.yml) + +A slightly safer version of `unsafe`. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1a19f72 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/KimMachineGun/ununsafe + +go 1.20 + +require github.com/stretchr/testify v1.8.4 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa4b6e6 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ununsafe.go b/ununsafe.go new file mode 100644 index 0000000..2993a60 --- /dev/null +++ b/ununsafe.go @@ -0,0 +1,66 @@ +package ununsafe + +import ( + "fmt" + "unsafe" +) + +// StringToBytes converts string to []byte without copy. +func StringToBytes(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) +} + +// BytesToString converts []byte to string without copy. +func BytesToString(b []byte) string { + return unsafe.String(unsafe.SliceData(b), len(b)) +} + +// SizeOf is a wrapper of unsafe.Sizeof. +func SizeOf[T any]() uint64 { + var t T + return uint64(unsafe.Sizeof(t)) +} + +func ValueToValue[From, To any](from From) To { + fromSize := SizeOf[From]() + toSize := SizeOf[To]() + if fromSize != toSize { + panic(fmt.Sprintf("ununsafe.ScalarToScalar: size mismatch %d != %d", fromSize, toSize)) + } + return *(*To)(unsafe.Pointer(&from)) +} + +func SliceToValue[From, To any](arr []From) To { + l := uint64(len(arr)) + fromSize := SizeOf[From]() + toSize := SizeOf[To]() + if l*fromSize != toSize { + panic(fmt.Sprintf("ununsafe.VectorToScalar: size mismatch %d*%d != %d", l, fromSize, toSize)) + } + return *(*To)(unsafe.Pointer(unsafe.SliceData(arr))) +} + +func ValueToSlice[From, To any](from From) []To { + fromSize := SizeOf[From]() + toSize := SizeOf[To]() + if fromSize%toSize != 0 { + panic(fmt.Sprintf("ununsafe.ScalarToVector: size mismatch %d%%%d != 0", fromSize, toSize)) + } + return unsafe.Slice( + (*To)(unsafe.Pointer(&from)), + fromSize/toSize, + ) +} + +func SliceToSlice[From, To any](arr []From) []To { + l := uint64(len(arr)) + fromSize := SizeOf[From]() + toSize := SizeOf[To]() + if l*fromSize%toSize != 0 { + panic(fmt.Sprintf("ununsafe.VectorToVector: size mismatch %d*%d%%%d != 0", l, fromSize, toSize)) + } + return unsafe.Slice( + (*To)(unsafe.Pointer(unsafe.SliceData(arr))), + l*fromSize/toSize, + ) +} diff --git a/ununsafe_test.go b/ununsafe_test.go new file mode 100644 index 0000000..578453a --- /dev/null +++ b/ununsafe_test.go @@ -0,0 +1,108 @@ +package ununsafe_test + +import ( + "math" + "testing" + + "github.com/KimMachineGun/ununsafe" + "github.com/stretchr/testify/assert" +) + +func TestStringBytes(t *testing.T) { + a := assert.New(t) + + tt := []struct { + s string + b []byte + }{ + {"", nil}, + {"a", []byte{'a'}}, + {"ab", []byte{'a', 'b'}}, + {"abc", []byte{'a', 'b', 'c'}}, + } + for _, tc := range tt { + a.Equal(tc.b, ununsafe.StringToBytes(tc.s)) + a.Equal(tc.s, ununsafe.BytesToString(tc.b)) + } +} + +func TestSameSize(t *testing.T) { + a := assert.New(t) + + type A struct { + a int64 + b uint32 + c [4]byte + } + type B [16]byte + type C struct { + a [4]byte + b int32 + c uint64 + } + + tt := []struct { + a A + b B + c C + expected []byte + }{ + { + a: A{ + a: 1, + b: 2, + c: [4]byte{3, 4, 5, 6}, + }, + b: B{ + 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, + 3, 4, 5, 6, + }, + c: C{ + a: [4]byte{1, 0, 0, 0}, + b: 0, + c: 433757350042533890, + }, + expected: []byte{ + 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + 0x2, 0x0, 0x0, 0x0, + 0x3, 0x4, 0x5, 0x6, + }, + }, + { + a: A{ + a: -1, + b: math.MaxUint32, + c: [4]byte{255, 255, 255, 255}, + }, + b: B{ + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + }, + c: C{ + a: [4]byte{255, 255, 255, 255}, + b: -1, + c: math.MaxUint64, + }, + expected: []byte{ + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + }, + }, + } + for _, tc := range tt { + tc := tc + t.Run("", func(t *testing.T) { + a.Equal(tc.expected, ununsafe.ValueToSlice[A, byte](tc.a)) + a.Equal(tc.expected, ununsafe.ValueToSlice[B, byte](tc.b)) + a.Equal(tc.expected, ununsafe.ValueToSlice[C, byte](tc.c)) + a.Equal(tc.a, ununsafe.SliceToValue[byte, A](tc.expected)) + a.Equal(tc.b, ununsafe.SliceToValue[byte, B](tc.expected)) + a.Equal(tc.c, ununsafe.SliceToValue[byte, C](tc.expected)) + }) + } +}