Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introspection (part 7) #116

Merged
merged 6 commits into from
Nov 23, 2023
Merged

Introspection (part 7) #116

merged 6 commits into from
Nov 23, 2023

Conversation

chriso
Copy link
Contributor

@chriso chriso commented Nov 23, 2023

This is the final PR in the introspection series (#107, #108, #109, #111, #112, #113), allowing users to scan objects within regions.

Each region encodes a primitive value, a compound array or struct, or a map. The scanner scans the tree using a preorder traversal, providing type information, primitive values (where applicable), access to functions/regions when references are encountered, and various other type-specific helpers to access information about an object. The scanner does not recurse when references to other regions are encountered. The user is responsible for following pointers (if desired) and keeping track of regions visited to prevent infinite loops when cycles are present in the object graph.

I fixed two inconsistencies in order to build the scanner:

  • the root region now has a type of interface{}, since that's what types.Serialize actually serializes. Encoding this type (rather than the unboxed type) means we don't need a special case for the root region when scanning
  • we now avoid double indirection when there's a reference to a map
Here's an example:
c, err := types.Inspect(b)
if err != nil {
	panic(err)
}

regions := []*types.Region{c.Root()}
for i := 0; i < c.NumRegion(); i++ {
	regions = append(regions, c.Region(i))
}

for i, r := range regions {
	fmt.Println("Reading region", i, "with type", r.Type(), "and size", r.Size())

	s := r.Scan()
	for s.Next() {
		fmt.Println("=> reading from offset", s.Pos())
		if s.Custom() {
			fmt.Println("=> read an object serialized by a custom serializer")
		}

		switch s.Kind() {
		case reflect.Bool:
			fmt.Println("=> read bool", s.Bool())
		case reflect.Int:
			fmt.Println("=> read int", s.Int())
		case reflect.Int8:
			fmt.Println("=> read int", s.Int8())
		case reflect.Int16:
			fmt.Println("=> read int16", s.Int16())
		case reflect.Int32:
			fmt.Println("=> read int32", s.Int32())
		case reflect.Int64:
			fmt.Println("=> read int64", s.Int64())
		case reflect.Uint:
			fmt.Println("=> read uint", s.Uint())
		case reflect.Uint8:
			fmt.Println("=> read uint8", s.Uint8())
		case reflect.Uint16:
			fmt.Println("=> read uint16", s.Uint16())
		case reflect.Uint32:
			fmt.Println("=> read uint32", s.Uint32())
		case reflect.Uint64:
			fmt.Println("=> read uint64", s.Uint64())
		case reflect.Uintptr:
			fmt.Println("=> read uintptr", s.Uintptr())
		case reflect.Float32:
			fmt.Println("=> read float32", s.Float32())
		case reflect.Float64:
			fmt.Println("=> read float64", s.Float64())
		case reflect.Complex64:
			fmt.Println("=> read complex64", s.Complex64())
		case reflect.Complex128:
			fmt.Println("=> read complex128", s.Complex128())

		case reflect.Array:
			fmt.Println("=> read array of type", s.Type(), "with length", s.Len())

		case reflect.Interface:
			if s.Nil() {
				if t := s.Type(); t != nil {
					fmt.Println("=> read interface of type", t, "with nil value")
				} else {
					fmt.Println("=> read nil interface")
				}
			} else if r, off := s.Region(); r != nil {
				fmt.Println("=> read interface of type", s.Type(), "pointing to region", r, "offset", off)
			} else {
				fmt.Println("=> read interface of type", s.Type(), "with static data", s.Uint64())
			}

		case reflect.String:
			if s.Len() == 0 {
				fmt.Println("=> read string with length 0")
			} else {
				r, off := s.Region()
				fmt.Println("=> read string with length", s.Len(), "pointing to region", r, "offset", off)
			}

		case reflect.Slice:
			if r, off := s.Region(); r != nil {
				fmt.Println("=> read slice of type", s.Type(), "with length", s.Len(), "and cap", s.Cap(), "pointing to region", r, "offset", off)
			} else {
				fmt.Println("=> read nil slice of type", s.Type(), "with length", s.Len(), "and cap", s.Cap())
			}

		case reflect.Func:
			if s.Nil() {
				fmt.Println("=> read nil function with type", s.Type())
			} else if ct := s.Function().ClosureType(); ct != nil {
				fmt.Println("=> read function with type", s.Type(), "and closure layout", ct)
			} else {
				fmt.Println("=> read function with type", s.Type())
			}

		case reflect.Struct:
			fmt.Println("=> read struct of type", s.Type())

		case reflect.Pointer:
			if r, off := s.Region(); r != nil {
				fmt.Println("=> read pointer of type", s.Type(), "pointing to region", r, off)
			} else {
				fmt.Println("=> read nil pointer of type", s.Type())
			}

		case reflect.UnsafePointer:
			if r, off := s.Region(); r != nil {
				fmt.Println("=> read unsafe pointer pointing to region", r, "offset", off)
			} else {
				fmt.Println("=> read nil unsafe pointer")
			}

		default:
			panic(fmt.Sprintf("not implemented: %s", s.Kind()))
		}
	}
	if err := s.Close(); err != nil {
		panic(err)
	}
}

Copy link
Contributor

@achille-roussel achille-roussel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Epic 🙌

@chriso chriso merged commit 3f58d01 into main Nov 23, 2023
2 checks passed
@chriso chriso deleted the object-graph-iteration branch November 23, 2023 01:15
@chriso chriso restored the object-graph-iteration branch November 23, 2023 01:17
@chriso chriso mentioned this pull request Nov 23, 2023
chriso added a commit that referenced this pull request Nov 23, 2023
There were a few commits missing from
#116
@chriso chriso mentioned this pull request Nov 30, 2023
chriso added a commit that referenced this pull request Nov 30, 2023
The deserialization layer panics on invalid input. Bad state can take
down the entire program currently.

This PR updates `(*coroutine.Context).Unmarshal` to catch these panics
and return an error value, allowing the caller to gracefully handle bad
input.

A better long term solution would be to abandon the `deserialize*()`
functions and instead build the deserialization layer on the scanner
introduced in #116, which
avoids panics in many cases and could be updated to avoid panics in all
cases.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants