Skip to content

Commit

Permalink
Refactor ErrorObject and Error classes' initialization
Browse files Browse the repository at this point in the history
Currently, we allow raising errors from vm with any given string. This
is a bad practice because any internal error should have a corresponding
error class.

This PR fixes this by only accepting pre-defined error types (enum) when
initializing error objects. It also checks every error type (int) has a
corresponding name (string) during vm initialization, which saves us the
work to visually sync the 2 lists manually.
  • Loading branch information
st0012 committed May 3, 2020
1 parent 44f3a14 commit a77e13f
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 27 deletions.
4 changes: 2 additions & 2 deletions vm/class.go
Original file line number Diff line number Diff line change
Expand Up @@ -1403,15 +1403,15 @@ var builtinClassCommonInstanceMethods = []*BuiltinMethodObject{
return t.vm.InitErrorObject(errors.InternalError, sourceLine, "%s", args[0].Inspect())
}

return t.vm.InitErrorObject(errorClass.Name, sourceLine, "%s", args[0].Inspect())
return t.vm.InitErrorObjectFromClass(errorClass, sourceLine, "%s", args[0].Inspect())
case 2:
errorClass, ok := args[0].(*RClass)

if !ok {
return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongArgumentTypeFormatNum, 2, "a class", args[0].Class().Name)
}

return t.vm.InitErrorObject(errorClass.Name, sourceLine, "%s", args[1].Inspect())
return t.vm.InitErrorObjectFromClass(errorClass, sourceLine, "%s", args[1].Inspect())
}

return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, errors.WrongNumberOfArgumentLess, 2, aLen)
Expand Down
22 changes: 12 additions & 10 deletions vm/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ type Error struct {
message string
stackTraces []string
storedTraces bool
Type string
}

// Internal functions ===================================================
Expand All @@ -35,9 +34,7 @@ func (vm *VM) InitNoMethodError(sourceLine int, methodName string, receiver Obje
}

// InitErrorObject initializes and returns Error object
func (vm *VM) InitErrorObject(errorType string, sourceLine int, format string, args ...interface{}) *Error {
errClass := vm.objectClass.getClassConstant(errorType)

func (vm *VM) InitErrorObjectFromClass(errClass *RClass, sourceLine int, format string, args ...interface{}) *Error {
t := &vm.mainThread
cf := t.callFrameStack.top()

Expand All @@ -53,17 +50,22 @@ func (vm *VM) InitErrorObject(errorType string, sourceLine int, format string, a
return &Error{
BaseObj: NewBaseObject(errClass),
// Add 1 to source line because it's zero indexed
message: fmt.Sprintf(errorType+": "+format, args...),
message: fmt.Sprintf(errClass.Name+": "+format, args...),
stackTraces: []string{fmt.Sprintf("from %s:%d", cf.FileName(), sourceLine)},
Type: errorType,
}
}

func (vm *VM) initErrorClasses() {
errTypes := []string{errors.InternalError, errors.IOError, errors.ArgumentError, errors.NameError, errors.StopIteration, errors.TypeError, errors.NoMethodError, errors.ConstantAlreadyInitializedError, errors.HTTPError, errors.ZeroDivisionError, errors.ChannelCloseError, errors.NotImplementedError}
// InitErrorObject initializes and returns Error object
func (vm *VM) InitErrorObject(errorType errors.ErrorType, sourceLine int, format string, args ...interface{}) *Error {
en := errors.GetErrorName(errorType)
errClass := vm.objectClass.getClassConstant(en)
return vm.InitErrorObjectFromClass(errClass, sourceLine, format, args...)
}

for _, errType := range errTypes {
c := vm.initializeClass(errType)
func (vm *VM) initErrorClasses() {
for _, et := range errors.AllErrorTypes() {
en := errors.GetErrorName(et)
c := vm.initializeClass(en)
vm.objectClass.setClassConstant(c)
}
}
Expand Down
68 changes: 56 additions & 12 deletions vm/errors/error.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,76 @@
package errors

import "fmt"

// ErrorType is the enum representation for built-in error types
type ErrorType int8

const (
// InternalError is the default error type
InternalError = "InternalError"
InternalError ErrorType = iota
// IOError is an IO error such as file error
IOError = "IOError"
IOError
// ArgumentError is for an argument-related error
ArgumentError = "ArgumentError"
ArgumentError
// NameError is for a constant-related error
NameError = "NameError"
NameError
// StopIteration is raised when there are no more elements in an iterator
StopIteration = "StopIteration"
StopIteration
// TypeError is for a type-related error
TypeError = "TypeError"
TypeError
// NoMethodError is for an intentionally unsupported-method error
NoMethodError = "NoMethodError"
NoMethodError
// ConstantAlreadyInitializedError means user re-declares twice
ConstantAlreadyInitializedError = "ConstantAlreadyInitializedError"
ConstantAlreadyInitializedError
// HTTPError is returned when when a request fails to return a proper response
HTTPError = "HTTPError"
HTTPError
// ZeroDivisionError is for zero-division by Integer/Float/Decimal value
ZeroDivisionError = "ZeroDivisionError"
ZeroDivisionError
// ChannelCloseError is for accessing to the closed channel
ChannelCloseError = "ChannelCloseError"
ChannelCloseError
// NotImplementedError means the method is missing
NotImplementedError = "NotImplementedError"
NotImplementedError

// This is an anchor for getting all error types' enum values, see AllErrorTypes
EndOfErrorTypeConst
)

var errorTypesMap = map[ErrorType]string{
InternalError: "InternalError",
IOError: "IOError",
ArgumentError: "ArgumentError",
NameError: "NameError",
StopIteration: "StopIteration",
TypeError: "TypeError",
NoMethodError: "NoMethodError",
ConstantAlreadyInitializedError: "ConstantAlreadyInitializedError",
HTTPError: "HTTPError",
ZeroDivisionError: "ZeroDivisionError",
ChannelCloseError: "ChannelCloseError",
NotImplementedError: "NotImplementedError",
}


// AllErrorTypes returns all error types defined in this package in their enum format.
func AllErrorTypes() []ErrorType {
ts := make([]ErrorType, EndOfErrorTypeConst)
for i := 0; i < int(EndOfErrorTypeConst); i++ {
ts[i] = ErrorType(i)
}
return ts
}

// GetErrorName receives an ErrorType enum and returns the corresponding error name.
func GetErrorName(t ErrorType) string {
v, ok := errorTypesMap[t]

if ok {
return v
}

panic(fmt.Errorf("expect to find ErrorType %d's name", t))
}

/*
Here defines different error message formats for different types of errors
*/
Expand Down
2 changes: 1 addition & 1 deletion vm/issue_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func PrintError(v *VM) {
if !ok {
fmt.Println("No error detected")
}
fmt.Printf("# %s\n", err.Type)
fmt.Printf("# %s\n", err.Class().Name)
fmt.Println(err.Message())

fmt.Printf("### Goby version\n%s\n", Version)
Expand Down
4 changes: 2 additions & 2 deletions vm/thread.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,14 +490,14 @@ func (t *Thread) reportArgumentError(sourceLine, idealArgNumber int, methodName
}

// pushErrorObject pushes the Error object to the stack
func (t *Thread) pushErrorObject(errorType string, sourceLine int, format string, args ...interface{}) {
func (t *Thread) pushErrorObject(errorType errors.ErrorType, sourceLine int, format string, args ...interface{}) {
err := t.vm.InitErrorObject(errorType, sourceLine, format, args...)
t.Stack.Push(&Pointer{Target: err})
panic(err.Message())
}

// setErrorObject replaces a certain stack element with the Error object
func (t *Thread) setErrorObject(receiverPtr, sp int, errorType string, sourceLine int, format string, args ...interface{}) {
func (t *Thread) setErrorObject(receiverPtr, sp int, errorType errors.ErrorType, sourceLine int, format string, args ...interface{}) {
err := t.vm.InitErrorObject(errorType, sourceLine, format, args...)
t.Stack.Set(receiverPtr, &Pointer{Target: err})
t.Stack.pointer = sp
Expand Down

0 comments on commit a77e13f

Please sign in to comment.