[llvm] [Instrumentor] Add a property filter for static properties (PR #197530)
Kevin Sala Penades via llvm-commits
llvm-commits at lists.llvm.org
Wed May 13 16:52:42 PDT 2026
================
@@ -449,6 +449,447 @@ Value *InstrumentationOpportunity::getIdPost(Value &V, Type &Ty,
return getCI(&Ty, -getIdFromEpoch(IIRB.Epoch), /*IsSigned=*/true);
}
+namespace {
+/// 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;
+ size_t Pos = 0;
+ std::string ErrorMsg;
+
+public:
+ FilterEvaluator(StringRef Expr,
+ DenseMap<StringRef, int64_t> &&IntPropertyValues,
+ DenseMap<StringRef, StringRef> &&StringPropertyValues,
+ DenseMap<StringRef, Value *> &&PointerPropertyValues)
+ : Expr(Expr), IntPropertyValues(std::move(IntPropertyValues)),
+ StringPropertyValues(std::move(StringPropertyValues)),
+ PointerPropertyValues(std::move(PointerPropertyValues)), Pos(0) {}
+
+ bool evaluate() {
+ if (Expr.empty())
+ return true;
+ bool Result = parseOrExpr();
+
+ // Check if we consumed the entire expression
+ skipWhitespace();
+ if (Pos < Expr.size() && ErrorMsg.empty()) {
+ ErrorMsg =
+ "unexpected characters at position " + std::to_string(Pos) + ": '" +
+ Expr.substr(Pos, std::min<size_t>(10, Expr.size() - Pos)).str() + "'";
+ }
+
+ return Result;
+ }
+
+ bool hasError() const { return !ErrorMsg.empty(); }
+ StringRef getError() const { return ErrorMsg; }
+
+private:
+ void skipWhitespace() {
+ while (Pos < Expr.size() && std::isspace(Expr[Pos]))
+ ++Pos;
+ }
+
+ bool parseOrExpr() {
+ bool Result = parseAndExpr();
+ while (true) {
+ skipWhitespace();
+ if (Pos + 1 < Expr.size() && Expr[Pos] == '|' && Expr[Pos + 1] == '|') {
+ Pos += 2;
+ Result = parseAndExpr() || Result;
+ } else {
+ break;
+ }
+ }
+ return Result;
+ }
+
+ bool parseAndExpr() {
+ bool Result = parseComparison();
+ while (true) {
+ skipWhitespace();
+ if (Pos + 1 < Expr.size() && Expr[Pos] == '&' && Expr[Pos + 1] == '&') {
+ Pos += 2;
+ Result = parseComparison() && Result;
+ } else {
+ break;
+ }
+ }
+ return Result;
+ }
+
+ // Parse a quoted string literal
+ std::optional<StringRef> parseStringLiteral() {
+ skipWhitespace();
+ if (Pos >= Expr.size() || Expr[Pos] != '"') {
+ if (ErrorMsg.empty())
+ ErrorMsg = "expected string literal at position " + std::to_string(Pos);
+ return std::nullopt;
+ }
+
+ ++Pos; // Skip opening quote
+ size_t Start = Pos;
+ while (Pos < Expr.size() && Expr[Pos] != '"')
+ ++Pos;
+
+ if (Pos >= Expr.size()) {
+ if (ErrorMsg.empty())
+ ErrorMsg = "unclosed string literal starting at position " +
+ std::to_string(Start - 1);
+ return std::nullopt;
+ }
+
+ StringRef Result = Expr.slice(Start, Pos);
+ ++Pos; // Skip closing quote
+ return Result;
+ }
+
+ 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()) {
+ if (ErrorMsg.empty())
+ ErrorMsg = "expected property name at position " + std::to_string(Pos);
+ return true; // Return true to avoid cascading errors
+ }
+ 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] != '(') {
+ if (ErrorMsg.empty())
+ ErrorMsg = "expected '(' after 'startswith' at position " +
+ std::to_string(Pos);
+ return true;
+ }
+ ++Pos;
+
+ // Parse string argument
+ auto Prefix = parseStringLiteral();
+ if (!Prefix)
+ return true; // Error already set in parseStringLiteral
+
+ skipWhitespace();
+
+ // Parse )
+ if (Pos >= Expr.size() || Expr[Pos] != ')') {
+ if (ErrorMsg.empty())
+ ErrorMsg = "expected ')' to close 'startswith' call at position " +
+ std::to_string(Pos);
+ return true;
+ }
+ ++Pos;
+
+ // Evaluate startswith
+ auto StrIt = StringPropertyValues.find(PropName);
+ if (StrIt == StringPropertyValues.end())
+ return true; // Unknown property, assume filter passes
+
+ return StrIt->second.starts_with(*Prefix);
+ }
+
+ if (ErrorMsg.empty())
+ ErrorMsg = "unknown method '" + MethodName.str() + "' on property '" +
+ PropName.str() + "'";
+ return true;
+ }
+
+ // Check if this is an integer property
+ auto IntIt = IntPropertyValues.find(PropName);
+ if (IntIt != IntPropertyValues.end()) {
+ // Integer comparison
+ 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 {
+ if (ErrorMsg.empty())
+ ErrorMsg = "expected comparison operator (==, !=, <, >, <=, >=) at "
+ "position " +
+ std::to_string(Pos);
+ return true;
+ }
+ } else {
+ if (ErrorMsg.empty())
+ ErrorMsg = "expected comparison operator after property '" +
+ PropName.str() + "'";
+ return true;
+ }
+
+ 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) {
+ if (ErrorMsg.empty())
+ ErrorMsg =
+ "expected integer value at position " + std::to_string(Pos);
+ return true;
+ }
+
+ StringRef ValueStr = Expr.slice(Start, Pos);
+ int64_t RHS = 0;
+ if (ValueStr.getAsInteger(10, RHS)) {
+ if (ErrorMsg.empty())
+ ErrorMsg = "invalid integer value '" + ValueStr.str() + "'";
+ return true;
+ }
+ 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()) {
+ // String comparison
+ 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 {
+ if (ErrorMsg.empty())
+ ErrorMsg = "string property '" + PropName.str() +
+ "' only supports == and != operators";
+ return true;
+ }
+ } else {
+ if (ErrorMsg.empty())
+ ErrorMsg = "expected comparison operator after string property '" +
+ PropName.str() + "'";
+ return true;
+ }
+
+ skipWhitespace();
+
+ // Parse right-hand side (string literal)
+ auto RHS = parseStringLiteral();
+ if (!RHS)
+ return true; // Error already set in parseStringLiteral
+
+ // 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()) {
+ // Pointer comparison (only against null)
+ 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 {
+ if (ErrorMsg.empty())
+ ErrorMsg = "pointer property '" + PropName.str() +
+ "' only supports == and != operators";
+ return true;
+ }
+ } else {
+ if (ErrorMsg.empty())
+ ErrorMsg = "expected comparison operator after pointer property '" +
+ PropName.str() + "'";
+ return true;
+ }
+
+ 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") {
+ if (ErrorMsg.empty())
+ ErrorMsg = "pointer comparisons only support 'null' as right-hand "
+ "side, got '" +
+ RHS.str() + "'";
+ return true;
+ }
+
+ // 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;
+ }
+
+ // Unknown property, assume filter passes
+ if (ErrorMsg.empty())
+ ErrorMsg = "expected property name, got '" + PropName.str() + "'";
+ return true;
+ }
+};
+} // anonymous namespace
+
+bool InstrumentationOpportunity::evaluateFilter(Value &V,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ if (Filter.empty())
+ return true;
+
+ // Collect constant property values for filter evaluation
+ DenseMap<StringRef, int64_t> IntPropertyValues;
+ DenseMap<StringRef, StringRef> StringPropertyValues;
+ DenseMap<StringRef, Value *> PointerPropertyValues;
+
+ for (auto &Arg : IRTArgs) {
+ if (!Arg.Enabled)
+ continue;
+
+ // Get the value for this argument
+ Value *ArgValue = Arg.GetterCB(V, *Arg.Ty, IConf, IIRB);
+ if (!ArgValue)
+ continue;
+
+ 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;
+ }
+ // If the value is not constant, we skip it - the filter will pass
+ // for dynamic values as per the requirement
+ }
+
+ FilterEvaluator Evaluator(Filter, std::move(IntPropertyValues),
+ std::move(StringPropertyValues),
+ std::move(PointerPropertyValues));
+ bool Result = Evaluator.evaluate();
+
+ // Emit an error if the filter is malformed
+ if (Evaluator.hasError()) {
+ IIRB.Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("malformed filter expression for instrumentation opportunity '") +
+ getName() + Twine("': ") + Evaluator.getError() +
+ Twine("\nFilter: ") + Filter,
+ DS_Error));
+ return false; // Skip instrumentation on error
----------------
kevinsala wrote:
Conservative option would be to instrument it I guess.
https://github.com/llvm/llvm-project/pull/197530
More information about the llvm-commits
mailing list