import cpp
import semmle.code.cpp.AutogeneratedFile

/*
 *
 * Counting nontrivial literal occurrences
 *
 */

predicate trivialPositiveIntValue(string s) {
  s="0" or s="1" or s="2" or s="3" or s="4" or s="5" or s="6" or s="7" or s="8" or
  s="9" or s="10" or s="11" or s="12" or s="13" or s="14" or s="15" or s="16" or s="17" or
  s="18" or s="19" or s="20"

  or

  s="16" or s="24" or s="32" or s="64" or s="128" or s="256" or s="512" or s="1024" or
  s="2048" or s="4096" or s="16384" or s="32768" or s="65536" or
  s="1048576" or s="2147483648" or s="4294967296"

  or

  s="15" or s="31" or s="63" or s="127" or s="255" or s="511" or s="1023" or
  s="2047" or s="4095" or s="16383" or s="32767" or s="65535" or
  s="1048577" or s="2147483647" or s="4294967295"

  or

  s = "0x00000001" or s = "0x00000002" or s = "0x00000004" or s = "0x00000008" or s = "0x00000010" or s = "0x00000020" or s = "0x00000040" or s = "0x00000080" or s = "0x00000100" or s = "0x00000200" or s = "0x00000400" or s = "0x00000800" or s = "0x00001000" or s = "0x00002000" or s = "0x00004000" or s = "0x00008000" or s = "0x00010000" or s = "0x00020000" or s = "0x00040000" or s = "0x00080000" or s = "0x00100000" or s = "0x00200000" or s = "0x00400000" or s = "0x00800000" or s = "0x01000000" or s = "0x02000000" or s = "0x04000000" or s = "0x08000000" or s = "0x10000000" or s = "0x20000000" or s = "0x40000000" or s = "0x80000000" or
  s = "0x00000001" or s = "0x00000003" or s = "0x00000007" or s = "0x0000000f" or s = "0x0000001f" or s = "0x0000003f" or s = "0x0000007f" or s = "0x000000ff" or s = "0x000001ff" or s = "0x000003ff" or s = "0x000007ff" or s = "0x00000fff" or s = "0x00001fff" or s = "0x00003fff" or s = "0x00007fff" or s = "0x0000ffff" or s = "0x0001ffff" or s = "0x0003ffff" or s = "0x0007ffff" or s = "0x000fffff" or s = "0x001fffff" or s = "0x003fffff" or s = "0x007fffff" or s = "0x00ffffff" or s = "0x01ffffff" or s = "0x03ffffff" or s = "0x07ffffff" or s = "0x0fffffff" or s = "0x1fffffff" or s = "0x3fffffff" or s = "0x7fffffff" or s = "0xffffffff" or
  s = "0x0001" or s = "0x0002" or s = "0x0004" or s = "0x0008" or s = "0x0010" or s = "0x0020" or s = "0x0040" or s = "0x0080" or s = "0x0100" or s = "0x0200" or s = "0x0400" or s = "0x0800" or s = "0x1000" or s = "0x2000" or s = "0x4000" or s = "0x8000" or
  s = "0x0001" or s = "0x0003" or s = "0x0007" or s = "0x000f" or s = "0x001f" or s = "0x003f" or s = "0x007f" or s = "0x00ff" or s = "0x01ff" or s = "0x03ff" or s = "0x07ff" or s = "0x0fff" or s = "0x1fff" or s = "0x3fff" or s = "0x7fff" or s = "0xffff" or
  s = "0x01" or s = "0x02" or s = "0x04" or s = "0x08" or s = "0x10" or s = "0x20" or s = "0x40" or s = "0x80" or
  s = "0x01" or s = "0x03" or s = "0x07" or s = "0x0f" or s = "0x1f" or s = "0x3f" or s = "0x7f" or s = "0xff" or
  s = "0x00"

  or

  s = "10" or s = "100" or s = "1000" or s = "10000" or s = "100000" or s = "1000000" or s = "10000000" or s = "100000000" or s = "1000000000"
}

predicate trivialIntValue(string s) {
  trivialPositiveIntValue(s) or
  exists(string pos | trivialPositiveIntValue(pos) and s = "-" + pos)
}

predicate trivialLongValue(string s) {
  exists(string v | trivialIntValue(v) and s = v + "L")
}

predicate intTrivial(Literal lit) {
  exists(string v | trivialIntValue(v) and v = lit.getValue())
}

predicate longTrivial(Literal lit) {
  exists(string v | trivialLongValue(v) and v = lit.getValue())
}

predicate powerOfTen(float f) {
  f = 10 or f = 100 or f = 1000 or f = 10000 or f = 100000 or f = 1000000 or f = 10000000 or f = 100000000 or f = 1000000000
}

predicate floatTrivial(Literal lit) {
  lit.getType() instanceof FloatingPointType and
  exists(string value, float f |
    lit.getValue() = value and
    f = value.toFloat() and
    (f.abs() <= 20.0 or powerOfTen(f))
  )
}

predicate charLiteral(Literal lit) {
  lit instanceof CharLiteral
}


Type literalType(Literal literal) {
  result = literal.getType()
}

predicate stringType(DerivedType t) {
  t.getBaseType() instanceof CharType
  or
  exists(SpecifiedType constCharType |
    t.getBaseType() = constCharType and
    constCharType.isConst() and
    constCharType.getBaseType() instanceof CharType
  )
}

predicate numberType(Type t) {
  t instanceof FloatingPointType or t instanceof IntegralType
}

predicate stringLiteral(Literal literal) {
  literal instanceof StringLiteral
}

predicate stringTrivial(Literal lit) {
  stringLiteral(lit) and
  lit.getValue().length() < 8
}

predicate joiningStringTrivial(Literal lit) {
  // We want to be more lenient with string literals that are being
  // joined together, because replacing sentence fragments with named
  // constants could actually result in code that is harder to
  // understand (which is against the spirit of these queries).
  stringLiteral(lit) and
  exists(FunctionCall fc |
    (
      fc.getTarget().getName() = "operator+" or
      fc.getTarget().getName() = "operator<<"
    ) and
    fc.getAnArgument().getAChild*() = lit
  ) and
  lit.getValue().length() < 16
}

predicate small(Literal lit) {
  lit.getValue().length() <= 1
}

predicate trivial(Literal lit) {
  charLiteral(lit) or
  intTrivial(lit) or
  floatTrivial(lit) or
  stringTrivial(lit) or
  joiningStringTrivial(lit) or
  longTrivial(lit) or
  small(lit)
}

private predicate isReferenceTo(Variable ref, Variable to) {
  exists(VariableAccess a |
         ref.getInitializer().getExpr().getConversion().(ReferenceToExpr).getExpr() = a and a.getTarget() = to)
}

private predicate variableNotModifiedAfterInitializer(Variable v) {
  not exists(VariableAccess a | a.getTarget() = v and a.isModified()) and
  not exists(AddressOfExpr e | e.getAddressable() = v) and
  forall(Variable v2 |
         isReferenceTo(v2, v) |
         variableNotModifiedAfterInitializer(v2))
}

predicate literalIsConstantInitializer(Literal literal, Variable f) {
  f.getInitializer().getExpr() = literal and
  variableNotModifiedAfterInitializer(f) and
  not f instanceof Parameter
}

predicate literalIsEnumInitializer(Literal literal) {
  exists(EnumConstant ec | ec.getInitializer().getExpr() = literal)
}

predicate literalInArrayInitializer(Literal literal) {
  exists(AggregateLiteral arrayInit |
    arrayInitializerChild(arrayInit, literal)
  )
}

predicate arrayInitializerChild(AggregateLiteral parent, Expr e) {
  e = parent
  or
  exists (Expr mid | arrayInitializerChild(parent, mid) and e.getParent() = mid)
}

// i.e. not a constant folded expression
predicate literallyLiteral(Literal lit) {
  lit.getValueText().regexpMatch(".*\".*|\\s*+[-+]?+\\s*+(0[xob][0-9a-fA-F]|[0-9])[0-9a-fA-F,._]*+([eE][-+]?+[0-9,._]*+)?+\\s*+[a-zA-Z]*+\\s*+")
}

predicate nonTrivialValue(string value, Literal literal) {
  value = literal.getValue() and
  not trivial(literal) and
  not literalIsConstantInitializer(literal, _) and
  not literalIsEnumInitializer(literal) and
  not literalInArrayInitializer(literal) and
  not literal.isAffectedByMacro() and
  literallyLiteral(literal)
}

predicate valueOccurrenceCount(string value, int n) {
  n = strictcount(Location loc |
    exists(Literal lit |
      lit.getLocation() = loc |
      nonTrivialValue(value, lit)
    ) and
 
    // Exclude generated files (they do not have the same maintainability
    // concerns as ordinary source files) 
    not loc.getFile() instanceof AutogeneratedFile
  ) and
  n > 20
}

predicate occurenceCount(Literal lit, string value, int n) {
  valueOccurrenceCount(value, n) and
  value = lit.getValue() and
  nonTrivialValue(_, lit)
}


/*
 *
 * Literals repeated frequently
 *
 */

predicate check(Literal lit, string value, int n, File f) {
  // Check that the literal is nontrivial
  not trivial(lit) and
  // Check that it is repeated a number of times
  occurenceCount(lit, value, n) and n > 20 and
  f = lit.getFile() and
 
  // Exclude generated files 
  not f instanceof AutogeneratedFile
}

predicate checkWithFileCount(string value, int overallCount, int fileCount, File f) {
  fileCount = strictcount(Location loc |
                          exists(Literal lit |
                                 lit.getLocation() = loc |
                                 check(lit, value, overallCount, f)))
}

predicate start(Literal lit, int startLine) {
  exists(Location l | l = lit.getLocation() and startLine = l.getStartLine())
}

predicate firstOccurrence(Literal lit, string value, int n) {
  exists(File f, int fileCount |
    checkWithFileCount(value, n, fileCount, f) and
    fileCount < 100 and
    check(lit, value, n, f) and
    not exists(Literal lit2, int start1, int start2 |
      check(lit2, value, n, f) and
      start(lit, start1) and
      start(lit2, start2) and
      start2 < start1)
  )
}

predicate magicConstant(Literal e, string msg) {
  exists(string value, int n | firstOccurrence(e, value, n)
    and msg = "Magic constant: literal '" + value + "' is repeated " + n.toString() + " times and should be encapsulated in a constant.")
}

/*
 *
 * Literals where there is a defined constant with the same value
 *
 */

predicate relevantVariable(Variable f, string value) {
  exists(Literal lit | not trivial(lit) and value = lit.getValue() and literalIsConstantInitializer(lit, f))
}

predicate relevantCallable(Function f, string value) {
  exists(Literal lit | not trivial(lit) and value = lit.getValue() and lit.getEnclosingFunction() = f)
}


predicate isVisible(Variable field, Function fromCallable) {
  exists(string value  |
  //public fields
  (
    relevantVariable(field, value) and
    field.(MemberVariable).isPublic() and
    relevantCallable(fromCallable, value)
  )
  or
  //in same class
  (
    relevantVariable(field, value) and
    exists(Type t |
      t = field.getDeclaringType() and
      t = fromCallable.getDeclaringType())
    and relevantCallable(fromCallable, value)
  )
  or
  //in subclass and not private
  (
    relevantVariable(field, value) and
    not field.(MemberVariable).isPrivate() and
    exists(Class sup, Class sub |
      sup = field.getDeclaringType() and
      sub.getABaseClass+() = sup and
      sub = fromCallable.getDeclaringType()
    ) and
    relevantCallable(fromCallable, value)
  )
  )
}

predicate canUseFieldInsteadOfLiteral(Variable constField, Literal magicLiteral) {
  exists(Literal initLiteral |
    literalIsConstantInitializer(initLiteral, constField) and
    not trivial(initLiteral) and
    not constField.getType().hasName("boolean") and
    exists(string value |
      value = initLiteral.getValue() and
      magicLiteral.getValue() = value
    )
    and constField.getType() = magicLiteral.getType()
    and not literalIsConstantInitializer(magicLiteral, _)
    and exists(Function c |
      c = magicLiteral.getEnclosingFunction() and
      (
        (
           constField.isTopLevel() and (not constField.isStatic() or constField.getFile() = c.getFile())
        )
        or
        isVisible(constField,c)
      )
    )
  )
}

predicate literalInsteadOfConstant(Literal magicLiteral, string message, Variable constField, string linkText) {
  canUseFieldInsteadOfLiteral(constField, magicLiteral) and
  message =
    "Literal value '" + magicLiteral.getValue() + "' used instead of constant $@." and
  linkText = constField.getName()
}
