Skip to content

Commit dc42259

Browse files
Merge pull request #31120 from ravikandhadai/oslog-diagnostic-improvement-sil
[OSLogOptimization] Improve SIL-level diagnostics generated for the os log APIs.
2 parents 9f2f5ca + 2e4977f commit dc42259

File tree

3 files changed

+134
-75
lines changed

3 files changed

+134
-75
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -580,14 +580,15 @@ NOTE(try_branch_doesnt_yield, none, "missing yield when error is "
580580

581581
// OS log optimization diagnostics.
582582

583-
ERROR(oslog_non_const_interpolation_options, none, "interpolation arguments "
584-
"like format and privacy options must be constants", ())
583+
ERROR(oslog_constant_eval_trap, none, "%0", (StringRef))
585584

586-
ERROR(oslog_const_evaluable_fun_error, none, "evaluation of constant-evaluable "
587-
"function '%0' failed", (StringRef))
585+
ERROR(oslog_too_many_instructions, none, "interpolated expression and arguments "
586+
"are too complex", ())
588587

589-
ERROR(oslog_fail_stop_error, none, "constant evaluation of log call failed "
590-
"with fatal error", ())
588+
ERROR(oslog_invalid_log_message, none, "invalid log message; do not define "
589+
"extensions to types defined in the os module", ())
590+
591+
NOTE(oslog_const_evaluable_fun_error, none, "'%0' failed evaluation", (StringRef))
591592

592593
ERROR(oslog_non_constant_message, none, "'OSLogMessage' instance passed to the "
593594
"log call is not a constant", ())
@@ -598,8 +599,11 @@ ERROR(oslog_non_constant_interpolation, none, "'OSLogInterpolation' instance "
598599
ERROR(oslog_property_not_constant, none, "'OSLogInterpolation.%0' is not a "
599600
"constant", (StringRef))
600601

601-
ERROR(oslog_message_alive_after_opts, none, "OSLogMessage instance must not "
602-
"be explicitly created and must be deletable", ())
602+
ERROR(oslog_message_alive_after_opts, none, "os log string interpolation cannot "
603+
"be used in this context", ())
604+
605+
ERROR(oslog_message_explicitly_created, none, "'OSLogMessage' must be "
606+
" created from a string interpolation or string literal", ())
603607

604608
WARNING(oslog_call_in_unreachable_code, none, "os log call will never be "
605609
"executed and may have undiagnosed errors", ())

lib/SILOptimizer/Mandatory/OSLogOptimization.cpp

Lines changed: 118 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -11,16 +11,16 @@
1111
//===----------------------------------------------------------------------===//
1212
///
1313
/// This pass implements SIL-level optimizations and diagnostics for the
14-
/// os log APIs based on string interpolations. The APIs are implemented
15-
/// in the files: OSLogMessage.swift, OSLog.swift. This pass constant evaluates
16-
/// the log calls along with the auto-generated calls to the custom string
17-
/// interpolation methods, which processes the string interpolation
14+
/// os log APIs based on string interpolations. A mock version of the APIs
15+
/// are available in the private module: OSLogTestHelper. This pass constant
16+
/// evaluates the log calls along with the auto-generated calls to the custom
17+
/// string interpolation methods, which processes the string interpolation
1818
/// passed to the log calls, and folds the constants found during the
19-
/// evaluation. The constants that are folded include the C format string that
20-
/// is constructed by the custom string interpolation methods from the string
21-
/// interpolation, and the size and headers of the byte buffer into which
22-
/// arguments are packed. This pass is closely tied to the implementation of
23-
/// the log APIs.
19+
/// evaluation. The constants that are folded include the printf-style format
20+
/// string that is constructed by the custom string interpolation methods from
21+
/// the string interpolation, and the size and headers of the byte buffer into
22+
/// which arguments are packed. This pass is closely tied to the implementation
23+
/// of the log APIs.
2424
///
2525
/// Pass Dependencies: This pass depends on MandatoryInlining and Mandatory
2626
/// Linking happening before this pass and ConstantPropagation happening after
@@ -51,7 +51,6 @@
5151
/// The errors discovered here may arise from the implementation of the
5252
/// log APIs in the overlay or could be because of wrong usage of the
5353
/// log APIs.
54-
/// TODO: these errors will be diagnosed by a separate, dedicated pass.
5554
///
5655
/// 4. The constant instructions that were found in step 2 are folded by
5756
/// generating SIL code that produces the constants. This also removes
@@ -160,6 +159,8 @@ class StringSILInfo {
160159
this->stringMetatype = stringMetatypeInst->getType();
161160
}
162161

162+
bool isInitialized() { return stringInitIntrinsic != nullptr; }
163+
163164
SILFunction *getStringInitIntrinsic() const {
164165
assert(stringInitIntrinsic);
165166
return stringInitIntrinsic;
@@ -353,59 +354,70 @@ static bool isSILValueFoldable(SILValue value) {
353354
isFoldableArray(value, astContext) || isFoldableClosure(value)));
354355
}
355356

357+
/// Diagnose traps and instruction-limit exceeded errors. These have customized
358+
/// error messages. \returns true if the given error is diagnosed. Otherwise,
359+
/// returns false.
360+
static bool diagnoseSpecialErrors(SILInstruction *unevaluableInst,
361+
SymbolicValue errorInfo) {
362+
SourceLoc sourceLoc = unevaluableInst->getLoc().getSourceLoc();
363+
ASTContext &ctx = unevaluableInst->getFunction()->getASTContext();
364+
UnknownReason unknownReason = errorInfo.getUnknownReason();
365+
366+
if (unknownReason.getKind() == UnknownReason::Trap) {
367+
// We have an assertion failure or fatal error.
368+
const char *message = unknownReason.getTrapMessage();
369+
diagnose(ctx, sourceLoc, diag::oslog_constant_eval_trap,
370+
StringRef(message));
371+
return true;
372+
}
373+
if (unknownReason.getKind() == UnknownReason::TooManyInstructions) {
374+
// This should not normally happen. But could be because of extensions
375+
// defined by users, or very rarely due to unknown bugs in the os_log API
376+
// implementation. These errors may get hidden during testing as it is input
377+
// specific.
378+
diagnose(ctx, sourceLoc, diag::oslog_too_many_instructions);
379+
return true;
380+
}
381+
return false;
382+
}
383+
356384
/// Diagnose failure during evaluation of a call to a constant-evaluable
357-
/// function. Note that all auto-generated 'appendInterpolation' calls are
358-
/// constant evaluable. This function detects and specially handles such
359-
/// functions to present better diagnostic messages.
385+
/// function that is not a specially-handled error. These are errors that
386+
/// happen within 'appendInterpolation' calls, which must be constant
387+
/// evaluable by the definition of APIs.
360388
static void diagnoseErrorInConstantEvaluableFunction(ApplyInst *call,
361389
SymbolicValue errorInfo) {
362-
SILNode *unknownNode = errorInfo.getUnknownNode();
363-
UnknownReason unknownReason = errorInfo.getUnknownReason();
364-
365390
SILFunction *callee = call->getCalleeFunction();
366391
assert(callee);
367392
SILLocation loc = call->getLoc();
368393
SourceLoc sourceLoc = loc.getSourceLoc();
369394
ASTContext &astContext = callee->getASTContext();
370395

396+
// Here, we know very little about what actually went wrong. It could be due
397+
// to bugs in the library implementation or in extensions created by users.
398+
// Emit a general message here and some diagnostic notes.
371399
std::string demangledCalleeName = Demangle::demangleSymbolAsString(
372400
callee->getName(),
373401
Demangle::DemangleOptions::SimplifiedUIDemangleOptions());
374-
375-
// If an 'appendInterpolation' evaluation failed, it is probably due to
376-
// invalid privacy or format specifiers. These are the only possible errors
377-
// that the users of the log API could make. The rest are for library authors
378-
// or users who extend the log APIs.
379-
if (unknownReason.getKind() == UnknownReason::CallArgumentUnknown &&
380-
dyn_cast<ApplyInst>(unknownNode) == call) {
381-
if (StringRef(demangledCalleeName)
382-
.contains(astContext.Id_appendInterpolation.str())) {
383-
// TODO: extract and report the label of the parameter that is not a
384-
// constant.
385-
diagnose(astContext, sourceLoc,
386-
diag::oslog_non_const_interpolation_options);
387-
return;
388-
}
389-
}
402+
diagnose(astContext, sourceLoc, diag::oslog_invalid_log_message);
390403
diagnose(astContext, sourceLoc, diag::oslog_const_evaluable_fun_error,
391404
demangledCalleeName);
392405
errorInfo.emitUnknownDiagnosticNotes(loc);
393-
return;
394406
}
395407

396408
/// Detect and emit diagnostics for errors found during evaluation. Errors
397-
/// can happen due to incorrect implementation of the os log API in the
398-
/// overlay or due to incorrect use of the os log API.
399-
/// TODO: errors due to incorrect use of the API should be diagnosed by a
400-
/// dedicated diagnostics pass that will happen before this optimization starts.
409+
/// can happen due to bugs in the implementation of the os log API, or
410+
/// due to incorrect use of the os log API.
401411
static bool detectAndDiagnoseErrors(SymbolicValue errorInfo,
402412
SILInstruction *unevaluableInst) {
413+
// TODO: fix the globalStrinTableBuiltin error after emitting diagnostics.
403414
SILFunction *parentFun = unevaluableInst->getFunction();
404415
ASTContext &astContext = parentFun->getASTContext();
405416

406-
// If evaluation of any other constant_evaluable function call fails, point
407-
// to that failed function along with a reason: such as that a parameter is
408-
// non-constant parameter or that body is not constant evaluable.
417+
if (diagnoseSpecialErrors(unevaluableInst, errorInfo))
418+
return true;
419+
// If evaluation of any constant_evaluable function call fails, point
420+
// to that failed function along with a reason.
409421
ApplyInst *call = dyn_cast<ApplyInst>(unevaluableInst);
410422
if (call) {
411423
SILFunction *callee = call->getCalleeFunction();
@@ -414,17 +426,14 @@ static bool detectAndDiagnoseErrors(SymbolicValue errorInfo,
414426
return true; // abort evaluation.
415427
}
416428
}
417-
418-
// Every other error must happen in the body of the os_log function which
419-
// is inlined in the 'parentFun' before this pass. In this case, if we have a
429+
// Every other error must happen in the top-level code containing the string
430+
// interpolation construction and body of the log methods. If we have a
420431
// fail-stop error, point to the error and abort evaluation. Otherwise, just
421432
// ignore the error and continue evaluation as this error might not affect the
422433
// constant value of the OSLogMessage instance.
423434
if (isFailStopError(errorInfo)) {
424-
assert(errorInfo.getKind() == SymbolicValue::Unknown);
425435
SILLocation loc = unevaluableInst->getLoc();
426-
SourceLoc sourceLoc = loc.getSourceLoc();
427-
diagnose(astContext, sourceLoc, diag::oslog_fail_stop_error);
436+
diagnose(astContext, loc.getSourceLoc(), diag::oslog_invalid_log_message);
428437
errorInfo.emitUnknownDiagnosticNotes(loc);
429438
return true;
430439
}
@@ -444,7 +453,8 @@ static Optional<SymbolicValue> collectConstants(FoldState &foldState) {
444453
auto &endInstructions = foldState.endInstructions;
445454

446455
// The loop will break when it sees a return instruction or an instruction in
447-
// endInstructions.
456+
// endInstructions or when the next instruction to evaluate cannot be
457+
// determined (which may happend due to non-constant branches).
448458
while (true) {
449459
SILInstruction *currInst = &(*currI);
450460
if (endInstructions.count(currInst))
@@ -1009,7 +1019,7 @@ static void substituteConstants(FoldState &foldState) {
10091019

10101020
/// Check whether OSLogMessage and OSLogInterpolation instances and all their
10111021
/// stored properties are constants. If not, it indicates errors that are due to
1012-
/// incorrect implementation of OSLogMessage either in the overlay or in the
1022+
/// incorrect implementation of OSLogMessage either in the os module or in the
10131023
/// extensions created by users. Detect and emit diagnostics for such errors.
10141024
/// The diagnostics here are for os log library authors.
10151025
static bool checkOSLogMessageIsConstant(SingleValueInstruction *osLogMessage,
@@ -1058,6 +1068,8 @@ static bool checkOSLogMessageIsConstant(SingleValueInstruction *osLogMessage,
10581068
osLogInterpolationValue.getAggregateMembers();
10591069
auto propValueI = propertyValues.begin();
10601070
bool errorDetected = false;
1071+
// Also, track if there is a string-valued property.
1072+
bool hasStringValuedProperty = false;
10611073

10621074
for (auto *propDecl : propertyDecls) {
10631075
SymbolicValue propertyValue = *(propValueI++);
@@ -1067,6 +1079,15 @@ static bool checkOSLogMessageIsConstant(SingleValueInstruction *osLogMessage,
10671079
errorDetected = true;
10681080
break;
10691081
}
1082+
hasStringValuedProperty = propertyValue.getKind() == SymbolicValue::String;
1083+
}
1084+
1085+
// If we have a string-valued property but don't have the stringInfo
1086+
// initialized here, it means the initializer OSLogInterpolation is explicitly
1087+
// called, which should be diagnosed.
1088+
if (hasStringValuedProperty && !foldState.stringInfo.isInitialized()) {
1089+
diagnose(astContext, sourceLoc, diag::oslog_message_explicitly_created);
1090+
errorDetected = true;
10701091
}
10711092
return errorDetected;
10721093
}
@@ -1185,9 +1206,7 @@ static void deleteInstructionWithUsersAndFixLifetimes(
11851206

11861207
/// Try to dead-code eliminate the OSLogMessage instance \c oslogMessage passed
11871208
/// to the os log call and clean up its dependencies. If the instance cannot be
1188-
/// eliminated, it implies that either the instance is not auto-generated or the
1189-
/// implementation of the os log overlay is incorrect. Therefore emit
1190-
/// diagnostics in such cases.
1209+
/// eliminated, emit diagnostics.
11911210
static void tryEliminateOSLogMessage(SingleValueInstruction *oslogMessage) {
11921211
InstructionDeleter deleter;
11931212
// List of instructions that are possibly dead.
@@ -1214,17 +1233,26 @@ static void tryEliminateOSLogMessage(SingleValueInstruction *oslogMessage) {
12141233
});
12151234
}
12161235
deleter.cleanUpDeadInstructions();
1217-
// If the OSLogMessage instance is not deleted, the overlay implementation
1218-
// (or its extensions by users) is incorrect.
1236+
// If the OSLogMessage instance is not deleted, either we couldn't see the
1237+
// body of the log call or there is a bug in the library implementation.
1238+
// Assuming that the library implementation is correct, it means that either
1239+
// OSLogMessage is used in a context where it is not supposed to be used, or
1240+
// we somehow saw a conditional branch with a non-constant argument before
1241+
// completing evaluation (this can happen with the os_log(_:log:type)
1242+
// overload, when log or type is an optional unwrapping). Report an error
1243+
// that covers both contexts. (Note that it is very hard to distinguish these
1244+
// error cases in the current state.)
12191245
if (!deletedInstructions.count(oslogMessage)) {
12201246
SILFunction *fun = oslogMessage->getFunction();
12211247
diagnose(fun->getASTContext(), oslogMessage->getLoc().getSourceLoc(),
12221248
diag::oslog_message_alive_after_opts);
12231249
}
12241250
}
12251251

1226-
/// Constant evaluate instructions starting from 'start' and fold the uses
1227-
/// of the value 'oslogMessage'. Stop when oslogMessageValue is released.
1252+
/// Constant evaluate instructions starting from \p start and fold the uses
1253+
/// of the SIL value \p oslogMessage.
1254+
/// \returns true if the body of the function containing \p oslogMessage is
1255+
/// modified. Returns false otherwise.
12281256
static bool constantFold(SILInstruction *start,
12291257
SingleValueInstruction *oslogMessage,
12301258
unsigned assertConfig) {
@@ -1394,6 +1422,30 @@ static SILInstruction *beginOfInterpolation(ApplyInst *oslogInit) {
13941422
return startInst;
13951423
}
13961424

1425+
/// Replace every _globalStringTablePointer builtin in the transitive users of
1426+
/// oslogMessage with an empty string literal. This would suppress the errors
1427+
/// emitted by a later pass on _globalStringTablePointerBuiltins. This utility
1428+
/// shoud be called only when this pass emits diagnostics.
1429+
static void
1430+
suppressGlobalStringTablePointerError(SingleValueInstruction *oslogMessage) {
1431+
SmallVector<SILInstruction *, 8> users;
1432+
getTransitiveUsers(oslogMessage, users);
1433+
1434+
for (SILInstruction *user : users) {
1435+
BuiltinInst *bi = dyn_cast<BuiltinInst>(user);
1436+
if (!bi ||
1437+
bi->getBuiltinInfo().ID != BuiltinValueKind::GlobalStringTablePointer)
1438+
continue;
1439+
// Replace this builtin by a string_literal instruction for an empty string.
1440+
SILBuilderWithScope builder(bi);
1441+
StringLiteralInst *stringLiteral = builder.createStringLiteral(
1442+
bi->getLoc(), StringRef(""), StringLiteralInst::Encoding::UTF8);
1443+
bi->replaceAllUsesWith(stringLiteral);
1444+
// Here, the bulitin instruction is dead, so clean it up.
1445+
eliminateDeadInstruction(bi);
1446+
}
1447+
}
1448+
13971449
/// If the SILInstruction is an initialization of OSLogMessage, return the
13981450
/// initialization call as an ApplyInst. Otherwise, return nullptr.
13991451
static ApplyInst *getAsOSLogMessageInit(SILInstruction *inst) {
@@ -1484,10 +1536,17 @@ class OSLogOptimization : public SILFunctionTransform {
14841536
// The log call is in unreachable code here.
14851537
continue;
14861538
}
1487-
madeChange |= constantFold(interpolationStart, oslogInit, assertConfig);
1539+
bool bodyModified =
1540+
constantFold(interpolationStart, oslogInit, assertConfig);
1541+
// If body was not modified, it implies that an error was diagnosed.
1542+
// However, this will also trigger a diagnostics later on since
1543+
// _globalStringTablePointerBuiltin would not be passed a string literal.
1544+
// Suppress this error by synthesizing a dummy string literal for the
1545+
// builtin.
1546+
if (!bodyModified)
1547+
suppressGlobalStringTablePointerError(oslogInit);
1548+
madeChange = true;
14881549
}
1489-
1490-
// TODO: Can we be more conservative here with our invalidation?
14911550
if (madeChange) {
14921551
invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody);
14931552
}

test/SILOptimizer/OSLogCompilerDiagnosticsTest.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,12 @@
66
// performs compile-time analysis and optimization of the new os log APIs.
77
// Note that many usage errors are caught by the Sema check: ConstantnessSemaDiagnostics.
88
// The tests here check only those diagnostics that are enforced at the SIL level.
9-
// TODO: diagnostics must be improved, and globalStringTablePointer builtin error must be
10-
// suppressed.
119

1210
import OSLogTestHelper
1311

1412
func testNonDecimalFormatOptionOnIntegers() {
1513
_osLogTestHelper("Minimum integer value: \(Int.min, format: .hex)")
16-
// expected-error @-1 {{evaluation of constant-evaluable function 'OSLogInterpolation.appendInterpolation(_:format:align:privacy:)' failed}}
17-
// expected-note @-2 {{Fatal error: Signed integers must be formatted using .decimal}}
18-
// expected-error @-3 {{globalStringTablePointer builtin must used only on string literals}}
14+
// expected-error @-1 {{Fatal error: Signed integers must be formatted using .decimal}}
1915
}
2016

2117
// Extending OSLogInterpolation (without the constant_evaluable attribute) would be an
@@ -31,9 +27,9 @@ extension OSLogInterpolation {
3127

3228
func testOSLogInterpolationExtension(a: A) {
3329
_osLogTestHelper("Error at line: \(a: a)")
34-
// expected-error @-1 {{evaluation of constant-evaluable function 'OSLogInterpolation.appendLiteral(_:)' failed}}
35-
// expected-note @-2 {{value mutable by an unevaluated instruction is not a constant}}
36-
// expected-error @-3 {{globalStringTablePointer builtin must used only on string literals}}
30+
// expected-error @-1 {{invalid log message; do not define extensions to types defined in the os module}}
31+
// expected-note @-2 {{'OSLogInterpolation.appendLiteral(_:)' failed evaluation}}
32+
// expected-note @-3 {{value mutable by an unevaluated instruction is not a constant}}
3733
}
3834

3935
internal enum Color {

0 commit comments

Comments
 (0)