Skip to content

Commit 9bc9f85

Browse files
committed
Fix SLF string scanning and log decoding
1 parent 0b908a0 commit 9bc9f85

9 files changed

Lines changed: 132 additions & 16 deletions

File tree

‎Package.swift‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ let package = Package(
3838
),
3939
.testTarget(
4040
name: "XCLogParserTests",
41-
dependencies: ["XCLogParser"]
41+
dependencies: [
42+
"XCLogParser",
43+
.product(name: "Gzip", package: "GzipSwift")
44+
]
4245
),
4346
]
4447

‎Package@swift-4.2.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ let package = Package(
3131
),
3232
.testTarget(
3333
name: "XCLogParserTests",
34-
dependencies: ["XCLogParser"]
34+
dependencies: ["XCLogParser", "Gzip"]
3535
),
3636
]
3737

‎Package@swift-5.0.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let package = Package(
3434
),
3535
.testTarget(
3636
name: "XCLogParserTests",
37-
dependencies: ["XCLogParser"]
37+
dependencies: ["XCLogParser", "Gzip"]
3838
),
3939
]
4040

‎Sources/XCLogParser/lexer/Scanner.swift‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ final class Scanner {
4444
let end = String.Index(compilerSafeOffset: endOffset, in: self.string)
4545
let result = self.string[start..<end]
4646

47-
guard result.count == count else { return nil }
48-
4947
self.offset += count
5048

5149
return String(result)

‎Sources/XCLogParser/loglocation/LogLoader.swift‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,7 @@ public struct LogLoader {
2626
do {
2727
let data = try Data(contentsOf: url)
2828
let unzipped = try data.gunzipped()
29-
let string: String? = unzipped.withUnsafeBytes { pointer in
30-
guard let charPointer = pointer
31-
.assumingMemoryBound(to: CChar.self)
32-
.baseAddress
33-
else {
34-
return nil
35-
}
36-
37-
return String(cString: charPointer, encoding: .ascii)
38-
}
39-
guard let contents = string else {
29+
guard let contents = String(data: unzipped, encoding: .utf8) else {
4030
throw LogError.readingFile(url.path)
4131
}
4232
return contents

‎Tests/XCLogParserTests/ActivityParserTests.swift‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
// specific language governing permissions and limitations
1818
// under the License.
1919

20+
import Foundation
21+
import Gzip
2022
import XCTest
2123
@testable import XCLogParser
2224

@@ -584,6 +586,30 @@ class ActivityParserTests: XCTestCase {
584586
XCTAssertEqual(10, parser.logVersion)
585587
}
586588

589+
func testParseActivityLogInURLPreservesUTF16LengthStringsAndEmbeddedNullBytes() throws {
590+
let title = "Sources/Bundle+Locali🙂\u{0}zation"
591+
var sectionTokens = IDEActivityLogSectionTokensWithoutAttachments
592+
sectionTokens[2] = Token.string(title)
593+
let tokens = [
594+
Token.int(10),
595+
Token.className("IDECommandLineBuildLog"),
596+
Token.classNameRef("IDECommandLineBuildLog")
597+
] + sectionTokens
598+
let directory = try TestUtils.createRandomTestDir()
599+
defer { try? FileManager.default.removeItem(at: directory) }
600+
let logURL = try writeActivityLog(contents: try slfContents(from: tokens), in: directory)
601+
602+
let activityLog = try parser.parseActivityLogInURL(
603+
logURL,
604+
redacted: false,
605+
withoutBuildSpecificInformation: false
606+
)
607+
608+
XCTAssertEqual(10, activityLog.version)
609+
XCTAssertEqual(title, activityLog.mainSection.title)
610+
XCTAssertEqual(10, parser.logVersion)
611+
}
612+
587613
func testParseDBGConsoleLog() throws {
588614
parser.logVersion = 11
589615
let tokens = DBGConsoleLogTokens
@@ -714,4 +740,42 @@ class ActivityParserTests: XCTestCase {
714740
XCTAssertEqual(metrics.taskCounters["SwiftDriver"]?["x"], 2)
715741
}
716742

743+
private func writeActivityLog(contents: String, in directory: URL) throws -> URL {
744+
let url = directory.appendingPathComponent("test.xcactivitylog")
745+
let data = contents.data(using: .utf8)!
746+
try data.gzipped().write(to: url)
747+
return url
748+
}
749+
750+
private func slfContents(from tokens: [Token]) throws -> String {
751+
var classNames: [String] = []
752+
var contents = "SLF"
753+
for token in tokens {
754+
switch token {
755+
case .int(let value):
756+
contents += "\(value)#"
757+
case .className(let value):
758+
classNames.append(value)
759+
contents += "\(value.utf16.count)%\(value)"
760+
case .classNameRef(let value):
761+
let index = try XCTUnwrap(
762+
classNames.firstIndex(of: value),
763+
"Class name \(value) must be emitted before it is referenced"
764+
)
765+
contents += "\(index + 1)@"
766+
case .string(let value):
767+
contents += "\(value.utf16.count)\"\(value)"
768+
case .double(let value):
769+
contents += "\(String(value.bitPattern.byteSwapped, radix: 16))^"
770+
case .null:
771+
contents += "-"
772+
case .list(let value):
773+
contents += "\(value)("
774+
case .json(let value):
775+
contents += "\(value.utf16.count)*\(value)"
776+
}
777+
}
778+
return contents
779+
}
780+
717781
}

‎Tests/XCLogParserTests/LexerTests.swift‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ class LexerTests: XCTestCase {
5656
XCTAssertEqual(stringToken, Token.string("Xcode.IDEActivityLogDomainType.BuildLog"))
5757
}
5858

59+
func testTokenizeStringUsesUTF16Length() throws {
60+
let value = "Sources/Bundle+Locali🙂"
61+
let logContents = "SLF0#\(value.utf16.count)\"\(value)1#"
62+
63+
let tokens = try lexer.tokenize(contents: logContents, redacted: false, withoutBuildSpecificInformation: false)
64+
65+
XCTAssertEqual(tokens, [.int(0), .string(value), .int(1)])
66+
}
67+
5968
func testTokenizeDouble() throws {
6069
let logContents = "SLF09#21%IDEActivityLogSection1@39\"Xcode.IDEActivityLogDomainType.BuildLog356098f239dfc041^"
6170
let tokens = try lexer.tokenize(contents: logContents, redacted: false, withoutBuildSpecificInformation: false)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) 2019 Spotify AB.
2+
//
3+
// Licensed to the Apache Software Foundation (ASF) under one
4+
// or more contributor license agreements. See the NOTICE file
5+
// distributed with this work for additional information
6+
// regarding copyright ownership. The ASF licenses this file
7+
// to you under the Apache License, Version 2.0 (the
8+
// "License"); you may not use this file except in compliance
9+
// with the License. You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing,
14+
// software distributed under the License is distributed on an
15+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
// KIND, either express or implied. See the License for the
17+
// specific language governing permissions and limitations
18+
// under the License.
19+
20+
import Foundation
21+
import Gzip
22+
import XCTest
23+
@testable import XCLogParser
24+
25+
class LogLoaderTests: XCTestCase {
26+
27+
func testLoadFromURLPreservesEmbeddedNullBytes() throws {
28+
let directory = try TestUtils.createRandomTestDir()
29+
defer { try? FileManager.default.removeItem(at: directory) }
30+
let url = directory.appendingPathComponent("test.xcactivitylog")
31+
let value = "Sources/Bundle+Locali\u{0}zation"
32+
let contents = "SLF0#\(value.utf16.count)\"\(value)1#"
33+
let data = contents.data(using: .utf8)!
34+
try data.gzipped().write(to: url)
35+
36+
let loadedContents = try LogLoader().loadFromURL(url)
37+
38+
XCTAssertEqual(loadedContents, contents)
39+
}
40+
}

‎Tests/XCLogParserTests/XCTestManifests.swift‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extension ActivityParserTests {
77
// to regenerate.
88
static let __allTests__ActivityParserTests = [
99
("testParseActivityLog", testParseActivityLog),
10+
("testParseActivityLogInURLPreservesUTF16LengthStringsAndEmbeddedNullBytes", testParseActivityLogInURLPreservesUTF16LengthStringsAndEmbeddedNullBytes),
1011
("testParseDBGConsoleLog", testParseDBGConsoleLog),
1112
("testParseDVTTextDocumentLocation", testParseDVTTextDocumentLocation),
1213
("testParseIBDocumentMemberLocation", testParseIBDocumentMemberLocation),
@@ -65,10 +66,20 @@ extension LexerTests {
6566
("testTokenizeListNil", testTokenizeListNil),
6667
("testTokenizeString", testTokenizeString),
6768
("testTokenizeStringRedacted", testTokenizeStringRedacted),
69+
("testTokenizeStringUsesUTF16Length", testTokenizeStringUsesUTF16Length),
6870
("testTokenizeStringWithTokenDelimiters", testTokenizeStringWithTokenDelimiters),
6971
]
7072
}
7173

74+
extension LogLoaderTests {
75+
// DO NOT MODIFY: This is autogenerated, use:
76+
// `swift test --generate-linuxmain`
77+
// to regenerate.
78+
static let __allTests__LogLoaderTests = [
79+
("testLoadFromURLPreservesEmbeddedNullBytes", testLoadFromURLPreservesEmbeddedNullBytes),
80+
]
81+
}
82+
7283
extension LogFinderTests {
7384
// DO NOT MODIFY: This is autogenerated, use:
7485
// `swift test --generate-linuxmain`
@@ -138,6 +149,7 @@ public func __allTests() -> [XCTestCaseEntry] {
138149
testCase(IssuesReporterTests.__allTests__IssuesReporterTests),
139150
testCase(LexRedactorTests.__allTests__LexRedactorTests),
140151
testCase(LexerTests.__allTests__LexerTests),
152+
testCase(LogLoaderTests.__allTests__LogLoaderTests),
141153
testCase(LogFinderTests.__allTests__LogFinderTests),
142154
testCase(LogManifestTests.__allTests__LogManifestTests),
143155
testCase(ParserTests.__allTests__ParserTests),

0 commit comments

Comments
 (0)