2
2
//
3
3
// This source file is part of the Swift.org open source project
4
4
//
5
- // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
5
+ // Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6
6
// Licensed under Apache License v2.0 with Runtime Library Exception
7
7
//
8
8
// See https://swift.org/LICENSE.txt for license information
11
11
// ===----------------------------------------------------------------------===//
12
12
// /
13
13
// / 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
18
18
// / 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.
24
24
// /
25
25
// / Pass Dependencies: This pass depends on MandatoryInlining and Mandatory
26
26
// / Linking happening before this pass and ConstantPropagation happening after
51
51
// / The errors discovered here may arise from the implementation of the
52
52
// / log APIs in the overlay or could be because of wrong usage of the
53
53
// / log APIs.
54
- // / TODO: these errors will be diagnosed by a separate, dedicated pass.
55
54
// /
56
55
// / 4. The constant instructions that were found in step 2 are folded by
57
56
// / generating SIL code that produces the constants. This also removes
@@ -160,6 +159,8 @@ class StringSILInfo {
160
159
this ->stringMetatype = stringMetatypeInst->getType ();
161
160
}
162
161
162
+ bool isInitialized () { return stringInitIntrinsic != nullptr ; }
163
+
163
164
SILFunction *getStringInitIntrinsic () const {
164
165
assert (stringInitIntrinsic);
165
166
return stringInitIntrinsic;
@@ -353,59 +354,70 @@ static bool isSILValueFoldable(SILValue value) {
353
354
isFoldableArray (value, astContext) || isFoldableClosure (value)));
354
355
}
355
356
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
+
356
384
// / 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 .
360
388
static void diagnoseErrorInConstantEvaluableFunction (ApplyInst *call,
361
389
SymbolicValue errorInfo) {
362
- SILNode *unknownNode = errorInfo.getUnknownNode ();
363
- UnknownReason unknownReason = errorInfo.getUnknownReason ();
364
-
365
390
SILFunction *callee = call->getCalleeFunction ();
366
391
assert (callee);
367
392
SILLocation loc = call->getLoc ();
368
393
SourceLoc sourceLoc = loc.getSourceLoc ();
369
394
ASTContext &astContext = callee->getASTContext ();
370
395
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.
371
399
std::string demangledCalleeName = Demangle::demangleSymbolAsString (
372
400
callee->getName (),
373
401
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);
390
403
diagnose (astContext, sourceLoc, diag::oslog_const_evaluable_fun_error,
391
404
demangledCalleeName);
392
405
errorInfo.emitUnknownDiagnosticNotes (loc);
393
- return ;
394
406
}
395
407
396
408
// / 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.
401
411
static bool detectAndDiagnoseErrors (SymbolicValue errorInfo,
402
412
SILInstruction *unevaluableInst) {
413
+ // TODO: fix the globalStrinTableBuiltin error after emitting diagnostics.
403
414
SILFunction *parentFun = unevaluableInst->getFunction ();
404
415
ASTContext &astContext = parentFun->getASTContext ();
405
416
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.
409
421
ApplyInst *call = dyn_cast<ApplyInst>(unevaluableInst);
410
422
if (call) {
411
423
SILFunction *callee = call->getCalleeFunction ();
@@ -414,17 +426,14 @@ static bool detectAndDiagnoseErrors(SymbolicValue errorInfo,
414
426
return true ; // abort evaluation.
415
427
}
416
428
}
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
420
431
// fail-stop error, point to the error and abort evaluation. Otherwise, just
421
432
// ignore the error and continue evaluation as this error might not affect the
422
433
// constant value of the OSLogMessage instance.
423
434
if (isFailStopError (errorInfo)) {
424
- assert (errorInfo.getKind () == SymbolicValue::Unknown);
425
435
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);
428
437
errorInfo.emitUnknownDiagnosticNotes (loc);
429
438
return true ;
430
439
}
@@ -444,7 +453,8 @@ static Optional<SymbolicValue> collectConstants(FoldState &foldState) {
444
453
auto &endInstructions = foldState.endInstructions ;
445
454
446
455
// 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).
448
458
while (true ) {
449
459
SILInstruction *currInst = &(*currI);
450
460
if (endInstructions.count (currInst))
@@ -1009,7 +1019,7 @@ static void substituteConstants(FoldState &foldState) {
1009
1019
1010
1020
// / Check whether OSLogMessage and OSLogInterpolation instances and all their
1011
1021
// / 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
1013
1023
// / extensions created by users. Detect and emit diagnostics for such errors.
1014
1024
// / The diagnostics here are for os log library authors.
1015
1025
static bool checkOSLogMessageIsConstant (SingleValueInstruction *osLogMessage,
@@ -1058,6 +1068,8 @@ static bool checkOSLogMessageIsConstant(SingleValueInstruction *osLogMessage,
1058
1068
osLogInterpolationValue.getAggregateMembers ();
1059
1069
auto propValueI = propertyValues.begin ();
1060
1070
bool errorDetected = false ;
1071
+ // Also, track if there is a string-valued property.
1072
+ bool hasStringValuedProperty = false ;
1061
1073
1062
1074
for (auto *propDecl : propertyDecls) {
1063
1075
SymbolicValue propertyValue = *(propValueI++);
@@ -1067,6 +1079,15 @@ static bool checkOSLogMessageIsConstant(SingleValueInstruction *osLogMessage,
1067
1079
errorDetected = true ;
1068
1080
break ;
1069
1081
}
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 ;
1070
1091
}
1071
1092
return errorDetected;
1072
1093
}
@@ -1185,9 +1206,7 @@ static void deleteInstructionWithUsersAndFixLifetimes(
1185
1206
1186
1207
// / Try to dead-code eliminate the OSLogMessage instance \c oslogMessage passed
1187
1208
// / 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.
1191
1210
static void tryEliminateOSLogMessage (SingleValueInstruction *oslogMessage) {
1192
1211
InstructionDeleter deleter;
1193
1212
// List of instructions that are possibly dead.
@@ -1214,17 +1233,26 @@ static void tryEliminateOSLogMessage(SingleValueInstruction *oslogMessage) {
1214
1233
});
1215
1234
}
1216
1235
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.)
1219
1245
if (!deletedInstructions.count (oslogMessage)) {
1220
1246
SILFunction *fun = oslogMessage->getFunction ();
1221
1247
diagnose (fun->getASTContext (), oslogMessage->getLoc ().getSourceLoc (),
1222
1248
diag::oslog_message_alive_after_opts);
1223
1249
}
1224
1250
}
1225
1251
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.
1228
1256
static bool constantFold (SILInstruction *start,
1229
1257
SingleValueInstruction *oslogMessage,
1230
1258
unsigned assertConfig) {
@@ -1394,6 +1422,30 @@ static SILInstruction *beginOfInterpolation(ApplyInst *oslogInit) {
1394
1422
return startInst;
1395
1423
}
1396
1424
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
+
1397
1449
// / If the SILInstruction is an initialization of OSLogMessage, return the
1398
1450
// / initialization call as an ApplyInst. Otherwise, return nullptr.
1399
1451
static ApplyInst *getAsOSLogMessageInit (SILInstruction *inst) {
@@ -1484,10 +1536,17 @@ class OSLogOptimization : public SILFunctionTransform {
1484
1536
// The log call is in unreachable code here.
1485
1537
continue ;
1486
1538
}
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 ;
1488
1549
}
1489
-
1490
- // TODO: Can we be more conservative here with our invalidation?
1491
1550
if (madeChange) {
1492
1551
invalidateAnalysis (SILAnalysis::InvalidationKind::FunctionBody);
1493
1552
}
0 commit comments