Компилятор: как проверить, правильно ли возвращается пользовательская функция? - PullRequest
0 голосов
/ 05 декабря 2018

Я пишу очень простой компилятор, в котором пользователям разрешено определять функции, которые возвращают либо void, int, либо char.Тем не менее, функция пользователя может быть нарушена.Они могут не возвращать значение для функции, которая не возвращает void, или возвращать значение для функции, которая возвращает void, как объявлено.В настоящее время мой компилятор не может обнаружить ошибки такого рода и не может создать правильный код для функции, которая возвращает void, поскольку этот класс функций может возвращаться без return; (они возвращаются неявно).Эти две проблемы стоили мне довольно много времени, чтобы четко их сформулировать.Смотрите пример кода ниже:

// Problem A: detect implicit return.
void Foo(int Arg) {
  if (Arg)
    return;
  else {
    Arg = 1;
    // Foo returns here! How can I know!
  }
}

// Problem B: detect "forgotten return".
int Bar(int Arg) {
  if (Arg > 1) {
    return 1;
  }
  // this is an error: control flow reaches end at non-void function!
  // How can I know!
}

Я думаю, что более общий вопрос может быть: как я могу сказать, что поток управления достигает конца в некоторой точке функции?Говоря достичь конца я имею в виду, что он достигает точки, после которой у функции нет кода для выполнения.Если я могу определить конец потока управления, я могу найти return в этой точке и либо сообщить об ошибке, если функция должна что-то вернуть, либо сгенерировать явное return для функции void.Если я перечислю все такие точки функции, я смогу убедиться, что функция полностью проверена или дополнена.

Я считаю эту проблему хорошо решенной в инженерии компиляторов, поскольку современный C / C ++ может сделать это довольноЧто ж.LLVM может предложить какой-либо API для этого?Или есть простой алгоритм для достижения этой цели?Большое спасибо.

Редактировать: В настоящее время я использую LLVM и уже BasicBlock испущено.Я надеюсь, что руководство сделает это особенно в LLVM.

Edit: В этом вопросе мы предполагаем, что тип возвращаемого значения, объявленный в прототипе функции всегда , соответствует этомуего return stmt.Я в первую очередь сосредотачиваюсь на отсутствии необходимого return.

1 Ответ

0 голосов
/ 05 декабря 2018

Ответ прост.После того, как все BB функции выведены, зациклите их и возьмите те концы без Терминатора (см. Документ llvm для получения инструкции Терминатора).Предполагая, что передача всех видов операторов потока управления (Пока, Для и т. Д.) Следует правилу (Один BB заканчивается одним и только одним Терминатором), единственное возможное объяснение этих нарушителей правил состоит в том, что они пропускают Return ИК в конце концов.Если текущая функция возвращает void, добавьте к ним ReturnVoid.В противном случае, это ошибка, сообщите об этом.

Обоснование в значительной степени правильное, поскольку оно основано на правильно сформированном свойстве BB LLVM, и его легко реализовать, дешево запустить.Вот код:

/// Generate body for a Function.
void visitFuncDef(FuncDef *FD) {
  // Unrelated code omitted...

  /// Generate the body
  for (Stmt *S : FD->stmts) {
    visitStmt(S);
  }

  /// Check for well-formness of all BBs. In particular, look for
  /// any unterminated BB and try to add a Return to it.
  for (BasicBlock &BB : *Fn) {
    Instruction *Terminator = BB.getTerminator();
    if (Terminator != nullptr) continue; /// Well-formed
    if (Fn->getReturnType()->isVoidTy()) {
      /// Make implicit return of void Function explicit.
      Builder.SetInsertPoint(&BB);
      Builder.CreateRetVoid();
    } else {
      // How to attach source location?
      EM.Error("control flow reaches end of non-void function");
      // No source location, make errors short
      return;
    }
  }

  /// Verify the function body
  String ErrorMsg;
  llvm::raw_string_ostream OS(ErrorMsg);
  if (llvm::verifyFunction(*Fn, &OS)) {
    EM.Error(ErrorMsg);
  }
}
...