/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "class_verifier.h"

#include <android-base/logging.h>
#include <android-base/stringprintf.h>

#include "art_method-inl.h"
#include "base/locks.h"
#include "base/logging.h"
#include "base/pointer_size.h"
#include "base/systrace.h"
#include "base/utils.h"
#include "class_linker.h"
#include "compiler_callbacks.h"
#include "dex/class_accessor-inl.h"
#include "dex/class_reference.h"
#include "dex/descriptors_names.h"
#include "dex/dex_file-inl.h"
#include "handle.h"
#include "handle_scope-inl.h"
#include "method_verifier-inl.h"
#include "mirror/class-inl.h"
#include "mirror/dex_cache.h"
#include "runtime.h"
#include "thread.h"
#include "verifier_compiler_binding.h"
#include "verifier/method_verifier.h"
#include "verifier/reg_type_cache.h"

namespace art HIDDEN {
namespace verifier {

using android::base::StringPrintf;

// We print a warning blurb about "dx --no-optimize" when we find monitor-locking issues. Make
// sure we only print this once.
static bool gPrintedDxMonitorText = false;

static void UpdateMethodFlags(uint32_t method_index,
                              Handle<mirror::Class> klass,
                              Handle<mirror::DexCache> dex_cache,
                              CompilerCallbacks* callbacks,
                              int error_types)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  if (callbacks != nullptr && !CanCompilerHandleVerificationFailure(error_types)) {
    MethodReference ref(dex_cache->GetDexFile(), method_index);
    callbacks->AddUncompilableMethod(ref);
  }
  if (klass == nullptr) {
    DCHECK(Runtime::Current()->IsAotCompiler());
    // Flags will be set at runtime.
    return;
  }

  // Mark methods with DontCompile/MustCountLocks flags.
  ClassLinker* const linker = Runtime::Current()->GetClassLinker();
  ArtMethod* method =
      klass->FindClassMethod(dex_cache.Get(), method_index, linker->GetImagePointerSize());
  DCHECK(method != nullptr);
  DCHECK(method->GetDeclaringClass() == klass.Get());
  if (!CanCompilerHandleVerificationFailure(error_types)) {
    method->SetDontCompile();
  }
  if ((error_types & VerifyError::VERIFY_ERROR_LOCKING) != 0) {
    method->SetMustCountLocks();
  }
}

FailureKind ClassVerifier::VerifyClass(Thread* self,
                                       VerifierDeps* verifier_deps,
                                       const DexFile* dex_file,
                                       Handle<mirror::Class> klass,
                                       Handle<mirror::DexCache> dex_cache,
                                       Handle<mirror::ClassLoader> class_loader,
                                       const dex::ClassDef& class_def,
                                       CompilerCallbacks* callbacks,
                                       HardFailLogMode log_level,
                                       uint32_t api_level,
                                       std::string* error) {
  // A class must not be abstract and final.
  if ((class_def.access_flags_ & (kAccAbstract | kAccFinal)) == (kAccAbstract | kAccFinal)) {
    *error = "Verifier rejected class ";
    *error += PrettyDescriptor(dex_file->GetClassDescriptor(class_def));
    *error += ": class is abstract and final.";
    return FailureKind::kHardFailure;
  }

  // Note that `klass` can be a redefined class, not in the loader's table yet.
  // Therefore, we do not use it for class resolution, but only when needing to
  // update its methods' flags.
  ClassAccessor accessor(*dex_file, class_def);
  SCOPED_TRACE << "VerifyClass " << PrettyDescriptor(accessor.GetDescriptor());
  metrics::AutoTimer timer{GetMetrics()->ClassVerificationTotalTime()};

  int64_t previous_method_idx[2] = { -1, -1 };
  MethodVerifier::FailureData failure_data;
  ClassLinker* const linker = Runtime::Current()->GetClassLinker();

  if (accessor.NumMethods() != 0u) {
    ArenaPool* arena_pool = Runtime::Current()->GetArenaPool();
    RegTypeCache reg_types(self, linker, arena_pool, class_loader, dex_file);
    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
      int64_t* previous_idx = &previous_method_idx[method.IsStaticOrDirect() ? 0u : 1u];
      self->AllowThreadSuspension();
      const uint32_t method_idx = method.GetIndex();
      if (method_idx == *previous_idx) {
        // smali can create dex files with two encoded_methods sharing the same method_idx
        // http://code.google.com/p/smali/issues/detail?id=119
        continue;
      }
      *previous_idx = method_idx;
      std::string hard_failure_msg;
      MethodVerifier::FailureData result =
          MethodVerifier::VerifyMethod(self,
                                       arena_pool,
                                       &reg_types,
                                       verifier_deps,
                                       method_idx,
                                       dex_cache,
                                       class_def,
                                       method.GetCodeItem(),
                                       method.GetAccessFlags(),
                                       log_level,
                                       api_level,
                                       Runtime::Current()->IsAotCompiler(),
                                       &hard_failure_msg);
      if (result.kind == FailureKind::kHardFailure) {
        if (failure_data.kind == FailureKind::kHardFailure) {
          // If we logged an error before, we need a newline.
          *error += "\n";
        } else {
          // If we didn't log a hard failure before, print the header of the message.
          *error += "Verifier rejected class ";
          *error += PrettyDescriptor(dex_file->GetClassDescriptor(class_def));
          *error += ":";
        }
        *error += " ";
        *error += hard_failure_msg;
      } else if (result.kind != FailureKind::kNoFailure) {
        UpdateMethodFlags(method.GetIndex(), klass, dex_cache, callbacks, result.types);
        if ((result.types & VerifyError::VERIFY_ERROR_LOCKING) != 0) {
          // Print a warning about expected slow-down.
          // Use a string temporary to print one contiguous warning.
          std::string tmp =
              StringPrintf("Method %s failed lock verification and will run slower.",
                           dex_file->PrettyMethod(method.GetIndex()).c_str());
          if (!gPrintedDxMonitorText) {
            tmp +=
                "\nCommon causes for lock verification issues are non-optimized dex code\n"
                "and incorrect proguard optimizations.";
            gPrintedDxMonitorText = true;
          }
          LOG(WARNING) << tmp;
        }
      }

      // Merge the result for the method into the global state for the class.
      failure_data.Merge(result);
    }
  }
  uint64_t elapsed_time_microseconds = timer.Stop();
  VLOG(verifier) << "VerifyClass took " << PrettyDuration(UsToNs(elapsed_time_microseconds))
                 << ", class: " << PrettyDescriptor(dex_file->GetClassDescriptor(class_def));

  GetMetrics()->ClassVerificationCount()->AddOne();

  GetMetrics()->ClassVerificationTotalTimeDelta()->Add(elapsed_time_microseconds);
  GetMetrics()->ClassVerificationCountDelta()->AddOne();

  if (failure_data.kind == verifier::FailureKind::kHardFailure && callbacks != nullptr) {
    ClassReference ref(dex_file, dex_file->GetIndexForClassDef(class_def));
    callbacks->ClassRejected(ref);
  }

  return failure_data.kind;
}

}  // namespace verifier
}  // namespace art
