//===-- 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 &IntPropertyValues; DenseMap &StringPropertyValues; DenseMap &PointerPropertyValues; DenseMap &DynamicProperties; size_t Pos = 0; public: FilterEvaluator(StringRef Expr, DenseMap &IntPropertyValues, DenseMap &StringPropertyValues, DenseMap &PointerPropertyValues, DenseMap &DynamicProperties) : Expr(Expr), IntPropertyValues(IntPropertyValues), StringPropertyValues(StringPropertyValues), PointerPropertyValues(PointerPropertyValues), DynamicProperties(DynamicProperties) {} Expected evaluate() { if (Expr.empty()) return true; Expected 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(10, Expr.size() - Pos)).str() + "'"); return Result; } private: void skipWhitespace() { while (Pos < Expr.size() && std::isspace(Expr[Pos])) ++Pos; } Expected parseOrExpr() { Expected Result = parseAndExpr(); while (Result) { skipWhitespace(); if (Pos + 1 < Expr.size() && Expr[Pos] == '|' && Expr[Pos + 1] == '|') { Pos += 2; Expected NextResult = parseAndExpr(); if (!NextResult) return NextResult; *Result |= *NextResult; } else { break; } } return Result; } Expected parseAndExpr() { Expected Result = parsePrimary(); while (Result) { skipWhitespace(); if (Pos + 1 < Expr.size() && Expr[Pos] == '&' && Expr[Pos + 1] == '&') { Pos += 2; Expected NextResult = parsePrimary(); if (!NextResult) return NextResult; *Result &= *NextResult; } else { break; } } return Result; } Expected parsePrimary() { skipWhitespace(); // Check for opening parenthesis. if (Pos < Expr.size() && Expr[Pos] == '(') { ++Pos; // Skip '(' Expected 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 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 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(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 IntPropertyValues; DenseMap StringPropertyValues; DenseMap PointerPropertyValues; DenseMap 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(ArgValue)) { // Check for constant integer values. IntPropertyValues[Arg.Name] = CI->getSExtValue(); } else if ((Arg.Flags & IRTArg::STRING) && isa(ArgValue)) { // Check for constant string values (marked with STRING flag). if (auto *GV = dyn_cast(ArgValue)) if (GV->isConstant() && GV->hasInitializer()) if (auto *CDA = dyn_cast(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 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(); }