| //===-- InstrumentorUtils.cpp - Highly configurable instrumentation pass --===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/Transforms/IPO/InstrumentorUtils.h" |
| #include "llvm/Transforms/IPO/Instrumentor.h" |
| |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/IR/DiagnosticInfo.h" |
| |
| using namespace llvm; |
| using namespace llvm::instrumentor; |
| |
| namespace { |
| enum PropertyType { INT, STRING, POINTER, UNKNOWN }; |
| |
| /// Simple filter expression evaluator for instrumentation opportunities. |
| /// Supports integer comparisons (==, !=, <, >, <=, >=), string comparisons |
| /// (==, !=), pointer comparisons (==, !=) against null, string prefix checks |
| /// (startswith), and logical operators (&&, ||). |
| class FilterEvaluator { |
| StringRef Expr; |
| DenseMap<StringRef, int64_t> &IntPropertyValues; |
| DenseMap<StringRef, StringRef> &StringPropertyValues; |
| DenseMap<StringRef, Value *> &PointerPropertyValues; |
| DenseMap<StringRef, PropertyType> &DynamicProperties; |
| size_t Pos = 0; |
| |
| public: |
| FilterEvaluator(StringRef Expr, |
| DenseMap<StringRef, int64_t> &IntPropertyValues, |
| DenseMap<StringRef, StringRef> &StringPropertyValues, |
| DenseMap<StringRef, Value *> &PointerPropertyValues, |
| DenseMap<StringRef, PropertyType> &DynamicProperties) |
| : Expr(Expr), IntPropertyValues(IntPropertyValues), |
| StringPropertyValues(StringPropertyValues), |
| PointerPropertyValues(PointerPropertyValues), |
| DynamicProperties(DynamicProperties) {} |
| |
| Expected<bool> evaluate() { |
| if (Expr.empty()) |
| return true; |
| |
| Expected<bool> Result = parseOrExpr(); |
| |
| // Check if we consumed the entire expression. |
| skipWhitespace(); |
| if (Pos < Expr.size() && Result) |
| return createStringError( |
| "unexpected characters at position " + std::to_string(Pos) + ": '" + |
| Expr.substr(Pos, std::min<size_t>(10, Expr.size() - Pos)).str() + |
| "'"); |
| |
| return Result; |
| } |
| |
| private: |
| void skipWhitespace() { |
| while (Pos < Expr.size() && std::isspace(Expr[Pos])) |
| ++Pos; |
| } |
| |
| Expected<bool> parseOrExpr() { |
| Expected<bool> Result = parseAndExpr(); |
| while (Result) { |
| skipWhitespace(); |
| if (Pos + 1 < Expr.size() && Expr[Pos] == '|' && Expr[Pos + 1] == '|') { |
| Pos += 2; |
| Expected<bool> NextResult = parseAndExpr(); |
| if (!NextResult) |
| return NextResult; |
| *Result |= *NextResult; |
| } else { |
| break; |
| } |
| } |
| return Result; |
| } |
| |
| Expected<bool> parseAndExpr() { |
| Expected<bool> Result = parsePrimary(); |
| while (Result) { |
| skipWhitespace(); |
| if (Pos + 1 < Expr.size() && Expr[Pos] == '&' && Expr[Pos + 1] == '&') { |
| Pos += 2; |
| Expected<bool> NextResult = parsePrimary(); |
| if (!NextResult) |
| return NextResult; |
| *Result &= *NextResult; |
| } else { |
| break; |
| } |
| } |
| return Result; |
| } |
| |
| Expected<bool> parsePrimary() { |
| skipWhitespace(); |
| |
| // Check for opening parenthesis. |
| if (Pos < Expr.size() && Expr[Pos] == '(') { |
| ++Pos; // Skip '(' |
| Expected<bool> Result = parseOrExpr(); |
| |
| skipWhitespace(); |
| if (Result && (Pos >= Expr.size() || Expr[Pos] != ')')) |
| return createStringError("expected ')' at position " + |
| std::to_string(Pos)); |
| |
| // Skip ')'. |
| ++Pos; |
| return Result; |
| } |
| |
| // Otherwise parse a comparison. |
| return parseComparison(); |
| } |
| |
| // Parse a quoted string literal. |
| Expected<StringRef> parseStringLiteral() { |
| skipWhitespace(); |
| if (Pos >= Expr.size() || Expr[Pos] != '"') |
| return createStringError("expected string literal at position " + |
| std::to_string(Pos)); |
| |
| // Skip opening quote. |
| ++Pos; |
| size_t Start = Pos; |
| while (Pos < Expr.size() && Expr[Pos] != '"') |
| ++Pos; |
| |
| if (Pos >= Expr.size()) |
| return createStringError("unclosed string literal starting at position " + |
| std::to_string(Start - 1)); |
| |
| StringRef Result = Expr.slice(Start, Pos); |
| // Skip closing quote. |
| ++Pos; |
| return Result; |
| } |
| |
| Expected<bool> parseComparison() { |
| skipWhitespace(); |
| |
| // Parse left-hand side (property name). |
| size_t Start = Pos; |
| while (Pos < Expr.size() && (std::isalnum(Expr[Pos]) || Expr[Pos] == '_')) |
| ++Pos; |
| |
| StringRef PropName = Expr.slice(Start, Pos); |
| if (PropName.empty()) |
| return createStringError("expected property name at position " + |
| std::to_string(Pos)); |
| |
| skipWhitespace(); |
| |
| // Check for .startswith() method call. |
| if (Pos < Expr.size() && Expr[Pos] == '.') { |
| ++Pos; |
| skipWhitespace(); |
| |
| // Parse method name. |
| Start = Pos; |
| while (Pos < Expr.size() && std::isalpha(Expr[Pos])) |
| ++Pos; |
| |
| StringRef MethodName = Expr.slice(Start, Pos); |
| skipWhitespace(); |
| |
| if (MethodName == "startswith") { |
| // Parse (. |
| if (Pos >= Expr.size() || Expr[Pos] != '(') |
| return createStringError( |
| "expected '(' after 'startswith' at position " + |
| std::to_string(Pos)); |
| |
| ++Pos; |
| |
| // Parse string argument. |
| auto Prefix = parseStringLiteral(); |
| if (!Prefix) |
| return Prefix.takeError(); |
| |
| skipWhitespace(); |
| |
| // Parse ) |
| if (Pos >= Expr.size() || Expr[Pos] != ')') |
| return createStringError( |
| "expected ')' to close 'startswith' call at position " + |
| std::to_string(Pos)); |
| |
| ++Pos; |
| |
| // Evaluate startswith. |
| auto StrIt = StringPropertyValues.find(PropName); |
| if (StrIt != StringPropertyValues.end()) |
| return StrIt->second.starts_with(*Prefix); |
| |
| // If this is a dynamic string property, assume the filter passes. |
| if (DynamicProperties.lookup_or(PropName, UNKNOWN) == STRING) |
| return true; |
| |
| return createStringError( |
| "startswith is only valid on string properties not '" + PropName + |
| "'"); |
| } |
| |
| return createStringError("unknown method '" + MethodName.str() + |
| "' on property '" + PropName.str() + "'"); |
| } |
| |
| // Check if this is an integer property. |
| auto IntIt = IntPropertyValues.find(PropName); |
| if (IntIt != IntPropertyValues.end()) { |
| int64_t LHS = IntIt->second; |
| |
| // Parse operator. |
| enum OpKind { EQ, NE, LT, GT, LE, GE } Op; |
| if (Pos < Expr.size()) { |
| if (Expr[Pos] == '=' && Pos + 1 < Expr.size() && Expr[Pos + 1] == '=') { |
| Op = EQ; |
| Pos += 2; |
| } else if (Expr[Pos] == '!' && Pos + 1 < Expr.size() && |
| Expr[Pos + 1] == '=') { |
| Op = NE; |
| Pos += 2; |
| } else if (Expr[Pos] == '<' && Pos + 1 < Expr.size() && |
| Expr[Pos + 1] == '=') { |
| Op = LE; |
| Pos += 2; |
| } else if (Expr[Pos] == '>' && Pos + 1 < Expr.size() && |
| Expr[Pos + 1] == '=') { |
| Op = GE; |
| Pos += 2; |
| } else if (Expr[Pos] == '<') { |
| Op = LT; |
| Pos += 1; |
| } else if (Expr[Pos] == '>') { |
| Op = GT; |
| Pos += 1; |
| } else { |
| return createStringError("expected comparison operator (==, !=, <, " |
| ">, <=, >=) at position " + |
| std::to_string(Pos)); |
| } |
| } else { |
| return createStringError( |
| "expected comparison operator after property '" + PropName.str() + |
| "'"); |
| } |
| |
| skipWhitespace(); |
| |
| // Parse right-hand side (constant value). |
| Start = Pos; |
| bool Negative = false; |
| if (Pos < Expr.size() && Expr[Pos] == '-') { |
| Negative = true; |
| ++Pos; |
| } |
| |
| size_t DigitStart = Pos; |
| while (Pos < Expr.size() && std::isdigit(Expr[Pos])) |
| ++Pos; |
| |
| if (Pos == DigitStart) |
| return createStringError("expected integer value at position " + |
| std::to_string(Pos)); |
| |
| StringRef ValueStr = Expr.slice(Start, Pos); |
| int64_t RHS = 0; |
| if (ValueStr.getAsInteger(10, RHS)) |
| return createStringError("invalid integer value '" + ValueStr.str() + |
| "'"); |
| |
| if (Negative) |
| RHS = -RHS; |
| |
| // Evaluate comparison. |
| switch (Op) { |
| case EQ: |
| return LHS == RHS; |
| case NE: |
| return LHS != RHS; |
| case LT: |
| return LHS < RHS; |
| case GT: |
| return LHS > RHS; |
| case LE: |
| return LHS <= RHS; |
| case GE: |
| return LHS >= RHS; |
| } |
| return true; |
| } |
| |
| // Check if this is a string property. |
| auto StrIt = StringPropertyValues.find(PropName); |
| if (StrIt != StringPropertyValues.end()) { |
| StringRef LHS = StrIt->second; |
| |
| // Parse operator (only == and != for strings). |
| enum OpKind { EQ, NE } Op; |
| if (Pos < Expr.size()) { |
| if (Expr[Pos] == '=' && Pos + 1 < Expr.size() && Expr[Pos + 1] == '=') { |
| Op = EQ; |
| Pos += 2; |
| } else if (Expr[Pos] == '!' && Pos + 1 < Expr.size() && |
| Expr[Pos + 1] == '=') { |
| Op = NE; |
| Pos += 2; |
| } else { |
| return createStringError("string property '" + PropName.str() + |
| "' only supports == and != operators"); |
| } |
| } else { |
| return createStringError( |
| "expected comparison operator after string property '" + |
| PropName.str() + "'"); |
| } |
| |
| skipWhitespace(); |
| |
| // Parse right-hand side (string literal). |
| auto RHS = parseStringLiteral(); |
| if (!RHS) |
| return RHS.takeError(); |
| |
| // Evaluate comparison. |
| switch (Op) { |
| case EQ: |
| return LHS == *RHS; |
| case NE: |
| return LHS != *RHS; |
| } |
| return true; |
| } |
| |
| // Check if this is a pointer property. |
| auto PtrIt = PointerPropertyValues.find(PropName); |
| if (PtrIt != PointerPropertyValues.end()) { |
| Value *LHS = PtrIt->second; |
| |
| // Parse operator (only == and != for pointers). |
| enum OpKind { EQ, NE } Op; |
| if (Pos < Expr.size()) { |
| if (Expr[Pos] == '=' && Pos + 1 < Expr.size() && Expr[Pos + 1] == '=') { |
| Op = EQ; |
| Pos += 2; |
| } else if (Expr[Pos] == '!' && Pos + 1 < Expr.size() && |
| Expr[Pos + 1] == '=') { |
| Op = NE; |
| Pos += 2; |
| } else { |
| return createStringError("pointer property '" + PropName.str() + |
| "' only supports == and != operators"); |
| } |
| } else { |
| return createStringError( |
| "expected comparison operator after pointer property '" + |
| PropName.str() + "'"); |
| } |
| |
| skipWhitespace(); |
| |
| // Parse right-hand side (must be "null"). |
| Start = Pos; |
| while (Pos < Expr.size() && std::isalpha(Expr[Pos])) |
| ++Pos; |
| |
| StringRef RHS = Expr.slice(Start, Pos); |
| if (RHS != "null") |
| return createStringError("pointer comparisons only support 'null' as " |
| "right-hand side, got '" + |
| RHS.str() + "'"); |
| |
| // Check if the pointer is a constant null. |
| bool IsNull = false; |
| if (auto *C = dyn_cast<Constant>(LHS)) { |
| IsNull = C->isNullValue(); |
| } else { |
| // Non-constant pointer - assume filter passes (conservative) |
| return true; |
| } |
| |
| // Evaluate comparison |
| switch (Op) { |
| case EQ: |
| return IsNull; |
| case NE: |
| return !IsNull; |
| } |
| return true; |
| } |
| |
| // Dynamic property value, assume filter passes. |
| if (DynamicProperties.count(PropName)) |
| return true; |
| |
| // Unknown property, record an error. |
| return createStringError("expected enabled property name, got '" + |
| PropName.str() + "'"); |
| } |
| }; |
| } // anonymous namespace |
| |
| bool llvm::instrumentor::evaluateFilter(Value &V, bool &Changed, |
| InstrumentationOpportunity &IO, |
| InstrumentationConfig &IConf, |
| InstrumentorIRBuilderTy &IIRB) { |
| if (IO.Filter.empty()) |
| return true; |
| |
| // Collect constant property values for filter evaluation. |
| DenseMap<StringRef, int64_t> IntPropertyValues; |
| DenseMap<StringRef, StringRef> StringPropertyValues; |
| DenseMap<StringRef, Value *> PointerPropertyValues; |
| DenseMap<StringRef, PropertyType> DynamicProperties; |
| |
| for (auto &Arg : IO.IRTArgs) { |
| if (!Arg.Enabled) |
| continue; |
| |
| // Get the value for this argument. |
| Value *ArgValue = Arg.GetterCB(V, *Arg.Ty, IConf, IIRB); |
| if (!ArgValue) |
| continue; |
| |
| // TODO: This is likely too broad and we might want GetterCB to indicate |
| // changes. |
| Changed = true; |
| |
| if (auto *CI = dyn_cast<ConstantInt>(ArgValue)) { |
| // Check for constant integer values. |
| IntPropertyValues[Arg.Name] = CI->getSExtValue(); |
| } else if ((Arg.Flags & IRTArg::STRING) && isa<Constant>(ArgValue)) { |
| // Check for constant string values (marked with STRING flag). |
| if (auto *GV = dyn_cast<GlobalVariable>(ArgValue)) |
| if (GV->isConstant() && GV->hasInitializer()) |
| if (auto *CDA = dyn_cast<ConstantDataArray>(GV->getInitializer())) |
| if (CDA->isCString()) |
| StringPropertyValues[Arg.Name] = CDA->getAsCString(); |
| } else if (ArgValue->getType()->isPointerTy()) { |
| // Check for pointer values (for null comparisons), after the strings. |
| PointerPropertyValues[Arg.Name] = ArgValue; |
| } else { |
| // If the value is not constant, we skip it - the filter will pass |
| // for dynamic values - but we still want to report broken filters. |
| DynamicProperties[Arg.Name] = |
| Arg.Ty->isIntegerTy() |
| ? INT |
| : (Arg.Flags & IRTArg::STRING |
| ? STRING |
| : (Arg.Ty->isPointerTy() ? POINTER : UNKNOWN)); |
| } |
| } |
| |
| FilterEvaluator Evaluator(IO.Filter, IntPropertyValues, StringPropertyValues, |
| PointerPropertyValues, DynamicProperties); |
| |
| Expected<bool> Result = Evaluator.evaluate(); |
| if (!Result) { |
| // Emit an error if the filter is malformed. |
| IIRB.Ctx.diagnose(DiagnosticInfoInstrumentation( |
| Twine("malformed filter expression for instrumentation opportunity '") + |
| IO.getName() + Twine("': ") + toString(Result.takeError()) + |
| Twine("\nFilter: ") + IO.Filter, |
| DS_Error)); |
| return false; |
| } |
| |
| return Result.get(); |
| } |