Skip to content

Commit 8e39711

Browse files
authored
Merge pull request #30338 from ravikandhadai/oslog-crash-in-unreachable-code
2 parents e229468 + 020c828 commit 8e39711

File tree

3 files changed

+114
-10
lines changed

3 files changed

+114
-10
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,9 @@ ERROR(oslog_property_not_constant, none, "'OSLogInterpolation.%0' is not a "
536536
ERROR(oslog_message_alive_after_opts, none, "OSLogMessage instance must not "
537537
"be explicitly created and must be deletable", ())
538538

539+
WARNING(oslog_call_in_unreachable_code, none, "os log call will never be "
540+
"executed and may have undiagnosed errors", ())
541+
539542
ERROR(global_string_pointer_on_non_constant, none, "globalStringTablePointer "
540543
"builtin must used only on string literals", ())
541544

lib/SILOptimizer/Mandatory/OSLogOptimization.cpp

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,17 @@ static SILInstruction *beginOfInterpolation(ApplyInst *oslogInit) {
13291329
worklist.push_back(storeInst);
13301330
candidateStartInstructions.insert(storeInst);
13311331
}
1332+
// Skip other uses of alloc_stack including function calls on the
1333+
// alloc_stack and data dependenceis through them. This is done because
1334+
// all functions using the alloc_stack are expected to be constant evaluated
1335+
// and therefore should only be passed constants or auto closures. These
1336+
// constants must be constructed immediately before the call and would only
1337+
// appear in the SIL after the alloc_stack instruction. This invariant is
1338+
// relied upon here so as to restrict the backward dependency search, which
1339+
// in turn keeps the code that is constant evaluated small.
1340+
// Note that if the client code violates this assumption, it will be
1341+
// diagnosed by this pass (in function detectAndDiagnoseErrors) as it will
1342+
// result in non-constant values for OSLogMessage instance.
13321343
}
13331344

13341345
// Find the first basic block in the control-flow order. Typically, if
@@ -1342,14 +1353,27 @@ static SILInstruction *beginOfInterpolation(ApplyInst *oslogInit) {
13421353
}
13431354

13441355
SILBasicBlock *firstBB = nullptr;
1345-
SILBasicBlock *entryBB = oslogInit->getFunction()->getEntryBlock();
1346-
for (SILBasicBlock *bb: llvm::breadth_first<SILBasicBlock *>(entryBB)) {
1347-
if (candidateBBs.count(bb)) {
1348-
firstBB = bb;
1349-
break;
1356+
if (candidateBBs.size() == 1) {
1357+
firstBB = *candidateBBs.begin();
1358+
} else {
1359+
SILBasicBlock *entryBB = oslogInit->getFunction()->getEntryBlock();
1360+
for (SILBasicBlock *bb : llvm::breadth_first<SILBasicBlock *>(entryBB)) {
1361+
if (candidateBBs.count(bb)) {
1362+
firstBB = bb;
1363+
break;
1364+
}
1365+
}
1366+
if (!firstBB) {
1367+
// This case will be reached only if the log call appears in unreachable
1368+
// code and, for some reason, its data depedencies extend beyond a basic
1369+
// block. This case should generally not happen unless the library
1370+
// implementation of the os log APIs change. It is better to warn in this
1371+
// case, rather than skipping the call silently.
1372+
diagnose(callee->getASTContext(), oslogInit->getLoc().getSourceLoc(),
1373+
diag::oslog_call_in_unreachable_code);
1374+
return nullptr;
13501375
}
13511376
}
1352-
assert(firstBB);
13531377

13541378
// Iterate over the instructions in the firstBB and find the instruction that
13551379
// starts the interpolation.
@@ -1360,7 +1384,7 @@ static SILInstruction *beginOfInterpolation(ApplyInst *oslogInit) {
13601384
break;
13611385
}
13621386
}
1363-
assert(startInst);
1387+
assert(startInst && "could not find beginning of interpolation");
13641388
return startInst;
13651389
}
13661390

@@ -1450,7 +1474,10 @@ class OSLogOptimization : public SILFunctionTransform {
14501474
// iteration.
14511475
for (auto *oslogInit : oslogMessageInits) {
14521476
SILInstruction *interpolationStart = beginOfInterpolation(oslogInit);
1453-
assert(interpolationStart);
1477+
if (!interpolationStart) {
1478+
// The log call is in unreachable code here.
1479+
continue;
1480+
}
14541481
madeChange |= constantFold(interpolationStart, oslogInit, assertConfig);
14551482
}
14561483

test/SILOptimizer/OSLogPrototypeCompileDiagnostics.swift

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
// performs compile-time analysis and optimization of the new os log prototype
66
// APIs. The tests here check whether bad user inputs are diagnosed correctly.
77
// The tests here model the possible invalid inputs to the os log methods.
8-
// TODO: diagnostics will be improved.
8+
// TODO: diagnostics will be improved. globalStringTablePointer builtin error
9+
// must be suppressed.
910

1011
import OSLogPrototype
1112

@@ -43,10 +44,83 @@ if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
4344
let logMessage: OSLogMessage = "Maximum integer value: \(Int.max)"
4445
// expected-error @-1 {{OSLogMessage instance must not be explicitly created and must be deletable}}
4546
if !b {
46-
return;
47+
return
4748
}
4849
h.log(level: .debug, logMessage)
4950
// expected-error @-1 {{globalStringTablePointer builtin must used only on string literals}}
5051
}
52+
53+
func testNoninlinedFormatOptions(h: Logger) {
54+
let formatOption: OSLogIntegerFormatting = .hex(includePrefix: true)
55+
h.debug("Minimum integer value: \(Int.min, format: formatOption)")
56+
// expected-error @-1 {{interpolation arguments like format and privacy options must be constants}}
57+
// expected-error @-2 {{globalStringTablePointer builtin must used only on string literals}}
58+
}
59+
60+
func testNoninlinedFormatOptionsComplex(h: Logger, b: Bool) {
61+
let formatOption: OSLogIntegerFormatting = .hex(includePrefix: true)
62+
if !b {
63+
return
64+
}
65+
h.debug("Minimum integer value: \(Int.min, format: formatOption)")
66+
// expected-error @-1 {{interpolation arguments like format and privacy options must be constants}}
67+
// expected-error @-2 {{globalStringTablePointer builtin must used only on string literals}}
68+
}
5169
}
5270

71+
internal enum Color {
72+
case red
73+
case blue
74+
}
75+
76+
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
77+
78+
// No error is expected here.
79+
func testUnreachableLogCall(h: Logger, c: Color) {
80+
let arg = 10
81+
switch c {
82+
case .red:
83+
return
84+
case .blue:
85+
return
86+
default: // expected-warning {{default will never be executed}}
87+
h.debug("Unreachable log call")
88+
h.info("Unreachable log call with argument \(arg)")
89+
h.log(
90+
"""
91+
Unreachable log call with argument and formatting \
92+
\(arg, align: .right(columns: 10))
93+
""")
94+
}
95+
}
96+
}
97+
98+
// This is an extension used only for testing a diagnostic that doesn't arise
99+
// normally but may be triggered by changes to the library.
100+
extension OSLogInterpolation {
101+
@_transparent
102+
mutating func appendInterpolation(_ c: Color) {
103+
switch c {
104+
case .red:
105+
appendInterpolation(1)
106+
case .blue:
107+
appendInterpolation(0)
108+
}
109+
}
110+
}
111+
112+
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
113+
114+
func testUnreachableLogCallComplex(h: Logger, c: Color) {
115+
switch c {
116+
case .red:
117+
return
118+
case .blue:
119+
return
120+
default: // expected-warning {{default will never be executed}}
121+
h.info("Some call \(c)")
122+
// expected-warning@-1 {{os log call will never be executed and may have undiagnosed errors}}
123+
// expected-error@-2 {{globalStringTablePointer builtin must used only on string literals}}
124+
}
125+
}
126+
}

0 commit comments

Comments
 (0)