Skip to content

Code safety improvements #105

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

Merged
merged 3 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ name: Tests

on:
push:
branches: [ master ]
branches: [ main ]
paths-ignore: [ README.md ]
pull_request:
branches: [ master ]
branches: [ main ]
paths-ignore: [ README.md ]
workflow_dispatch:

Expand Down
19 changes: 11 additions & 8 deletions Sources/GraphQL/Error/SyntaxError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,19 @@ func highlightSourceAtLocation(source: Source, location: SourceLocation) -> Stri

func splitLines(string: String) -> [String] {

let nsstring = NSString(string: string)
let regex = try! NSRegularExpression(pattern: "\r\n|[\n\r]", options: [])

var lines: [String] = []
var location = 0

for match in regex.matches(in: string, options: [], range: NSRange(0..<nsstring.length)) {
let range = NSRange(location..<match.range.location)
lines.append(nsstring.substring(with: range))
location = match.range.location + match.range.length

let nsstring = NSString(string: string)
do {
let regex = try NSRegularExpression(pattern: "\r\n|[\n\r]", options: [])
for match in regex.matches(in: string, options: [], range: NSRange(0..<nsstring.length)) {
let range = NSRange(location..<match.range.location)
lines.append(nsstring.substring(with: range))
location = match.range.location + match.range.length
}
} catch {
// Let lines and location remain unchanged
}

if lines.isEmpty {
Expand Down
17 changes: 10 additions & 7 deletions Sources/GraphQL/Execution/Execute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ public func resolveField(
let fieldAST = fieldASTs[0]
let fieldName = fieldAST.name.value

let fieldDef = getFieldDef(
let fieldDef = try getFieldDef(
schema: exeContext.schema,
parentType: parentType,
fieldName: fieldName
Expand Down Expand Up @@ -796,7 +796,7 @@ func completeValueCatchingError(
result: result
).flatMapError { error -> EventLoopFuture<Any?> in
guard let error = error as? GraphQLError else {
fatalError()
return exeContext.eventLoopGroup.next().makeFailedFuture(error)
}
exeContext.append(error: error)
return exeContext.eventLoopGroup.next().makeSucceededFuture(nil)
Expand All @@ -809,7 +809,7 @@ func completeValueCatchingError(
exeContext.append(error: error)
return exeContext.eventLoopGroup.next().makeSucceededFuture(nil)
} catch {
fatalError()
return exeContext.eventLoopGroup.next().makeFailedFuture(error)
}
}

Expand Down Expand Up @@ -1195,15 +1195,18 @@ func getFieldDef(
schema: GraphQLSchema,
parentType: GraphQLObjectType,
fieldName: String
) -> GraphQLFieldDefinition {
) throws -> GraphQLFieldDefinition {
if fieldName == SchemaMetaFieldDef.name && schema.queryType.name == parentType.name {
return SchemaMetaFieldDef
} else if fieldName == TypeMetaFieldDef.name && schema.queryType.name == parentType.name {
return TypeMetaFieldDef
} else if fieldName == TypeNameMetaFieldDef.name {
return TypeNameMetaFieldDef
}

// we know this field exists because we passed validation before execution
return parentType.fields[fieldName]!

// This field should exist because we passed validation before execution
guard let fieldDefinition = parentType.fields[fieldName] else {
throw GraphQLError(message: "Expected field definition not found: '\(fieldName)' on '\(parentType.name)'")
}
return fieldDefinition
}
7 changes: 5 additions & 2 deletions Sources/GraphQL/GraphQL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ public struct GraphQLResult : Equatable, Codable, CustomStringConvertible {
}

public var description: String {
let data = try! GraphQLJSONEncoder().encode(self)
return String(data: data, encoding: .utf8)!
guard let data = try? GraphQLJSONEncoder().encode(self),
let dataString = String(data: data, encoding: .utf8) else {
return "Unable to encode GraphQLResult"
}
return dataString
}
}

Expand Down
57 changes: 48 additions & 9 deletions Sources/GraphQL/Language/Lexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ func advanceLexer(lexer: Lexer) throws -> Token {

if token.kind != .eof {
repeat {
token.next = try readToken(lexer: lexer, prev: token)
token = token.next!
let nextToken = try readToken(lexer: lexer, prev: token)
token.next = nextToken
token = nextToken
} while token.kind == .comment

lexer.token = token
Expand Down Expand Up @@ -608,7 +609,14 @@ func readString(source: Source, start: Int, line: Int, col: Int, prev: Token) th
positionIndex = body.utf8.index(after: positionIndex)

if code == 92 { // \
value += String(body.utf8[chunkStartIndex..<startIterationIndex])!
guard let chunk = String(body.utf8[chunkStartIndex..<startIterationIndex]) else {
throw syntaxError(
source: source,
position: body.offset(of: positionIndex),
description: "Unable to initialize String from chunk: \(body.utf8[chunkStartIndex..<startIterationIndex])."
)
}
value += chunk
currentCode = body.charCode(at: positionIndex)

if let code = currentCode {
Expand Down Expand Up @@ -643,8 +651,16 @@ func readString(source: Source, start: Int, line: Int, col: Int, prev: Token) th
"\\u\(body.utf8[aIndex...dIndex])."
)
}

value += String(Character(UnicodeScalar(UInt32(charCode))!))

guard let unicodeScalar = UnicodeScalar(UInt32(charCode)) else {
throw syntaxError(
source: source,
position: body.offset(of: positionIndex),
description:
"Invalid unicode character code: \\u\(charCode)."
)
}
value += String(Character(unicodeScalar))

positionIndex = dIndex
default:
Expand All @@ -668,8 +684,15 @@ func readString(source: Source, start: Int, line: Int, col: Int, prev: Token) th
description: "Unterminated string."
)
}

value += String(body.utf8[chunkStartIndex..<positionIndex])!

guard let chunk = String(body.utf8[chunkStartIndex..<positionIndex]) else {
throw syntaxError(
source: source,
position: body.offset(of: positionIndex),
description: "Unable to initialize String from chunk: \(body.utf8[chunkStartIndex..<positionIndex])."
)
}
value += chunk

return Token(
kind: .string,
Expand Down Expand Up @@ -702,7 +725,15 @@ func readBlockString(lexer: Lexer, source: Source, start: Int, line: Int, col: I
body.utf8[body.utf8.index(positionIndex, offsetBy: 1)] == 34,
body.utf8[body.utf8.index(positionIndex, offsetBy: 2)] == 34 {

rawValue += String(body.utf8[chunkStartIndex..<positionIndex])!
guard let chunk = String(body.utf8[chunkStartIndex..<positionIndex]) else {
throw syntaxError(
source: source,
position: body.offset(of: positionIndex),
description: "Unable to initialize String from chunk: \(body.utf8[chunkStartIndex..<positionIndex])."
)
}
rawValue += chunk

return Token(
kind: .blockstring,
start: start,
Expand Down Expand Up @@ -747,7 +778,15 @@ func readBlockString(lexer: Lexer, source: Source, start: Int, line: Int, col: I
body.utf8[body.utf8.index(positionIndex, offsetBy: 2)] == 34,
body.utf8[body.utf8.index(positionIndex, offsetBy: 3)] == 34 {
// escaped triple quote (\""")
rawValue += String(body.utf8[chunkStartIndex..<positionIndex])! + "\"\"\""

guard let chunk = String(body.utf8[chunkStartIndex..<positionIndex]) else {
throw syntaxError(
source: source,
position: body.offset(of: positionIndex),
description: "Unable to initialize String from chunk: \(body.utf8[chunkStartIndex..<positionIndex])."
)
}
rawValue += chunk + "\"\"\""
positionIndex = body.utf8.index(positionIndex, offsetBy: 4)
chunkStartIndex = positionIndex
} else {
Expand Down
16 changes: 9 additions & 7 deletions Sources/GraphQL/Language/Location.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ func getLocation(source: Source, position: Int) -> SourceLocation {
var line = 1
var column = position + 1

let regex = try! NSRegularExpression(pattern: "\r\n|[\n\r]", options: [])

let matches = regex.matches(in: source.body, options: [], range: NSRange(0..<source.body.utf16.count))

for match in matches where match.range.location < position {
line += 1
column = position + 1 - (match.range.location + match.range.length)
do {
let regex = try NSRegularExpression(pattern: "\r\n|[\n\r]", options: [])
let matches = regex.matches(in: source.body, options: [], range: NSRange(0..<source.body.utf16.count))
for match in matches where match.range.location < position {
line += 1
column = position + 1 - (match.range.location + match.range.length)
}
} catch {
// Leave line and position unset if regex fails
}

return SourceLocation(line: line, column: column)
Expand Down
50 changes: 36 additions & 14 deletions Sources/GraphQL/Language/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,12 @@ func parseType(source: Source, noLocation: Bool = false) throws -> Type {
*/
func parseName(lexer: Lexer) throws -> Name {
let token = try expect(lexer: lexer, kind: .name)
guard let value = token.value else {
throw GraphQLError(message: "Expected name token to have value: \(token)")
}
return Name(
loc: loc(lexer: lexer, startToken: token),
value: token.value!
value: value
)
}

Expand Down Expand Up @@ -172,8 +175,10 @@ func parseDefinition(lexer: Lexer) throws -> Definition {
}

if peek(lexer: lexer, kind: .name) {
switch lexer.token.value! {
// Note: subscription is an experimental non-spec addition.
guard let value = lexer.token.value else {
throw GraphQLError(message: "Expected name token to have value: \(lexer.token)")
}
switch value {
case "query", "mutation", "subscription":
return try parseOperationDefinition(lexer: lexer);
case "fragment":
Expand Down Expand Up @@ -236,11 +241,13 @@ func parseOperationDefinition(lexer: Lexer) throws -> OperationDefinition {
*/
func parseOperationType(lexer: Lexer) throws -> OperationType {
let operationToken = try expect(lexer: lexer, kind: .name)
guard let value = operationToken.value else {
throw GraphQLError(message: "Expected name token to have value: \(operationToken)")
}

switch operationToken.value! {
switch value {
case "query": return .query
case "mutation": return .mutation
// Note: subscription is an experimental non-spec addition.
case "subscription": return .subscription
default: throw unexpected(lexer: lexer, atToken: operationToken)
}
Expand Down Expand Up @@ -458,26 +465,35 @@ func parseValueLiteral(lexer: Lexer, isConst: Bool) throws -> Value {
return try parseObject(lexer: lexer, isConst: isConst)
case .int:
try lexer.advance()
guard let value = token.value else {
throw GraphQLError(message: "Expected int token to have value: \(token)")
}
return IntValue(
loc: loc(lexer: lexer, startToken: token),
value: token.value!
value: value
)
case .float:
try lexer.advance()
guard let value = token.value else {
throw GraphQLError(message: "Expected float token to have value: \(token)")
}
return FloatValue(
loc: loc(lexer: lexer, startToken: token),
value: token.value!
value: value
)
case .string, .blockstring:
return try parseStringLiteral(lexer: lexer, startToken: token)
case .name:
if (token.value == "true" || token.value == "false") {
guard let value = token.value else {
throw GraphQLError(message: "Expected name token to have value: \(token)")
}
if (value == "true" || value == "false") {
try lexer.advance()
return BooleanValue(
loc: loc(lexer: lexer, startToken: token),
value: token.value == "true"
value: value == "true"
)
} else if token.value == "null" {
} else if value == "null" {
try lexer.advance()
return NullValue(
loc: loc(lexer: lexer, startToken: token)
Expand All @@ -486,7 +502,7 @@ func parseValueLiteral(lexer: Lexer, isConst: Bool) throws -> Value {
try lexer.advance()
return EnumValue(
loc: loc(lexer: lexer, startToken: token),
value: token.value!
value: value
)
}
case .dollar:
Expand Down Expand Up @@ -564,10 +580,13 @@ func parseObjectField(lexer: Lexer, isConst: Bool) throws -> ObjectField {
*/

func parseStringLiteral(lexer: Lexer, startToken: Token) throws -> StringValue {
try lexer.advance();
try lexer.advance()
guard let value = startToken.value else {
throw GraphQLError(message: "Expected string literal token to have value: \(startToken)")
}
return StringValue(
loc: loc(lexer: lexer, startToken: startToken),
value: startToken.value!,
value: value,
block: startToken.kind == .blockstring
)
}
Expand Down Expand Up @@ -668,7 +687,10 @@ func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition {
: lexer.token

if keywordToken.kind == .name {
switch keywordToken.value! {
guard let value = keywordToken.value else {
throw GraphQLError(message: "Expected keyword token to have value: \(keywordToken)")
}
switch value {
case "schema": return try parseSchemaDefinition(lexer: lexer);
case "scalar": return try parseScalarTypeDefinition(lexer: lexer);
case "type": return try parseObjectTypeDefinition(lexer: lexer);
Expand Down
11 changes: 10 additions & 1 deletion Sources/GraphQL/Map/AnySerialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ public struct AnySerialization {
}

static func object(with map: Any) throws -> NSObject {
return map as! NSObject
guard let result = map as? NSObject else {
throw EncodingError.invalidValue(
map,
EncodingError.Context(
codingPath: [],
debugDescription: "Expected object input to be castable to NSObject: \(type(of: map))"
)
)
}
return result
}
}
18 changes: 16 additions & 2 deletions Sources/GraphQL/Map/Map.swift
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,13 @@ extension Map : Codable {

switch self {
case .undefined:
fatalError("undefined values should have been excluded from encoding")
throw EncodingError.invalidValue(
self,
EncodingError.Context(
codingPath: [],
debugDescription: "undefined values should have been excluded from encoding"
)
)
case .null:
try container.encodeNil()
case let .bool(value):
Expand All @@ -681,7 +687,15 @@ extension Map : Codable {
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in dictionary {
if !value.isUndefined {
let codingKey = _DictionaryCodingKey(stringValue: key)!
guard let codingKey = _DictionaryCodingKey(stringValue: key) else {
throw EncodingError.invalidValue(
self,
EncodingError.Context(
codingPath: [],
debugDescription: "codingKey not found for dictionary key: \(key)"
)
)
}
try container.encode(value, forKey: codingKey)
}
}
Expand Down
Loading